@gobing-ai/ts-llm-jsonl-importer 0.4.15 → 0.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,5 @@
1
1
  import type { ImportOptions, ImportResult, SourceDefinition } from './types';
2
- /** Apply importer-owned schema to the target database. */
3
- export declare function applyHistoryImportSchema(db: ImportOptions['db']): Promise<void>;
4
- /**
5
- * Run the JSONL import pipeline for one source.
2
+ /** Run the JSONL import pipeline for one source.
6
3
  *
7
4
  * @param source - Either a built-in source identifier resolved from the
8
5
  * registry, or a fully-specified {@link SourceDefinition} for a custom
@@ -1 +1 @@
1
- {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAe,aAAa,EAAE,YAAY,EAAc,gBAAgB,EAAoB,MAAM,SAAS,CAAC;AAWxH,0DAA0D;AAC1D,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrF;AAiCD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA8GrH"}
1
+ {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAe,aAAa,EAAE,YAAY,EAAc,gBAAgB,EAAoB,MAAM,SAAS,CAAC;AAOxH;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA8GrH"}
package/dist/importer.js CHANGED
@@ -1,50 +1,9 @@
1
1
  import { ambientRuntimePaths, createNodeFileSystem, resolvePath, walkDir, } from '@gobing-ai/ts-runtime';
2
- import { HistoryImportError } from './errors.js';
3
2
  import { sha256 } from './hash.js';
3
+ import { applyHistoryImportSchema, ensureTargetTables, insertLedger, insertRecord, ledgerExists, readCheckpoint, resetCheckpoints, targetTableFor, writeCheckpoint, } from './jsonl-importer-dao.js';
4
4
  import { redactRecord } from './redaction.js';
5
- import { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql.js';
6
- import { resolveSourceDefinition, VALID_TABLE_NAME } from './sources.js';
7
- /** Apply importer-owned schema to the target database. */
8
- export async function applyHistoryImportSchema(db) {
9
- for (const statement of HISTORY_IMPORT_SCHEMA_SQL.split(';')) {
10
- const sql = statement.trim();
11
- if (sql.length > 0) {
12
- await db.exec(sql);
13
- }
14
- }
15
- }
16
- /**
17
- * Ensure the ETL table(s) for a source definition exist.
18
- *
19
- * WHY: the static {@link HISTORY_IMPORT_SCHEMA_SQL} only creates the built-in
20
- * `history_etl_*` tables. Custom source definitions (and built-in definitions
21
- * with a `splitConfig.targetTable` override) need their target table(s) created
22
- * on demand. The table name is already gated by {@link VALID_TABLE_NAME} in
23
- * {@link validateSourceDefinition} / {@link targetTableFor}, so it is safe to
24
- * interpolate into DDL. `CREATE TABLE IF NOT EXISTS` makes this idempotent for
25
- * built-in tables that the static schema already created.
26
- */
27
- async function ensureTargetTables(db, definition) {
28
- const tables = new Set([targetTableFor(definition.targetTable)]);
29
- if (definition.splitConfig.mode !== 'one-to-one' && definition.splitConfig.targetTable !== undefined) {
30
- tables.add(targetTableFor(definition.splitConfig.targetTable));
31
- }
32
- for (const table of tables) {
33
- await db.exec(ETL_TABLE_DDL(table));
34
- }
35
- }
36
- function ETL_TABLE_DDL(table) {
37
- return `CREATE TABLE IF NOT EXISTS ${table} (
38
- record_hash TEXT PRIMARY KEY,
39
- source_file TEXT NOT NULL,
40
- source_line INTEGER NOT NULL,
41
- split_index INTEGER NOT NULL,
42
- payload_json TEXT NOT NULL,
43
- imported_at TEXT NOT NULL
44
- );`;
45
- }
46
- /**
47
- * Run the JSONL import pipeline for one source.
5
+ import { resolveSourceDefinition } from './sources.js';
6
+ /** Run the JSONL import pipeline for one source.
48
7
  *
49
8
  * @param source - Either a built-in source identifier resolved from the
50
9
  * registry, or a fully-specified {@link SourceDefinition} for a custom
@@ -234,45 +193,6 @@ function matchesPattern(path, patterns) {
234
193
  return path.endsWith(pattern.replace(/^\*/, ''));
