Skip to content

Node.js / TypeScript

개요

Machbase TypeScript 클라이언트(@machbase/ts-client)는 Machbase CMI 프로토콜을 순수 TypeScript로 구현한 라이브러리입니다. Node.js 애플리케이션이 네이티브 바인딩 없이도 Machbase(스탠더드 에디션) 서버에 연결해 SQL 실행, 결과 조회, Prepared Statement 처리, 로그 데이터 Append를 수행할 수 있습니다.

이 문서는 설치 방법, 핵심 API, 실용적인 예제, 테스트 흐름, 주의해야 할 동작 특성을 다룹니다.

설치

요구 사항

  • Node.js 18 이상(LTS 권장)
  • 접속 가능한 Machbase 서버(스탠더드 에디션)

npm에서 설치

패키지 매니저로 설치합니다.

npm install @machbase/ts-client
# or
yarn add @machbase/ts-client
# or
pnpm add @machbase/ts-client

오프라인 설치

Machbase에서 .tgz 패키지를 전달받은 경우:

# example file name; your version may differ
npm install ./machbase-ts-client-0.9.0.tgz

설치 확인

node -e "const { createConnection } = require('@machbase/ts-client'); console.log(typeof createConnection === 'function' ? 'ts-client import ok' : 'ts-client import failed')"

참고: 이 클라이언트는 Node.js에서 TCP 소켓을 사용하며, 브라우저용 라이브러리(웹소켓 전송)를 제공하지 않습니다.

이 문서의 기본 계정(SYS/MANAGER)은 로컬 테스트용 예시입니다. 운영 환경에서는 전용 계정과 비밀번호를 사용하세요.

빠르게 시작하기

아래 예제는 로컬 서버에 연결해 샘플 테이블을 생성하고 데이터를 삽입·조회한 뒤 세션을 종료하는 흐름을 보여줍니다.

// src/example.ts
import { createConnection } from '@machbase/ts-client';

const conn = createConnection({
  host: process.env.MACH_HOST ?? '127.0.0.1',
  port: +(process.env.MACH_PORT ?? 5656),
  user: process.env.MACH_USER ?? 'SYS',
  password: process.env.MACH_PASS ?? 'MANAGER',
});

await conn.connect();
const [rows] = await conn.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]);
console.log(rows);
await conn.end();

CommonJS 예제

// quickstart.js (CommonJS, Node 18+)
const { createConnection } = require('@machbase/ts-client');

async function main() {
  const conn = createConnection({
    host: '127.0.0.1',
    user: 'SYS',
    password: 'MANAGER',
    port: 5656,
  });
  await conn.connect();

  const [rows] = await conn.query('SELECT * FROM V$TABLES ORDER BY NAME LIMIT ?', [10]);
  console.table(rows);

  await conn.end();
}

main().catch(err => console.error('Unexpected failure:', err));

트랜잭션 안내: Machbase는 모든 명령을 자동 커밋합니다. BEGIN, COMMIT, ROLLBACK 같은 명령은 항상 에러를 반환하므로, 트랜잭션을 지원하지 않음을 확인하는 용도로만 사용하십시오.

Machbase 페이사드

익숙한 Node.js SQL 클라이언트 스타일을 선호한다면 createConnection()으로 제공되는 페이사드를 사용할 수 있습니다.

// facade-basic.js (CommonJS)
const { createConnection } = require('@machbase/ts-client');

async function bootstrap() {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  try {
    const [rows, fields] = await conn.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [3]);
    console.log('rows', rows, 'fields', fields?.map(f => f.name));

    await new Promise((resolve, reject) =>
      conn.query('SELECT VALUE FROM V$SYSSTAT WHERE NAME = ?', ['SERVER_VERSION'], (err, result) => {
        if (err) return reject(err);
        console.log('callback result', result);
        resolve();
      })
    );
  } finally {
    await conn.end();
  }
}

bootstrap().catch(console.error);

페이사드는 콜백과 .promise()를 모두 지원하고, 실패 시 QueryError를 반환하며, 서버 메시지를 그대로 전달합니다.

페이사드 제약: beginTransaction, commit, rollback은 Machbase가 SQL 트랜잭션을 지원하지 않으므로 즉시 QueryError를 반환합니다. LOG/TAG 테이블에 대한 UPDATE도 서버 오류로 바로 실패합니다.

