@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.
- package/dist/importer.d.ts +1 -4
- package/dist/importer.d.ts.map +1 -1
- package/dist/importer.js +3 -83
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/jsonl-importer-dao.d.ts +30 -0
- package/dist/jsonl-importer-dao.d.ts.map +1 -0
- package/dist/jsonl-importer-dao.js +82 -0
- package/package.json +4 -4
- package/src/importer.ts +13 -151
- package/src/index.ts +2 -1
- package/src/jsonl-importer-dao.ts +165 -0
package/dist/importer.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { ImportOptions, ImportResult, SourceDefinition } from './types';
|
|
2
|
-
/**
|
|
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
|
package/dist/importer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"
|
|
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 {
|
|
6
|
-
|
|
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 {
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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 {
|
|
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.
|
|
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.
|
|
51
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
52
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
+
};
|