235
194
  });
236
195
  }
237
- async function readCheckpoint(db, source, sourceFile) {
238
- const row = await db.queryFirst('SELECT last_imported_line FROM history_import_checkpoint WHERE source = ? AND source_file = ?', source, sourceFile);
239
- return row?.last_imported_line ?? 0;
240
- }
241
- async function resetCheckpoints(db, source, files) {
242
- for (const file of files) {
243
- await db.run('DELETE FROM history_import_checkpoint WHERE source = ? AND source_file = ?', source, file);
244
- }
245
- }
246
- async function writeCheckpoint(db, source, sourceFile, line, now) {
247
- await db.run(`INSERT INTO history_import_checkpoint (source, source_file, last_imported_line, updated_at)
248
- VALUES (?, ?, ?, ?)
249
- ON CONFLICT(source, source_file) DO UPDATE SET
250
- last_imported_line = excluded.last_imported_line,
251
- updated_at = excluded.updated_at`, source, sourceFile, line, timestamp(now));
252
- }
253
- async function ledgerExists(db, recordHash) {
254
- const row = await db.queryFirst('SELECT record_hash FROM history_import_ledger WHERE record_hash = ?', recordHash);
255
- return row !== undefined && row !== null;
256
- }
257
- async function insertRecord(db, targetTable, recordHash, sourceFile, sourceLine, splitIndex, payload, now) {
258
- const table = targetTableFor(targetTable);
259
- await db.run(`INSERT INTO ${table} (record_hash, source_file, source_line, split_index, payload_json, imported_at)
260
- VALUES (?, ?, ?, ?, ?, ?)
261
- ON CONFLICT(record_hash) DO NOTHING`, recordHash, sourceFile, sourceLine, splitIndex, JSON.stringify(payload), timestamp(now));
262
- }
263
- async function insertLedger(db, recordHash, source, sourceFile, sourceLine, splitIndex, targetTable, now) {
264
- await db.run(`INSERT INTO history_import_ledger (record_hash, source, source_file, source_line, split_index, target_table, imported_at)
265
- VALUES (?, ?, ?, ?, ?, ?, ?)`, recordHash, source, sourceFile, sourceLine, splitIndex, targetTable, timestamp(now));
266
- }
267
- function targetTableFor(table) {
268
- if (!VALID_TABLE_NAME.test(table)) {
269
- throw new HistoryImportError(`Invalid history ETL target table: ${table}`, { table });
270
- }
271
- return table;
272
- }
273
- function timestamp(now) {
274
- return (now?.() ?? new Date()).toISOString();
275
- }
276
196
  /**
277
197
  * Yield lines from a file, streaming when the FileSystem supports it.
278
198
  *
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { HistoryImportError } from './errors';
2
2
  export { sha256, stableJson } from './hash';
3
- export { applyHistoryImportSchema, runJsonlImport } from './importer';
3
+ export { runJsonlImport } from './importer';
4
+ export { applyHistoryImportSchema } from './jsonl-importer-dao';
4
5
  export { DEFAULT_REDACTION_RULES, redactRecord, redactValue } from './redaction';
5
6
  export { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql';
6
7
  export { getSourceDefinition, resolveSourceDefinition, SOURCE_DEFINITIONS, VALID_TABLE_NAME, validateSourceDefinition, } from './sources';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,GAC3B,MAAM,WAAW,CAAC;AACnB,YAAY,EACR,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,GAC3B,MAAM,WAAW,CAAC;AACnB,YAAY,EACR,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { HistoryImportError } from './errors.js';
2
2
  export { sha256, stableJson } from './hash.js';
3
- export { applyHistoryImportSchema, runJsonlImport } from './importer.js';
3
+ export { runJsonlImport } from './importer.js';
4
+ export { applyHistoryImportSchema } from './jsonl-importer-dao.js';
4
5
  export { DEFAULT_REDACTION_RULES, redactRecord, redactValue } from './redaction.js';
5
6
  export { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql.js';
6
7
  export { getSourceDefinition, resolveSourceDefinition, SOURCE_DEFINITIONS, VALID_TABLE_NAME, validateSourceDefinition, } from './sources.js';
@@ -0,0 +1,30 @@
1
+ import type { ImportOptions, JsonObject, SourceDefinition } from './types';
2
+ interface CheckpointRow {
3
+ readonly last_imported_line: number;
4
+ }
5
+ declare function targetTableFor(table: string): string;
6
+ declare function timestamp(now: ImportOptions['now']): string;
7
+ declare function ETL_TABLE_DDL(table: string): string;
8
+ /** Apply importer-owned schema to the target database. */
9
+ export declare function applyHistoryImportSchema(db: ImportOptions['db']): Promise<void>;
10
+ /**
11
+ * Ensure the ETL table(s) for a source definition exist.
12
+ *
13
+ * WHY: the static {@link HISTORY_IMPORT_SCHEMA_SQL} only creates the built-in
14
+ * `history_etl_*` tables. Custom source definitions (and built-in definitions
15
+ * with a `splitConfig.targetTable` override) need their target table(s) created
16
+ * on demand. The table name is already gated by {@link VALID_TABLE_NAME} in
17
+ * {@link validateSourceDefinition} / {@link targetTableFor}, so it is safe to
18
+ * interpolate into DDL. `CREATE TABLE IF NOT EXISTS` makes this idempotent for
19
+ * built-in tables that the static schema already created.
20
+ */
21
+ declare function ensureTargetTables(db: ImportOptions['db'], definition: SourceDefinition): Promise<void>;
22
+ declare function readCheckpoint(db: ImportOptions['db'], source: string, sourceFile: string): Promise<number>;
23
+ declare function resetCheckpoints(db: ImportOptions['db'], source: string, files: readonly string[]): Promise<void>;
24
+ declare function writeCheckpoint(db: ImportOptions['db'], source: string, sourceFile: string, line: number, now: ImportOptions['now']): Promise<void>;
25
+ declare function ledgerExists(db: ImportOptions['db'], recordHash: string): Promise<boolean>;
26
+ declare function insertRecord(db: ImportOptions['db'], targetTable: string, recordHash: string, sourceFile: string, sourceLine: number, splitIndex: number, payload: JsonObject, now: ImportOptions['now']): Promise<void>;
27
+ declare function insertLedger(db: ImportOptions['db'], recordHash: string, source: string, sourceFile: string, sourceLine: number, splitIndex: number, targetTable: string, now: ImportOptions['now']): Promise<void>;
28
+ export type { CheckpointRow };
29
+ export { ETL_TABLE_DDL, ensureTargetTables, insertLedger, insertRecord, ledgerExists, readCheckpoint, resetCheckpoints, targetTableFor, timestamp, writeCheckpoint, };
30
+ //# sourceMappingURL=jsonl-importer-dao.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonl-importer-dao.d.ts","sourceRoot":"","sources":["../src/jsonl-importer-dao.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3E,UAAU,aAAa;IACnB,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACvC;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK7C;AAED,iBAAS,SAAS,CAAC,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,MAAM,CAEpD;AAED,iBAAS,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED,0DAA0D;AAC1D,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrF;AAED;;;;;;;;;;GAUG;AACH,iBAAe,kBAAkB,CAAC,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtG;AAED,iBAAe,cAAc,CAAC,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO1G;AAED,iBAAe,gBAAgB,CAAC,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhH;AAED,iBAAe,eAAe,CAC1B,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,EACvB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,iBAAe,YAAY,CAAC,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMzF;AAED,iBAAe,YAAY,CACvB,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,EACvB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,iBAAe,YAAY,CACvB,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,YAAY,EAAE,aAAa,EAAE,CAAC;AAC9B,OAAO,EACH,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,SAAS,EACT,eAAe,GAClB,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { HistoryImportError } from './errors.js';
2
+ import { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql.js';
3
+ import { VALID_TABLE_NAME } from './sources.js';
4
+ function targetTableFor(table) {
5
+ if (!VALID_TABLE_NAME.test(table)) {
6
+ throw new HistoryImportError(`Invalid history ETL target table: ${table}`, { table });
7
+ }
8
+ return table;
9
+ }
10
+ function timestamp(now) {
11
+ return (now?.() ?? new Date()).toISOString();
12
+ }
13
+ function ETL_TABLE_DDL(table) {
14
+ return `CREATE TABLE IF NOT EXISTS ${table} (
15
+ record_hash TEXT PRIMARY KEY,
16
+ source_file TEXT NOT NULL,
17
+ source_line INTEGER NOT NULL,
18
+ split_index INTEGER NOT NULL,
19
+ payload_json TEXT NOT NULL,
20
+ imported_at TEXT NOT NULL
21
+ );`;
22
+ }
23
+ /** Apply importer-owned schema to the target database. */
24
+ export async function applyHistoryImportSchema(db) {
25
+ for (const statement of HISTORY_IMPORT_SCHEMA_SQL.split(';')) {
26
+ const sql = statement.trim();
27
+ if (sql.length > 0) {
28
+ await db.exec(sql);
29
+ }
30
+ }
31
+ }
32
+ /**
33
+ * Ensure the ETL table(s) for a source definition exist.
34
+ *
35
+ * WHY: the static {@link HISTORY_IMPORT_SCHEMA_SQL} only creates the built-in
36
+ * `history_etl_*` tables. Custom source definitions (and built-in definitions
37
+ * with a `splitConfig.targetTable` override) need their target table(s) created
38
+ * on demand. The table name is already gated by {@link VALID_TABLE_NAME} in
39
+ * {@link validateSourceDefinition} / {@link targetTableFor}, so it is safe to
40
+ * interpolate into DDL. `CREATE TABLE IF NOT EXISTS` makes this idempotent for
41
+ * built-in tables that the static schema already created.
42
+ */
43
+ async function ensureTargetTables(db, definition) {
44
+ const tables = new Set([targetTableFor(definition.targetTable)]);
45
+ if (definition.splitConfig.mode !== 'one-to-one' && definition.splitConfig.targetTable !== undefined) {
46
+ tables.add(targetTableFor(definition.splitConfig.targetTable));
47
+ }
48
+ for (const table of tables) {
49
+ await db.exec(ETL_TABLE_DDL(table));
50
+ }
51
+ }
52
+ async function readCheckpoint(db, source, sourceFile) {
53
+ const row = await db.queryFirst('SELECT last_imported_line FROM history_import_checkpoint WHERE source = ? AND source_file = ?', source, sourceFile);
54
+ return row?.last_imported_line ?? 0;
55
+ }
56
+ async function resetCheckpoints(db, source, files) {
57
+ for (const file of files) {
58
+ await db.run('DELETE FROM history_import_checkpoint WHERE source = ? AND source_file = ?', source, file);
59
+ }
60
+ }
61
+ async function writeCheckpoint(db, source, sourceFile, line, now) {
62
+ await db.run(`INSERT INTO history_import_checkpoint (source, source_file, last_imported_line, updated_at)
63
+ VALUES (?, ?, ?, ?)
64
+ ON CONFLICT(source, source_file) DO UPDATE SET
65
+ last_imported_line = excluded.last_imported_line,
66
+ updated_at = excluded.updated_at`, source, sourceFile, line, timestamp(now));
67
+ }
68
+ async function ledgerExists(db, recordHash) {
69
+ const row = await db.queryFirst('SELECT record_hash FROM history_import_ledger WHERE record_hash = ?', recordHash);
70
+ return row !== undefined && row !== null;
71
+ }
72
+ async function insertRecord(db, targetTable, recordHash, sourceFile, sourceLine, splitIndex, payload, now) {
73
+ const table = targetTableFor(targetTable);
74
+ await db.run(`INSERT INTO ${table} (record_hash, source_file, source_line, split_index, payload_json, imported_at)
75
+ VALUES (?, ?, ?, ?, ?, ?)
76
+ ON CONFLICT(record_hash) DO NOTHING`, recordHash, sourceFile, sourceLine, splitIndex, JSON.stringify(payload), timestamp(now));
77
+ }
78
+ async function insertLedger(db, recordHash, source, sourceFile, sourceLine, splitIndex, targetTable, now) {
79
+ await db.run(`INSERT INTO history_import_ledger (record_hash, source, source_file, source_line, split_index, target_table, imported_at)
80
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, recordHash, source, sourceFile, sourceLine, splitIndex, targetTable, timestamp(now));
81
+ }
82
+ export { ETL_TABLE_DDL, ensureTargetTables, insertLedger, insertRecord, ledgerExists, readCheckpoint, resetCheckpoints, targetTableFor, timestamp, writeCheckpoint, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-llm-jsonl-importer",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
4
4
  "description": "@gobing-ai/ts-llm-jsonl-importer — Generic JSONL importer for LLM agent history files.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -47,9 +47,9 @@
47
47
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-llm-jsonl-importer-v<version> && git push --tags' && exit 1"
48
48
  },
49
49
  "dependencies": {
50
- "@gobing-ai/ts-db": "^0.4.15",
51
- "@gobing-ai/ts-runtime": "^0.4.15",
52
- "@gobing-ai/ts-utils": "^0.4.15",
50
+ "@gobing-ai/ts-db": "^0.4.17",
51
+ "@gobing-ai/ts-runtime": "^0.4.17",
52
+ "@gobing-ai/ts-utils": "^0.4.17",
53
53
  "zod": "^4.1.0"
54
54
  },
55
55
  "devDependencies": {
package/src/importer.ts CHANGED
@@ -6,11 +6,20 @@ import {
6
6
  resolvePath,
7
7
  walkDir,
8
8
  } from '@gobing-ai/ts-runtime';
9
- import { HistoryImportError } from './errors';
10
9
  import { sha256 } from './hash';
10
+ import {
11
+ applyHistoryImportSchema,
12
+ ensureTargetTables,
13
+ insertLedger,
14
+ insertRecord,
15
+ ledgerExists,
16
+ readCheckpoint,
17
+ resetCheckpoints,
18
+ targetTableFor,
19
+ writeCheckpoint,
20
+ } from './jsonl-importer-dao';
11
21
  import { redactRecord } from './redaction';
12
- import { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql';
13
- import { resolveSourceDefinition, VALID_TABLE_NAME } from './sources';
22
+ import { resolveSourceDefinition } from './sources';
14
23
  import type { ImportIssue, ImportOptions, ImportResult, JsonObject, SourceDefinition, TransformContext } from './types';
15
24
 
16
25
  interface SplitRecord {
@@ -18,53 +27,7 @@ interface SplitRecord {
18
27
  readonly raw: JsonObject;
19
28
  }
20
29
 
21
- interface CheckpointRow {
22
- readonly last_imported_line: number;
23
- }
24
-
25
- /** Apply importer-owned schema to the target database. */
26
- export async function applyHistoryImportSchema(db: ImportOptions['db']): Promise<void> {
27
- for (const statement of HISTORY_IMPORT_SCHEMA_SQL.split(';')) {
28
- const sql = statement.trim();
29
- if (sql.length > 0) {
30
- await db.exec(sql);
31
- }
32
- }
33
- }
34
-
35
- /**
36
- * Ensure the ETL table(s) for a source definition exist.
37
- *
38
- * WHY: the static {@link HISTORY_IMPORT_SCHEMA_SQL} only creates the built-in
39
- * `history_etl_*` tables. Custom source definitions (and built-in definitions
40
- * with a `splitConfig.targetTable` override) need their target table(s) created
41
- * on demand. The table name is already gated by {@link VALID_TABLE_NAME} in
42
- * {@link validateSourceDefinition} / {@link targetTableFor}, so it is safe to
43
- * interpolate into DDL. `CREATE TABLE IF NOT EXISTS` makes this idempotent for
44
- * built-in tables that the static schema already created.
45
- */
46
- async function ensureTargetTables(db: ImportOptions['db'], definition: SourceDefinition): Promise<void> {
47
- const tables = new Set<string>([targetTableFor(definition.targetTable)]);
48
- if (definition.splitConfig.mode !== 'one-to-one' && definition.splitConfig.targetTable !== undefined) {
49
- tables.add(targetTableFor(definition.splitConfig.targetTable));
50
- }
51
- for (const table of tables) {
52
- await db.exec(ETL_TABLE_DDL(table));
53
- }
54
- }
55
-
56
- function ETL_TABLE_DDL(table: string): string {
57
- return `CREATE TABLE IF NOT EXISTS ${table} (
58
- record_hash TEXT PRIMARY KEY,
59
- source_file TEXT NOT NULL,
60
- source_line INTEGER NOT NULL,
61
- split_index INTEGER NOT NULL,
62
- payload_json TEXT NOT NULL,
63
- imported_at TEXT NOT NULL
64
- );`;
65
- }
66
- /**
67
- * Run the JSONL import pipeline for one source.
30
+ /** Run the JSONL import pipeline for one source.
68
31
  *
69
32
  * @param source - Either a built-in source identifier resolved from the
70
33
  * registry, or a fully-specified {@link SourceDefinition} for a custom
@@ -290,107 +253,6 @@ function matchesPattern(path: string, patterns: readonly string[]): boolean {
290
253
  });
291
254
  }
292
255
 
293
- async function readCheckpoint(db: ImportOptions['db'], source: string, sourceFile: string): Promise<number> {
294
- const row = await db.queryFirst<CheckpointRow>(
295
- 'SELECT last_imported_line FROM history_import_checkpoint WHERE source = ? AND source_file = ?',
296
- source,
297
- sourceFile,
298
- );
299
- return row?.last_imported_line ?? 0;
300
- }
301
-
302
- async function resetCheckpoints(db: ImportOptions['db'], source: string, files: readonly string[]): Promise<void> {
303
- for (const file of files) {
304
- await db.run('DELETE FROM history_import_checkpoint WHERE source = ? AND source_file = ?', source, file);
305
- }
306
- }
307
-
308
- async function writeCheckpoint(
309
- db: ImportOptions['db'],
310
- source: string,
311
- sourceFile: string,
312
- line: number,
313
- now: ImportOptions['now'],
314
- ): Promise<void> {
315
- await db.run(
316
- `INSERT INTO history_import_checkpoint (source, source_file, last_imported_line, updated_at)
317
- VALUES (?, ?, ?, ?)
318
- ON CONFLICT(source, source_file) DO UPDATE SET
319
- last_imported_line = excluded.last_imported_line,
320
- updated_at = excluded.updated_at`,
321
- source,
322
- sourceFile,
323
- line,
324
- timestamp(now),
325
- );
326
- }
327
-
328
- async function ledgerExists(db: ImportOptions['db'], recordHash: string): Promise<boolean> {
329
- const row = await db.queryFirst<{ record_hash: string }>(
330
- 'SELECT record_hash FROM history_import_ledger WHERE record_hash = ?',
331
- recordHash,
332
- );
333
- return row !== undefined && row !== null;
334
- }
335
-
336
- async function insertRecord(
337
- db: ImportOptions['db'],
338
- targetTable: string,
339
- recordHash: string,
340
- sourceFile: string,
341
- sourceLine: number,
342
- splitIndex: number,
343
- payload: JsonObject,
344
- now: ImportOptions['now'],
345
- ): Promise<void> {
346
- const table = targetTableFor(targetTable);
347
- await db.run(
348
- `INSERT INTO ${table} (record_hash, source_file, source_line, split_index, payload_json, imported_at)
349
- VALUES (?, ?, ?, ?, ?, ?)
350
- ON CONFLICT(record_hash) DO NOTHING`,
351
- recordHash,
352
- sourceFile,
353
- sourceLine,
354
- splitIndex,
355
- JSON.stringify(payload),
356
- timestamp(now),
357
- );
358
- }
359
-
360
- async function insertLedger(
361
- db: ImportOptions['db'],
362
- recordHash: string,
363
- source: string,
364
- sourceFile: string,
365
- sourceLine: number,
366
- splitIndex: number,
367
- targetTable: string,
368
- now: ImportOptions['now'],
369
- ): Promise<void> {
370
- await db.run(
371
- `INSERT INTO history_import_ledger (record_hash, source, source_file, source_line, split_index, target_table, imported_at)
372
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
373
- recordHash,
374
- source,
375
- sourceFile,
376
- sourceLine,
377
- splitIndex,
378
- targetTable,
379
- timestamp(now),
380
- );
381
- }
382
-
383
- function targetTableFor(table: string): string {
384
- if (!VALID_TABLE_NAME.test(table)) {
385
- throw new HistoryImportError(`Invalid history ETL target table: ${table}`, { table });
386
- }
387
- return table;
388
- }
389
-
390
- function timestamp(now: ImportOptions['now']): string {
391
- return (now?.() ?? new Date()).toISOString();
392
- }
393
-
394
256
  /**
395
257
  * Yield lines from a file, streaming when the FileSystem supports it.
396
258
  *
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { HistoryImportError } from './errors';
2
2
  export { sha256, stableJson } from './hash';
3
- export { applyHistoryImportSchema, runJsonlImport } from './importer';
3
+ export { runJsonlImport } from './importer';
4
+ export { applyHistoryImportSchema } from './jsonl-importer-dao';
4
5
  export { DEFAULT_REDACTION_RULES, redactRecord, redactValue } from './redaction';
5
6
  export { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql';
6
7
  export {
@@ -0,0 +1,165 @@
1
+ import { HistoryImportError } from './errors';
2
+ import { HISTORY_IMPORT_SCHEMA_SQL } from './schema-sql';
3
+ import { VALID_TABLE_NAME } from './sources';
4
+ import type { ImportOptions, JsonObject, SourceDefinition } from './types';
5
+
6
+ interface CheckpointRow {
7
+ readonly last_imported_line: number;
8
+ }
9
+
10
+ function targetTableFor(table: string): string {
11
+ if (!VALID_TABLE_NAME.test(table)) {
12
+ throw new HistoryImportError(`Invalid history ETL target table: ${table}`, { table });
13
+ }
14
+ return table;
15
+ }
16
+
17
+ function timestamp(now: ImportOptions['now']): string {
18
+ return (now?.() ?? new Date()).toISOString();
19
+ }
20
+
21
+ function ETL_TABLE_DDL(table: string): string {
22
+ return `CREATE TABLE IF NOT EXISTS ${table} (
23
+ record_hash TEXT PRIMARY KEY,
24
+ source_file TEXT NOT NULL,
25
+ source_line INTEGER NOT NULL,
26
+ split_index INTEGER NOT NULL,
27
+ payload_json TEXT NOT NULL,
28
+ imported_at TEXT NOT NULL
29
+ );`;
30
+ }
31
+
32
+ /** Apply importer-owned schema to the target database. */
33
+ export async function applyHistoryImportSchema(db: ImportOptions['db']): Promise<void> {
34
+ for (const statement of HISTORY_IMPORT_SCHEMA_SQL.split(';')) {
35
+ const sql = statement.trim();
36
+ if (sql.length > 0) {
37
+ await db.exec(sql);
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Ensure the ETL table(s) for a source definition exist.
44
+ *
45
+ * WHY: the static {@link HISTORY_IMPORT_SCHEMA_SQL} only creates the built-in
46
+ * `history_etl_*` tables. Custom source definitions (and built-in definitions
47
+ * with a `splitConfig.targetTable` override) need their target table(s) created
48
+ * on demand. The table name is already gated by {@link VALID_TABLE_NAME} in
49
+ * {@link validateSourceDefinition} / {@link targetTableFor}, so it is safe to
50
+ * interpolate into DDL. `CREATE TABLE IF NOT EXISTS` makes this idempotent for
51
+ * built-in tables that the static schema already created.
52
+ */
53
+ async function ensureTargetTables(db: ImportOptions['db'], definition: SourceDefinition): Promise<void> {
54
+ const tables = new Set<string>([targetTableFor(definition.targetTable)]);
55
+ if (definition.splitConfig.mode !== 'one-to-one' && definition.splitConfig.targetTable !== undefined) {
56
+ tables.add(targetTableFor(definition.splitConfig.targetTable));
57
+ }
58
+ for (const table of tables) {
59
+ await db.exec(ETL_TABLE_DDL(table));
60
+ }
61
+ }
62
+
63
+ async function readCheckpoint(db: ImportOptions['db'], source: string, sourceFile: string): Promise<number> {
64
+ const row = await db.queryFirst<CheckpointRow>(
65
+ 'SELECT last_imported_line FROM history_import_checkpoint WHERE source = ? AND source_file = ?',
66
+ source,
67
+ sourceFile,
68
+ );
69
+ return row?.last_imported_line ?? 0;
70
+ }
71
+
72
+ async function resetCheckpoints(db: ImportOptions['db'], source: string, files: readonly string[]): Promise<void> {
73
+ for (const file of files) {
74
+ await db.run('DELETE FROM history_import_checkpoint WHERE source = ? AND source_file = ?', source, file);
75
+ }
76
+ }
77
+
78
+ async function writeCheckpoint(
79
+ db: ImportOptions['db'],
80
+ source: string,
81
+ sourceFile: string,
82
+ line: number,
83
+ now: ImportOptions['now'],
84
+ ): Promise<void> {
85
+ await db.run(
86
+ `INSERT INTO history_import_checkpoint (source, source_file, last_imported_line, updated_at)
87
+ VALUES (?, ?, ?, ?)
88
+ ON CONFLICT(source, source_file) DO UPDATE SET
89
+ last_imported_line = excluded.last_imported_line,
90
+ updated_at = excluded.updated_at`,
91
+ source,
92
+ sourceFile,
93
+ line,
94
+ timestamp(now),
95
+ );
96
+ }
97
+
98
+ async function ledgerExists(db: ImportOptions['db'], recordHash: string): Promise<boolean> {
99
+ const row = await db.queryFirst<{ record_hash: string }>(
100
+ 'SELECT record_hash FROM history_import_ledger WHERE record_hash = ?',
101
+ recordHash,
102
+ );
103
+ return row !== undefined && row !== null;
104
+ }
105
+
106
+ async function insertRecord(
107
+ db: ImportOptions['db'],
108
+ targetTable: string,
109
+ recordHash: string,
110
+ sourceFile: string,
111
+ sourceLine: number,
112
+ splitIndex: number,
113
+ payload: JsonObject,
114
+ now: ImportOptions['now'],
115
+ ): Promise<void> {
116
+ const table = targetTableFor(targetTable);
117
+ await db.run(
118
+ `INSERT INTO ${table} (record_hash, source_file, source_line, split_index, payload_json, imported_at)
119
+ VALUES (?, ?, ?, ?, ?, ?)
120
+ ON CONFLICT(record_hash) DO NOTHING`,
121
+ recordHash,
122
+ sourceFile,
123
+ sourceLine,
124
+ splitIndex,
125
+ JSON.stringify(payload),
126
+ timestamp(now),
127
+ );
128
+ }
129
+
130
+ async function insertLedger(
131
+ db: ImportOptions['db'],
132
+ recordHash: string,
133
+ source: string,
134
+ sourceFile: string,
135
+ sourceLine: number,
136
+ splitIndex: number,
137
+ targetTable: string,
138
+ now: ImportOptions['now'],
139
+ ): Promise<void> {
140
+ await db.run(
141
+ `INSERT INTO history_import_ledger (record_hash, source, source_file, source_line, split_index, target_table, imported_at)
142
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
143
+ recordHash,
144
+ source,
145
+ sourceFile,
146
+ sourceLine,
147
+ splitIndex,
148
+ targetTable,
149
+ timestamp(now),
150
+ );
151
+ }
152
+
153
+ export type { CheckpointRow };
154
+ export {
155
+ ETL_TABLE_DDL,
156
+ ensureTargetTables,
157
+ insertLedger,
158
+ insertRecord,
159
+ ledgerExists,
160
+ readCheckpoint,
161
+ resetCheckpoints,
162
+ targetTableFor,
163
+ timestamp,
164
+ writeCheckpoint,
165
+ };