자주 발생하는 문제

  • ECONNREFUSED – 서버가 실행 중인지(machadmin -u), 호스트와 포트가 맞는지, 방화벽이 리스너 포트(기본 5656)의 TCP 연결을 허용하는지 확인하세요.
  • Authentication failed – 사용자/비밀번호를 다시 확인하고 대상 데이터베이스가 생성되어 있는지(machadmin -c) 점검하세요.

API 참조

연결 관리

createConnection(config)

Machbase 리스너에 네트워크 세션을 열고 CMI 핸드셰이크를 완료합니다.

매개변수타입기본값설명
hoststring127.0.0.1Machbase 서버 IP 또는 호스트명
portnumber5656리스너 포트
userstring데이터베이스 사용자(기본 SYS)
passwordstring비밀번호(기본 MANAGER)
databasestringdata데이터베이스 이름
clientIdstringCLI서버 로그에 표시될 클라이언트 ID
showHiddenColumnsbooleanfalse메타데이터에 숨김 컬럼 포함 여부
timezonestring빈 값선택적 타임존 식별자
connectTimeoutnumber5000소켓 연결 타임아웃(ms)
queryTimeoutnumber60000명령별 타임아웃(ms)
const conn = createConnection({ host: '192.168.1.10', user: 'SYS', password: 'MANAGER' });
await conn.connect();

소켓 연결 실패, 인증 오류, 핸드셰이크 응답 이상 시 프로미스가 reject됩니다.

connect()

서버와의 연결을 엽니다.

await conn.connect();

end()

소켓 연결을 종료합니다. end() 호출 이후 추가 작업을 시도하면 에러가 발생합니다.

await conn.end();

SQL 실행

execute(sql, values?)

결과 집합을 반환하지 않을 수도 있는 명령을 실행합니다. DDL(CREATE, ALTER, DROP)이나 DML(INSERT, UPDATE, DELETE)에 사용하십시오.

const [create] = await conn.execute('CREATE TABLE demo (ID INTEGER, NAME VARCHAR(32))');
console.log('Rows affected:', create.affectedRows); // -> 0 for DDL

const [insert] = await conn.execute("INSERT INTO demo VALUES (1, 'alpha')");
console.log('Rows affected:', insert.affectedRows); // -> 1

await expectTransactionUnsupported(conn, 'COMMIT');

통합 테스트에서 사용하는 보조 함수:

async function expectTransactionUnsupported(conn, sql) {
  try {
    await conn.execute(sql);
    throw new Error(`Expected ${sql} to fail because Machbase does not support transactions.`);
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    console.log(`${sql} expected failure:`, msg);
  }
}

query(sql, values?)

행을 반환하는 쿼리를 실행합니다. 반환값은 [rows, fields] 형태의 2요소 튜플입니다.

const [rows, fields] = await conn.query('SELECT ID, NAME FROM demo ORDER BY ID');
console.table(rows);

Prepared Statement 사용

prepare(sql)

서버에 Prepared Statement를 생성합니다.

const stmt = await conn.prepare('SELECT NAME FROM demo WHERE ID = ?');
try {
  const [rows] = await stmt.execute([1]);
  console.log(rows); // -> [ { NAME: 'alpha' } ]
} finally {
  await stmt.close();
}

반환된 객체에서 제공하는 메서드는 다음과 같습니다.

  • execute(parameters?) – 문을 실행하고 [rowsOrPacket, fields]를 반환합니다.
  • getColumns() – 컬럼 메타데이터 캐시를 반환합니다.
  • getLastMessage() – 최근 서버 메시지를 확인합니다.
  • getStatementId() – 내부 Statement ID를 조회합니다.
  • close() – 서버 리소스를 정리합니다. 여러 번 호출해도 안전합니다.

Prepared Statement Examples

Prepared SELECT 재사용:

const select = await conn.prepare('SELECT DEVICE_ID, SENSOR_VALUE FROM sensors WHERE DEVICE_ID = ?');
for (const { id } of samples) {
  const [rows] = await select.execute([id]);
  console.log(`selected ${id}:`, rows);
}
await select.close();

Prepared Upsert:

const upsert = await conn.prepare('INSERT INTO devices VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE');
const [result] = await upsert.execute([deviceId, firstValue, firstValue]);
console.log('Affected rows:', result.affectedRows);
await upsert.close();

타입 지정 인자와 NULL 처리:

await update.execute([
  { value: null, type: 'varchar' },
  { value: new Date(), type: 'varchar' },
  { value: 'sensor-200', type: 'varchar' },
]);

실행 예제 스크립트는 보통 npm run builddist/examples/ 아래에 생성됩니다. 예제는 일반적으로 MACHBASE_EXAMPLE_*, MACHBASE_SMOKE_*, 마지막으로 SYS/MANAGER@127.0.0.1 순서로 접속 정보를 찾습니다.

Append Protocol

appendBatch(table, columns, rows, options?)

CMI_APPEND_BATCH_PROTOCOL을 사용해 로그 테이블에 행을 추가합니다. 사용자에게 보이는 컬럼만 전달하면 됩니다(로그 테이블에는 _arrival_time, _rid가 자동 포함됩니다).

const appendResult = await conn.appendBatch(
  'sensor_log',
  [
    { name: 'ID', type: 'int32' },
    { name: 'NAME', type: 'varchar' },
    { name: 'VALUE', type: 'float64' },
  ],
  [
    [1, 'alpha', 0.5],
    { values: [2, 'bravo', 1.25], arrivalTime: Date.now() * 1_000_000 },
  ],
);
console.log('Appended rows:', appendResult.rowsAppended);

지원 컬럼 타입: int32, int64, float64, varchar.

  • rows는 값 배열 또는 { values, arrivalTime } 객체 배열을 받을 수 있습니다. null은 Machbase 센티널 값으로 자동 인코딩됩니다.
  • optionsarrivalTime(기본값 1개) 또는 arrivalTimes(행별 배열)를 지정할 수 있습니다.

반환값은 { table, rowsAppended, rowsFailed, message } 형태입니다.

: “column count does not match” 오류는 대상 테이블이 로그 테이블이 아니거나, 컬럼 순서가 스키마와 일치하지 않을 때 발생합니다. TAG 테이블에는 appendOpen()을 사용하세요.

appendOpen(table, columns, options?)

경량 Append 세션을 엽니다. 기본적으로 네이티브 APPEND open/data/close 흐름을 사용하며, 성공한 네이티브 쓰기는 청크별 응답을 반환하지 않습니다.

const stream = await conn.appendOpen('sensor_log', [
  { name: 'ID', type: 'int32' },
  { name: 'NAME', type: 'varchar' },
  { name: 'VALUE', type: 'float64' },
]);

await stream.append([
  [1, 'alpha', 0.5],
  [2, 'bravo', 1.25],
]);

await stream.append({ values: [3, 'charlie', 2.5] });
await stream.close();

네이티브 Append를 끄고 Prepared Statement 기반으로 강제하려면 MACHBASE_NATIVE_APPEND=0을 설정하세요. 서버가 특정 테이블 타입이나 세션에서 네이티브 Append를 지원하지 않으면 페이사드가 자동으로 Prepared Statement 방식으로 폴백합니다.

TAG 테이블의 DATETIME 컬럼에는 Date 객체 또는 bigint epoch 값을 전달하세요.

append(rows) on an append stream

열린 Append 스트림으로 하나 이상의 행을 전송합니다.

const frames = await stream.append([
  ['S-001', new Date(), 1.0],
  ['S-002', new Date(Date.now() + 1), 2.0],
]);
console.log('frames sent:', frames);

네이티브 모드에서는 최대 처리량을 위해 성공 응답이 생략되며, 오류가 있을 때만 실패 패킷이 반환됩니다.

Helper Methods

ping()

SELECT 1 FROM V$TABLES로 연결 상태를 점검합니다.

await conn.ping();

promise()

익숙한 .promise()와 같은 형태의 래퍼를 제공합니다.

const p = conn.promise();
await p.ping();
const [rows] = await p.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]);

escape, escapeId, format

SQL 문자열을 안전하게 구성하기 위한 유틸리티입니다.

const safeName = conn.escapeId('table_name');
const safeValue = conn.escape('user input');

테스트 및 진단

스크립트

  • npm run build – TypeScript 컴파일
  • npm run lintsrc/에 ESLint 수행
  • npm run smoke – 선택적 스모크 테스트(환경변수 없으면 생략)
  • npm test – 통합 스위트(실서버 필요)
    1. 로그 테이블 생성
    2. 샘플 데이터 INSERT/SELECT
    3. 자리기반 바인딩 준비문 시연
    4. append 부하 테스트(기본: 5배치 x 200행) 및 건수 검증
    5. 각 단계에서 COMMIT을 호출하여 트랜잭션 미지원 동작 확인
    6. Machbase 페이사드와 UPDATE 제한 동작 검증

샘플 출력:

COMMIT expected failure: Expected COMMIT to fail because Machbase does not support transactions.
machbase-facade-basic callback query returned 3 rows.
machbase-facade-update-log-fails message: UPDATE is not supported for LOG tables.
append-batch progress: batch 4/5 { table: 'TS_CLIENT_IT_...', rowsAppended: 200, rowsFailed: 0 }
append-batch final count: 1004

튜토리얼

빠른 시작 (로그 테이블)

// quickstart-log.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', port: 5656, user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  const table = 'JS_LOG_' + Math.random().toString(36).slice(2, 7).toUpperCase();
  try {
    await conn.execute(`CREATE LOG TABLE "${table}" (ID INTEGER, NAME VARCHAR(64), VALUE DOUBLE)`);
    await conn.execute(`INSERT INTO "${table}" VALUES (1, 'A', 0.5)`);
    const [rows] = await conn.query(`SELECT * FROM "${table}" ORDER BY ID`);
    console.table(rows);
  } finally {
    await conn.execute(`DROP TABLE "${table}"`);
    await conn.end();
  }
})();

Prepared Statement 재사용

// prepared-reuse.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  const table = 'JS_VOL_' + Math.random().toString(36).slice(2, 7).toUpperCase();
  try {
    await conn.execute(`CREATE VOLATILE TABLE "${table}" (ID INTEGER PRIMARY KEY, NAME VARCHAR(64))`);
    for (let i = 1; i <= 3; i++) await conn.execute(`INSERT INTO "${table}" VALUES (${i}, 'N${i}')`);
    const stmt = await conn.prepare(`SELECT NAME FROM "${table}" WHERE ID = ?`);
    try {
      for (const id of [1, 2, 3]) {
        const [rows] = await stmt.execute([id]);
        console.log(id, rows[0]?.NAME);
      }
    } finally {
      await stmt.close();
    }
  } finally {
    await conn.execute(`DROP TABLE "${table}"`);
    await conn.end();
  }
})();

로그 테이블 배치 Append

// append-batch.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  const table = 'JS_LOGAPP_' + Math.random().toString(36).slice(2, 7).toUpperCase();
  try {
    await conn.execute(`CREATE LOG TABLE "${table}" (ID INTEGER, NAME VARCHAR(64), VALUE DOUBLE)`);
    const result = await conn.appendBatch(
      table,
      [
        { name: 'ID', type: 'int32' },
        { name: 'NAME', type: 'varchar' },
        { name: 'VALUE', type: 'float64' },
      ],
      [[1, 'X', 0.5], [2, 'Y', 1.25]],
    );
    console.log(result);
  } finally {
    await conn.execute(`DROP TABLE "${table}"`);
    await conn.end();
  }
})();

TAG 테이블 스트리밍 Append

// append-tag-stream.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  const table = 'JS_TAG_' + Math.random().toString(36).slice(2, 7).toUpperCase();
  try {
    await conn.execute(`CREATE TAG TABLE "${table}" (name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED)`);
    const stream = await conn.appendOpen(table, [
      { name: 'NAME', type: 'varchar' },
      { name: 'TIME', type: 'int64' },
      { name: 'VALUE', type: 'float64' },
    ]);
    const now = Date.now();
    await stream.append([
      ['T-0001', new Date(now), 1.0],
      ['T-0002', new Date(now + 1), 2.0],
    ]);
    await stream.close();
    const [rows] = await conn.query(`SELECT COUNT(*) AS CNT FROM "${table}"`);
    console.log('count', rows[0]?.CNT);
  } finally {
    await conn.execute(`DROP TABLE "${table}"`);
    await conn.end();
  }
})();

네이티브 모드는 기본 활성화입니다. 비활성화하려면 MACHBASE_NATIVE_APPEND=0을 설정하세요. 성공 시 청크별 응답은 생략되고, 오류만 실패 응답으로 전달됩니다.

Promise 래퍼와 Ping

// promise-and-ping.js
const { createConnection } = require('@machbase/ts-client');

(async () => {
  const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' });
  await conn.connect();
  try {
    const p = conn.promise();
    await p.ping(); // SELECT 1 FROM V$TABLES
    const [rows] = await p.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]);
    console.log(rows.map(r => r.NAME));
  } finally {
    await conn.end();
  }
})();

동작 특성과 한계

트랜잭션

Machbase는 모든 명령을 자동 커밋합니다. BEGIN, COMMIT, ROLLBACK 같은 트랜잭션 키워드는 항상 실패하며, 래퍼도 QueryError(ERR_MACHBASE_NO_TX)를 통해 동일하게 알립니다.

try {
  await conn.execute('COMMIT');
} catch (err) {
  console.log('Expected error:', err.message);
  // Error: Machbase does not support transactions
}

결과 버퍼링 및 페이지네이션

래퍼의 query 메서드는 전체 결과 집합을 버퍼링한 뒤 반환합니다. 대용량 테이블에서는 ORDER BY … LIMIT 쿼리나 기본 키 범위를 이용해 직접 페이지를 나누세요.

파라미터 바인딩

지원 타입은 int32, int64, float64, varchar 등 범용 스칼라 타입입니다. null을 전달할 경우 명시적 타입을 함께 지정하세요.

{ value: null, type: 'varchar' }

Append 프로토콜

로그 테이블에는 appendBatch를, 점진적 유입이 필요한 경우 스트리밍 도우미(appendOpen/append)를 사용하세요. 특정 테이블 타입(예: TAG 테이블)에서 스트리밍을 지원하지 않으면 준비된 문 반복 방식으로 자동 대체됩니다. 운영 시에는 데이터를 청크로 나누고 rowsFailed를 확인하는 패턴이 안전합니다.

오류 처리

오류는 기본 Error 객체(래퍼 사용 시 QueryError)로 전달됩니다. 문제를 진단하려면 error.message 또는 QueryErrorcode, sql 필드를 확인하세요. 통합 테스트는 존재하지 않는 테이블 조회와 지원하지 않는 UPDATE를 일부러 실행해 오류 메시지가 충분히 설명적인지 확인합니다.

테이블 타입별 SQL 유의사항

  • LOG/TAG 테이블SELECT, INSERT, DELETE를 지원하며 UPDATE는 사용할 수 없습니다.
  • VOLATILE/LOOKUP 테이블은 모든 DML을 지원하지만, 인덱스를 올바르게 사용하려면 WHERE 절에 기본 키 조건을 포함해야 합니다.

모범 사례

  1. 항상 연결을 닫기: try...finally 블록으로 conn.end()가 호출되도록 보장하세요.
  2. Prepared Statement 재사용: 한 번 생성한 후 여러 번 실행하면 성능이 향상됩니다.
  3. 배치 입력 활용: 단건 INSERT 대신 appendBatchappendOpen으로 대량 적재를 수행하세요.
  4. 오류 처리: DB 작업을 try...catch로 감싸고 적절히 로깅합니다.
  5. 커넥션 풀 사용: 운영 환경에서는 커넥션 풀을 도입해 동시 요청을 안정적으로 처리하세요.
  6. 쿼리 파라미터화: SQL 인젝션을 방지하려면 문자열 결합 대신 바인딩(? 플레이스홀더)을 사용하세요.

변경 이력

2025-10-08

  • npm, yarn, pnpm 및 오프라인 .tgz 설치 절차를 일반 사용자 관점으로 정리했습니다.
  • Node.js 전용 런타임 제약과 연결 문제 해결 팁을 보강했습니다.
  • 테이블 타입별 SQL 제약 사항을 동작 특성 섹션에 통합했습니다.

2025-10-03

  • TAG 스트리밍 예제를 추가하고 기본 MACHBASE_NATIVE_APPEND 동작을 문서화했습니다.
  • 네이티브 Append가 불가능할 때 Prepared Statement로 자동 폴백하는 동작을 정리했습니다.

2025-10-02

  • Machbase 페이사드(createConnection, QueryError, .promise(), 페이사드 준비문)를 도입했습니다.
  • 콜백/프로미스 흐름과 LOG/TAG UPDATE 거부 동작에 대한 통합 검증을 확장했습니다.

2025-09-30

  • 자리기반 Prepared Statement를 추가했습니다.
  • 로그 테이블용 appendBatch와 null 처리, 통계 반환을 추가했습니다.
  • 트랜잭션, 페이지네이션, 파라미터 바인딩, append 청크, 오류 처리 예제와 통합 검증을 보강했습니다.
최근 업데이트