@titan-design/store-sqlite 0.1.0 → 0.2.0

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/index.d.ts CHANGED
@@ -229,11 +229,36 @@ interface SpanHit extends SpanInput {
229
229
  /** FTS5 bm25 rank; lower is better. */
230
230
  rank: number;
231
231
  }
232
+ /**
233
+ * Narrow a search to one class of owner, so one class cannot flood a result set.
234
+ *
235
+ * A span table that holds several entity classes holds them in wildly different
236
+ * volumes. Measured on active-work's graph: 92,713 transcript spans against
237
+ * 1,569 note spans, 59 to 1. Ranking one pooled query returns transcript
238
+ * chatter and the notes never surface, so the fix is a separate ranked list per
239
+ * class, which needs a way to ask for one class.
240
+ *
241
+ * `ownerPrefix` and `fields` are both needed, not either. active-work keys both
242
+ * a mined transcript and a workspace session record under `session:`, and they
243
+ * are separable only by field — `body` is the record, `prompt` and the
244
+ * `tool_*` fields are the transcript. A prefix-only filter would put 92,713
245
+ * transcript spans into the records' list and look like it was working.
246
+ */
247
+ interface SpanScope {
248
+ /** Match owners whose ref starts with this, e.g. `note:`. A range scan, so the owner index applies. */
249
+ ownerPrefix?: string;
250
+ /** Match only these fields, e.g. `["title", "body"]`. Empty means no field matches. */
251
+ fields?: string[];
252
+ }
232
253
  declare class SpanFtsTables {
254
+ private readonly db;
255
+ private readonly tableNames;
233
256
  private readonly insertSpan;
234
257
  private readonly findSpan;
235
258
  private readonly insertFts;
236
259
  private readonly searchStmt;
260
+ /** One prepared statement per scope shape, since the predicate list varies. */
261
+ private readonly scopedSearchStmts;
237
262
  private readonly deleteAllFts;
238
263
  private readonly deleteSpansStmt;
239
264
  private readonly countSpans;
@@ -241,8 +266,23 @@ declare class SpanFtsTables {
241
266
  constructor(db: Db, { name }?: SpanFtsOptions);
242
267
  /** Index `text` for a span, storing only the locator. Re-indexing the same span is a no-op. Returns the span id. */
243
268
  index(span: SpanInput, text: string): number;
244
- /** FTS5 MATCH, joined through the span table so stranded FTS rows never surface. */
245
- search(query: string, limit?: number): SpanHit[];
269
+ /**
270
+ * FTS5 MATCH, joined through the span table so stranded FTS rows never surface.
271
+ *
272
+ * `scope` narrows to one class of owner; `limit` is applied after it, so a
273
+ * scoped search returns its own top-N rather than whatever survives the
274
+ * filter out of a global top-N.
275
+ */
276
+ search(query: string, limit?: number, scope?: SpanScope): SpanHit[];
277
+ /**
278
+ * Prepared statement for one scope shape, cached.
279
+ *
280
+ * Cached by shape rather than by values, because a product registers a fixed
281
+ * handful of class retrievers and then queries them for the life of the
282
+ * process. Keyed on the field *count* and whether a prefix is present, which
283
+ * is everything that changes the SQL.
284
+ */
285
+ private scopedStatement;
246
286
  /** Drop an owner's spans. Their FTS rows become orphans until `rebuild`; the join hides them. */
247
287
  purgeOwner(ownerRef: string): number;
248
288
  /** Fraction of FTS rows with no span behind them; a high value means it is time to rebuild. */
@@ -300,4 +340,4 @@ declare class WatermarkTable {
300
340
  list(): WatermarkRow[];
301
341
  }
302
342
 
303
- export { type CacheBlobOptions, CacheBlobTable, type CacheHit, type CacheKey, type Db, type EdgeInput, type EdgeRow, EdgeTable, type EdgeTableOptions, type EntityInput, type EntityRow, EntityTable, type KitSelection, type Migration, type OpenOptions, type ParsedRef, SQL_NOW, type SpanFtsOptions, SpanFtsTables, type SpanHit, type SpanInput, type TableName, type WatermarkAdvance, type WatermarkOptions, type WatermarkRow, type WatermarkStatus, WatermarkTable, appliedVersions, cacheBlobTableDdl, contentHashOf, edgeTableDdl, entitySnapTableDdl, entityTableDdl, hasColumn, hasTable, isRef, kitDdl, kitMigration, nowIso, openDatabase, parseRef, quoteIdent, ref, refKind, runMigrations, snapshotTableDdl, spanFtsTablesDdl, watermarkTableDdl };
343
+ export { type CacheBlobOptions, CacheBlobTable, type CacheHit, type CacheKey, type Db, type EdgeInput, type EdgeRow, EdgeTable, type EdgeTableOptions, type EntityInput, type EntityRow, EntityTable, type KitSelection, type Migration, type OpenOptions, type ParsedRef, SQL_NOW, type SpanFtsOptions, SpanFtsTables, type SpanHit, type SpanInput, type SpanScope, type TableName, type WatermarkAdvance, type WatermarkOptions, type WatermarkRow, type WatermarkStatus, WatermarkTable, appliedVersions, cacheBlobTableDdl, contentHashOf, edgeTableDdl, entitySnapTableDdl, entityTableDdl, hasColumn, hasTable, isRef, kitDdl, kitMigration, nowIso, openDatabase, parseRef, quoteIdent, ref, refKind, runMigrations, snapshotTableDdl, spanFtsTablesDdl, watermarkTableDdl };
package/dist/index.js CHANGED
@@ -368,17 +368,36 @@ function spanFtsTablesDdl({ name = "search" } = {}) {
368
368
  CREATE VIRTUAL TABLE IF NOT EXISTS ${fts} USING fts5(text, content='');
369
369
  `;
370
370
  }
371
+ function prefixUpperBound(prefix) {
372
+ const last = prefix.charCodeAt(prefix.length - 1);
373
+ return prefix.slice(0, -1) + String.fromCharCode(last + 1);
374
+ }
375
+ function hasPrefix(scope) {
376
+ return scope.ownerPrefix !== void 0 && scope.ownerPrefix.length > 0;
377
+ }
378
+ function scopeValues(scope) {
379
+ const values = [];
380
+ if (hasPrefix(scope)) values.push(scope.ownerPrefix, prefixUpperBound(scope.ownerPrefix));
381
+ if (scope.fields !== void 0) values.push(...scope.fields);
382
+ return values;
383
+ }
371
384
  var SpanFtsTables = class {
385
+ db;
386
+ tableNames;
372
387
  insertSpan;
373
388
  findSpan;
374
389
  insertFts;
375
390
  searchStmt;
391
+ /** One prepared statement per scope shape, since the predicate list varies. */
392
+ scopedSearchStmts = /* @__PURE__ */ new Map();
376
393
  deleteAllFts;
377
394
  deleteSpansStmt;
378
395
  countSpans;
379
396
  countFts;
380
397
  constructor(db, { name = "search" } = {}) {
381
398
  const { span, fts } = names(name);
399
+ this.db = db;
400
+ this.tableNames = { span, fts };
382
401
  this.insertSpan = db.prepare(
383
402
  `INSERT INTO ${span} (owner_ref, field, source_id, byte_offset, byte_length) VALUES (?, ?, ?, ?, ?)
384
403
  ON CONFLICT (owner_ref, field, source_id, byte_offset) DO NOTHING`
@@ -404,9 +423,16 @@ var SpanFtsTables = class {
404
423
  if (inserted.changes === 1) this.insertFts.run(spanId, text);
405
424
  return spanId;
406
425
  }
407
- /** FTS5 MATCH, joined through the span table so stranded FTS rows never surface. */
408
- search(query, limit = 50) {
409
- return this.searchStmt.all(query, limit).map((r) => ({
426
+ /**
427
+ * FTS5 MATCH, joined through the span table so stranded FTS rows never surface.
428
+ *
429
+ * `scope` narrows to one class of owner; `limit` is applied after it, so a
430
+ * scoped search returns its own top-N rather than whatever survives the
431
+ * filter out of a global top-N.
432
+ */
433
+ search(query, limit = 50, scope) {
434
+ const rows = scope ? this.scopedStatement(scope).all([query, ...scopeValues(scope), limit]) : this.searchStmt.all(query, limit);
435
+ return rows.map((r) => ({
410
436
  spanId: r.span_id,
411
437
  ownerRef: r.owner_ref,
412
438
  field: r.field,
@@ -416,6 +442,33 @@ var SpanFtsTables = class {
416
442
  rank: r.rank
417
443
  }));
418
444
  }
445
+ /**
446
+ * Prepared statement for one scope shape, cached.
447
+ *
448
+ * Cached by shape rather than by values, because a product registers a fixed
449
+ * handful of class retrievers and then queries them for the life of the
450
+ * process. Keyed on the field *count* and whether a prefix is present, which
451
+ * is everything that changes the SQL.
452
+ */
453
+ scopedStatement(scope) {
454
+ const fieldCount = scope.fields?.length ?? 0;
455
+ const key = `${hasPrefix(scope) ? 1 : 0}:${scope.fields === void 0 ? "all" : fieldCount}`;
456
+ const cached = this.scopedSearchStmts.get(key);
457
+ if (cached) return cached;
458
+ const { span, fts } = this.tableNames;
459
+ const predicates = [`${fts} MATCH ?`];
460
+ if (hasPrefix(scope)) predicates.push("s.owner_ref >= ? AND s.owner_ref < ?");
461
+ if (scope.fields !== void 0) {
462
+ predicates.push(fieldCount === 0 ? "0" : `s.field IN (${new Array(fieldCount).fill("?").join(", ")})`);
463
+ }
464
+ const statement = this.db.prepare(
465
+ `SELECT s.span_id, s.owner_ref, s.field, s.source_id, s.byte_offset, s.byte_length, f.rank
466
+ FROM ${fts} f JOIN ${span} s ON s.span_id = f.rowid
467
+ WHERE ${predicates.join(" AND ")} ORDER BY f.rank LIMIT ?`
468
+ );
469
+ this.scopedSearchStmts.set(key, statement);
470
+ return statement;
471
+ }
419
472
  /** Drop an owner's spans. Their FTS rows become orphans until `rebuild`; the join hides them. */
420
473
  purgeOwner(ownerRef) {
421
474
  return this.deleteSpansStmt.run(ownerRef).changes;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/open.ts","../src/migrations.ts","../src/ref.ts","../src/tables/cache-blob.ts","../src/tables/edge.ts","../src/tables/entity.ts","../src/tables/span-fts.ts","../src/tables/watermark.ts","../src/kit.ts"],"sourcesContent":["import Database from \"better-sqlite3\";\nimport { mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport type Db = Database.Database;\n\nexport interface OpenOptions {\n readonly?: boolean;\n /** WAL keeps readers from blocking a writer. On by default for file databases. */\n wal?: boolean;\n /** SQLite leaves foreign keys off per connection; the kit turns them on. */\n foreignKeys?: boolean;\n}\n\n/** Open (creating if absent) a database with the kit's pragmas. `:memory:` works for tests. */\nexport function openDatabase(dbPath: string, options: OpenOptions = {}): Db {\n const inMemory = dbPath === \":memory:\";\n if (!inMemory && !options.readonly) mkdirSync(path.dirname(dbPath), { recursive: true });\n const db = new Database(dbPath, { readonly: options.readonly ?? false });\n if (!inMemory && !options.readonly && (options.wal ?? true)) db.pragma(\"journal_mode = WAL\");\n if (options.foreignKeys ?? true) db.pragma(\"foreign_keys = ON\");\n return db;\n}\n\n/** ISO-8601 with milliseconds, the timestamp shape every kit table stores. */\nexport function nowIso(): string {\n return new Date().toISOString();\n}\n\n/** SQL expression producing the same shape as `nowIso()` inside the database. */\nexport const SQL_NOW = \"strftime('%Y-%m-%dT%H:%M:%fZ','now')\";\n\nexport function hasTable(db: Db, table: string): boolean {\n const row = db.prepare(\"SELECT 1 FROM sqlite_master WHERE type IN ('table','view') AND name = ?\").get(table);\n return row !== undefined;\n}\n\nexport function hasColumn(db: Db, table: string, column: string): boolean {\n const cols = db.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all() as { name: string }[];\n return cols.some((c) => c.name === column);\n}\n\n/** Double-quote an identifier so table factories can take caller-chosen names safely. */\nexport function quoteIdent(name: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`invalid SQL identifier: ${name}`);\n return `\"${name}\"`;\n}\n","import type { Db } from \"./open.js\";\nimport { nowIso } from \"./open.js\";\n\nexport interface Migration {\n /** Positive, unique, applied in ascending order. */\n version: number;\n name?: string;\n up: (db: Db) => void;\n}\n\nconst MIGRATION_TABLE = `\n CREATE TABLE IF NOT EXISTS _migration (\n version INTEGER PRIMARY KEY,\n name TEXT,\n applied_at TEXT NOT NULL\n )`;\n\nexport function appliedVersions(db: Db): number[] {\n db.exec(MIGRATION_TABLE);\n const rows = db.prepare(\"SELECT version FROM _migration ORDER BY version\").all() as { version: number }[];\n return rows.map((r) => r.version);\n}\n\n/**\n * Apply every migration not yet recorded in `_migration`, each in its own\n * transaction so a failure leaves the database at the last good version.\n * Returns the versions applied this call.\n */\nexport function runMigrations(db: Db, migrations: readonly Migration[]): number[] {\n assertWellFormed(migrations);\n const applied = new Set(appliedVersions(db));\n const record = db.prepare(\"INSERT INTO _migration (version, name, applied_at) VALUES (?, ?, ?)\");\n const applyOne = db.transaction((m: Migration) => {\n m.up(db);\n record.run(m.version, m.name ?? null, nowIso());\n });\n const done: number[] = [];\n for (const m of [...migrations].sort((a, b) => a.version - b.version)) {\n if (applied.has(m.version)) continue;\n applyOne(m);\n done.push(m.version);\n }\n return done;\n}\n\nfunction assertWellFormed(migrations: readonly Migration[]): void {\n const seen = new Set<number>();\n for (const m of migrations) {\n if (!Number.isInteger(m.version) || m.version <= 0) throw new Error(`migration version must be a positive integer: ${m.version}`);\n if (seen.has(m.version)) throw new Error(`duplicate migration version: ${m.version}`);\n seen.add(m.version);\n }\n}\n","/**\n * The cross-domain identity grammar: `<kind>:<id>`, where `kind` is a short\n * lowercase token and `id` is anything the domain needs (paths, `repo#node`,\n * numbers). Every domain mints refs for its own entities and the shared edge\n * table links them, so `session:abc` can point at `codewatch:repo#src/a.ts::Foo`\n * without either side knowing the other's schema.\n */\nexport interface ParsedRef {\n kind: string;\n id: string;\n}\n\nconst KIND = /^[a-z][a-z0-9_-]*$/;\n\nexport function ref(kind: string, id: string): string {\n if (!KIND.test(kind)) throw new Error(`invalid ref kind: ${JSON.stringify(kind)}`);\n if (id.length === 0) throw new Error(\"ref id must not be empty\");\n return `${kind}:${id}`;\n}\n\nexport function parseRef(value: string): ParsedRef {\n const at = value.indexOf(\":\");\n if (at <= 0 || at === value.length - 1 || !KIND.test(value.slice(0, at))) {\n throw new Error(`invalid ref: ${JSON.stringify(value)}`);\n }\n return { kind: value.slice(0, at), id: value.slice(at + 1) };\n}\n\nexport function isRef(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n try {\n parseRef(value);\n return true;\n } catch {\n return false;\n }\n}\n\n/** A typed minter for one kind: `const session = refKind(\"session\"); session(\"abc\")`. */\nexport function refKind(kind: string): (id: string) => string {\n ref(kind, \"probe\");\n return (id) => ref(kind, id);\n}\n","import { createHash } from \"node:crypto\";\nimport { SQL_NOW, quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Content-addressed cache for anything that is a pure function of some text\n * and a model: embeddings, summaries, extracted structure. Keyed by\n * `(namespace, model, content_hash)`, so unchanged inputs across re-indexes\n * are cache hits rather than re-computation, and identical text is stored once.\n */\nexport interface CacheBlobOptions {\n name?: string;\n}\n\nexport function cacheBlobTableDdl({ name = \"cache_blob\" }: CacheBlobOptions = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n namespace TEXT NOT NULL,\n model TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n value BLOB NOT NULL,\n meta TEXT,\n created_at TEXT NOT NULL DEFAULT (${SQL_NOW}),\n PRIMARY KEY (namespace, model, content_hash)\n );\n `;\n}\n\nexport function contentHashOf(text: string): string {\n return createHash(\"sha256\").update(text, \"utf8\").digest(\"hex\");\n}\n\nexport interface CacheKey {\n namespace: string;\n model: string;\n /** Either the raw text (hashed for you) or a precomputed `contentHash`. */\n text?: string;\n contentHash?: string;\n}\n\nexport interface CacheHit {\n value: Buffer;\n meta: Record<string, unknown> | null;\n}\n\nfunction hashFor(key: CacheKey): string {\n if (key.contentHash !== undefined) return key.contentHash;\n if (key.text !== undefined) return contentHashOf(key.text);\n throw new Error(\"cache key needs text or contentHash\");\n}\n\nexport class CacheBlobTable {\n private readonly getStmt;\n private readonly putStmt;\n private readonly countStmt;\n\n constructor(db: Db, { name = \"cache_blob\" }: CacheBlobOptions = {}) {\n const t = quoteIdent(name);\n this.getStmt = db.prepare(`SELECT value, meta FROM ${t} WHERE namespace = ? AND model = ? AND content_hash = ?`);\n this.putStmt = db.prepare(\n `INSERT INTO ${t} (namespace, model, content_hash, value, meta) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (namespace, model, content_hash) DO NOTHING`,\n );\n this.countStmt = db.prepare(`SELECT count(*) AS n FROM ${t} WHERE namespace = ? AND model = ?`);\n }\n\n get(key: CacheKey): CacheHit | undefined {\n const raw = this.getStmt.get(key.namespace, key.model, hashFor(key)) as { value: Buffer; meta: string | null } | undefined;\n if (raw === undefined) return undefined;\n return { value: raw.value, meta: raw.meta === null ? null : (JSON.parse(raw.meta) as Record<string, unknown>) };\n }\n\n /** Store once; a second put for the same key is a no-op. Returns true when written. */\n put(key: CacheKey, value: Buffer | string, meta?: Record<string, unknown>): boolean {\n const bytes = typeof value === \"string\" ? Buffer.from(value, \"utf8\") : value;\n const info = this.putStmt.run(key.namespace, key.model, hashFor(key), bytes, meta === undefined ? null : JSON.stringify(meta));\n return info.changes === 1;\n }\n\n /** Get, or compute and store. The compute runs only on a miss. */\n getOrCompute(key: CacheKey, compute: () => Buffer | string, meta?: Record<string, unknown>): Buffer {\n const hit = this.get(key);\n if (hit) return hit.value;\n const value = compute();\n this.put(key, value, meta);\n return typeof value === \"string\" ? Buffer.from(value, \"utf8\") : value;\n }\n\n count(namespace: string, model: string): number {\n return (this.countStmt.get(namespace, model) as { n: number }).n;\n }\n}\n","import { SQL_NOW, nowIso, quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Interval bi-temporal edge table: `t_valid`/`t_invalid` is when the relation\n * held in the world, `t_created`/`t_expired` is when this row was believed.\n * Corrections expire a row and insert a replacement, never delete, so history\n * stays auditable while partial indexes keep live lookups cheap. This is the\n * cross-domain outer graph; relation names are free strings by design.\n */\nexport interface EdgeTableOptions {\n name?: string;\n}\n\nexport function edgeTableDdl({ name = \"edge\" }: EdgeTableOptions = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n edge_id INTEGER PRIMARY KEY,\n source_ref TEXT NOT NULL,\n relation TEXT NOT NULL,\n target_ref TEXT NOT NULL,\n t_valid TEXT NOT NULL,\n t_invalid TEXT,\n t_created TEXT NOT NULL DEFAULT (${SQL_NOW}),\n t_expired TEXT,\n fact_id INTEGER,\n confidence REAL NOT NULL DEFAULT 1.0,\n attrs TEXT\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_source`)} ON ${t}(source_ref, relation) WHERE t_expired IS NULL;\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_target`)} ON ${t}(target_ref, relation) WHERE t_expired IS NULL;\n CREATE UNIQUE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_current`)} ON ${t}(source_ref, relation, target_ref) WHERE t_expired IS NULL;\n `;\n}\n\nexport interface EdgeInput {\n sourceRef: string;\n relation: string;\n targetRef: string;\n tValid?: string;\n tInvalid?: string | null;\n factId?: number | null;\n confidence?: number;\n attrs?: Record<string, unknown> | null;\n}\n\nexport interface EdgeRow {\n edgeId: number;\n sourceRef: string;\n relation: string;\n targetRef: string;\n tValid: string;\n tInvalid: string | null;\n tCreated: string;\n tExpired: string | null;\n factId: number | null;\n confidence: number;\n attrs: Record<string, unknown> | null;\n}\n\ninterface RawEdgeRow {\n edge_id: number;\n source_ref: string;\n relation: string;\n target_ref: string;\n t_valid: string;\n t_invalid: string | null;\n t_created: string;\n t_expired: string | null;\n fact_id: number | null;\n confidence: number;\n attrs: string | null;\n}\n\nfunction toEdgeRow(raw: RawEdgeRow): EdgeRow {\n return {\n edgeId: raw.edge_id,\n sourceRef: raw.source_ref,\n relation: raw.relation,\n targetRef: raw.target_ref,\n tValid: raw.t_valid,\n tInvalid: raw.t_invalid,\n tCreated: raw.t_created,\n tExpired: raw.t_expired,\n factId: raw.fact_id,\n confidence: raw.confidence,\n attrs: raw.attrs === null ? null : (JSON.parse(raw.attrs) as Record<string, unknown>),\n };\n}\n\n/** Prepared helpers over one edge table. Construct once per connection. */\nexport class EdgeTable {\n private readonly insertStmt;\n private readonly expireStmt;\n private readonly currentStmt;\n private readonly fromStmt;\n private readonly toStmt;\n\n constructor(db: Db, { name = \"edge\" }: EdgeTableOptions = {}) {\n const t = quoteIdent(name);\n this.insertStmt = db.prepare(\n `INSERT INTO ${t} (source_ref, relation, target_ref, t_valid, t_invalid, fact_id, confidence, attrs)\n VALUES (@sourceRef, @relation, @targetRef, @tValid, @tInvalid, @factId, @confidence, @attrs)\n ON CONFLICT (source_ref, relation, target_ref) WHERE t_expired IS NULL DO NOTHING`,\n );\n this.expireStmt = db.prepare(\n `UPDATE ${t} SET t_expired = ? WHERE source_ref = ? AND relation = ? AND target_ref = ? AND t_expired IS NULL`,\n );\n this.currentStmt = db.prepare(\n `SELECT * FROM ${t} WHERE source_ref = ? AND relation = ? AND target_ref = ? AND t_expired IS NULL`,\n );\n this.fromStmt = db.prepare(`SELECT * FROM ${t} WHERE source_ref = ? AND t_expired IS NULL ORDER BY edge_id`);\n this.toStmt = db.prepare(`SELECT * FROM ${t} WHERE target_ref = ? AND t_expired IS NULL ORDER BY edge_id`);\n }\n\n /** Assert an edge. A live identical edge is left alone (idempotent re-ingest). Returns true when inserted. */\n assert(edge: EdgeInput): boolean {\n const info = this.insertStmt.run({\n sourceRef: edge.sourceRef,\n relation: edge.relation,\n targetRef: edge.targetRef,\n tValid: edge.tValid ?? nowIso(),\n tInvalid: edge.tInvalid ?? null,\n factId: edge.factId ?? null,\n confidence: edge.confidence ?? 1,\n attrs: edge.attrs == null ? null : JSON.stringify(edge.attrs),\n });\n return info.changes === 1;\n }\n\n /** Retract the current belief in an edge (invalidate, never delete). Returns true when one was live. */\n expire(sourceRef: string, relation: string, targetRef: string, at: string = nowIso()): boolean {\n return this.expireStmt.run(at, sourceRef, relation, targetRef).changes === 1;\n }\n\n /** Replace the live edge with a corrected one in a single step. */\n supersede(edge: EdgeInput, at: string = nowIso()): void {\n this.expire(edge.sourceRef, edge.relation, edge.targetRef, at);\n this.assert(edge);\n }\n\n current(sourceRef: string, relation: string, targetRef: string): EdgeRow | undefined {\n const raw = this.currentStmt.get(sourceRef, relation, targetRef) as RawEdgeRow | undefined;\n return raw === undefined ? undefined : toEdgeRow(raw);\n }\n\n from(sourceRef: string): EdgeRow[] {\n return (this.fromStmt.all(sourceRef) as RawEdgeRow[]).map(toEdgeRow);\n }\n\n to(targetRef: string): EdgeRow[] {\n return (this.toStmt.all(targetRef) as RawEdgeRow[]).map(toEdgeRow);\n }\n}\n","import { SQL_NOW, nowIso, quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Two entity shapes, because the projects that converged on \"kind + name +\n * parent + JSON attrs\" genuinely diverge on time. Snapshot-scoped rows are\n * right when a whole population is re-indexed together (a code graph);\n * interval rows are right when facts change one at a time (memory, sessions).\n */\nexport interface TableName {\n name?: string;\n}\n\n/** Snapshot-scoped entity: every row belongs to one snapshot. */\nexport function entitySnapTableDdl({ name = \"entity_snap\" }: TableName = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n snapshot_id INTEGER NOT NULL,\n id TEXT NOT NULL,\n kind TEXT NOT NULL,\n name TEXT,\n parent_id TEXT,\n attrs TEXT,\n PRIMARY KEY (snapshot_id, id)\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_kind`)} ON ${t}(snapshot_id, kind);\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_parent`)} ON ${t}(snapshot_id, parent_id);\n `;\n}\n\n/** The snapshot registry the snapshot-scoped tables key on. */\nexport function snapshotTableDdl({ name = \"snapshot\" }: TableName = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n id INTEGER PRIMARY KEY,\n ref TEXT NOT NULL,\n taken_at TEXT NOT NULL DEFAULT (${SQL_NOW}),\n attrs TEXT\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_ref`)} ON ${t}(ref, taken_at);\n `;\n}\n\n/** Interval bi-temporal entity keyed by its cross-domain ref. */\nexport function entityTableDdl({ name = \"entity\" }: TableName = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n ref TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n name TEXT,\n parent_ref TEXT,\n attrs TEXT,\n t_valid TEXT NOT NULL,\n t_invalid TEXT,\n t_created TEXT NOT NULL DEFAULT (${SQL_NOW}),\n t_expired TEXT\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_kind`)} ON ${t}(kind) WHERE t_expired IS NULL;\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_parent`)} ON ${t}(parent_ref) WHERE t_expired IS NULL;\n `;\n}\n\nexport interface EntityInput {\n ref: string;\n kind: string;\n name?: string | null;\n parentRef?: string | null;\n attrs?: Record<string, unknown> | null;\n tValid?: string;\n}\n\nexport interface EntityRow {\n ref: string;\n kind: string;\n name: string | null;\n parentRef: string | null;\n attrs: Record<string, unknown> | null;\n tValid: string;\n tInvalid: string | null;\n tCreated: string;\n tExpired: string | null;\n}\n\ninterface RawEntityRow {\n ref: string;\n kind: string;\n name: string | null;\n parent_ref: string | null;\n attrs: string | null;\n t_valid: string;\n t_invalid: string | null;\n t_created: string;\n t_expired: string | null;\n}\n\nfunction toEntityRow(raw: RawEntityRow): EntityRow {\n return {\n ref: raw.ref,\n kind: raw.kind,\n name: raw.name,\n parentRef: raw.parent_ref,\n attrs: raw.attrs === null ? null : (JSON.parse(raw.attrs) as Record<string, unknown>),\n tValid: raw.t_valid,\n tInvalid: raw.t_invalid,\n tCreated: raw.t_created,\n tExpired: raw.t_expired,\n };\n}\n\n/** Prepared helpers over one interval entity table. */\nexport class EntityTable {\n private readonly upsertStmt;\n private readonly getStmt;\n private readonly expireStmt;\n private readonly byKindStmt;\n\n constructor(db: Db, { name = \"entity\" }: TableName = {}) {\n const t = quoteIdent(name);\n this.upsertStmt = db.prepare(\n `INSERT INTO ${t} (ref, kind, name, parent_ref, attrs, t_valid)\n VALUES (@ref, @kind, @name, @parentRef, @attrs, @tValid)\n ON CONFLICT (ref) DO UPDATE SET\n kind = excluded.kind, name = excluded.name, parent_ref = excluded.parent_ref,\n attrs = excluded.attrs, t_expired = NULL, t_invalid = NULL`,\n );\n this.getStmt = db.prepare(`SELECT * FROM ${t} WHERE ref = ?`);\n this.expireStmt = db.prepare(`UPDATE ${t} SET t_expired = ? WHERE ref = ? AND t_expired IS NULL`);\n this.byKindStmt = db.prepare(`SELECT * FROM ${t} WHERE kind = ? AND t_expired IS NULL ORDER BY ref`);\n }\n\n /** Insert or refresh the entity; a previously expired ref comes back live. */\n upsert(entity: EntityInput): void {\n this.upsertStmt.run({\n ref: entity.ref,\n kind: entity.kind,\n name: entity.name ?? null,\n parentRef: entity.parentRef ?? null,\n attrs: entity.attrs == null ? null : JSON.stringify(entity.attrs),\n tValid: entity.tValid ?? nowIso(),\n });\n }\n\n get(ref: string): EntityRow | undefined {\n const raw = this.getStmt.get(ref) as RawEntityRow | undefined;\n return raw === undefined ? undefined : toEntityRow(raw);\n }\n\n expire(ref: string, at: string = nowIso()): boolean {\n return this.expireStmt.run(at, ref).changes === 1;\n }\n\n listByKind(kind: string): EntityRow[] {\n return (this.byKindStmt.all(kind) as RawEntityRow[]).map(toEntityRow);\n }\n}\n","import { quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Full-text search without storing the text. `searchable_span` holds only a\n * locator into the source (a byte range in a transcript, a file, a blob); the\n * FTS5 table is genuinely contentless (`content=''`), so text is streamed in\n * at index time and discarded. Reads MUST join through the span table: a\n * contentless FTS5 table cannot delete rows without their original text, so\n * purges strand FTS rows, and the join is what makes those orphans invisible.\n */\nexport interface SpanFtsOptions {\n /** Base name; tables are `<name>_span` and `<name>_fts`. */\n name?: string;\n}\n\nfunction names(name: string): { span: string; fts: string } {\n return { span: quoteIdent(`${name}_span`), fts: quoteIdent(`${name}_fts`) };\n}\n\nexport function spanFtsTablesDdl({ name = \"search\" }: SpanFtsOptions = {}): string {\n const { span, fts } = names(name);\n return `\n CREATE TABLE IF NOT EXISTS ${span} (\n span_id INTEGER PRIMARY KEY,\n owner_ref TEXT NOT NULL,\n field TEXT NOT NULL,\n source_id INTEGER NOT NULL,\n byte_offset INTEGER NOT NULL,\n byte_length INTEGER NOT NULL,\n UNIQUE (owner_ref, field, source_id, byte_offset)\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_span_owner`)} ON ${span}(owner_ref);\n CREATE VIRTUAL TABLE IF NOT EXISTS ${fts} USING fts5(text, content='');\n `;\n}\n\nexport interface SpanInput {\n /** The entity this text belongs to, e.g. `session:abc`. */\n ownerRef: string;\n /** Which part of the owner, e.g. `prompt` or `tool_result`. */\n field: string;\n /** Transcript or file id the byte range is relative to. */\n sourceId: number;\n byteOffset: number;\n byteLength: number;\n}\n\nexport interface SpanHit extends SpanInput {\n spanId: number;\n /** FTS5 bm25 rank; lower is better. */\n rank: number;\n}\n\ninterface RawSpanHit {\n span_id: number;\n owner_ref: string;\n field: string;\n source_id: number;\n byte_offset: number;\n byte_length: number;\n rank: number;\n}\n\nexport class SpanFtsTables {\n private readonly insertSpan;\n private readonly findSpan;\n private readonly insertFts;\n private readonly searchStmt;\n private readonly deleteAllFts;\n private readonly deleteSpansStmt;\n private readonly countSpans;\n private readonly countFts;\n\n constructor(db: Db, { name = \"search\" }: SpanFtsOptions = {}) {\n const { span, fts } = names(name);\n this.insertSpan = db.prepare(\n `INSERT INTO ${span} (owner_ref, field, source_id, byte_offset, byte_length) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (owner_ref, field, source_id, byte_offset) DO NOTHING`,\n );\n this.findSpan = db.prepare(\n `SELECT span_id FROM ${span} WHERE owner_ref = ? AND field = ? AND source_id = ? AND byte_offset = ?`,\n );\n this.insertFts = db.prepare(`INSERT INTO ${fts} (rowid, text) VALUES (?, ?)`);\n this.searchStmt = db.prepare(\n `SELECT s.span_id, s.owner_ref, s.field, s.source_id, s.byte_offset, s.byte_length, f.rank\n FROM ${fts} f JOIN ${span} s ON s.span_id = f.rowid\n WHERE ${fts} MATCH ? ORDER BY f.rank LIMIT ?`,\n );\n this.deleteAllFts = db.prepare(`INSERT INTO ${fts}(${fts}) VALUES ('delete-all')`);\n this.deleteSpansStmt = db.prepare(`DELETE FROM ${span} WHERE owner_ref = ?`);\n this.countSpans = db.prepare(`SELECT count(*) AS n FROM ${span}`);\n this.countFts = db.prepare(`SELECT count(*) AS n FROM ${fts}`);\n }\n\n /** Index `text` for a span, storing only the locator. Re-indexing the same span is a no-op. Returns the span id. */\n index(span: SpanInput, text: string): number {\n const inserted = this.insertSpan.run(span.ownerRef, span.field, span.sourceId, span.byteOffset, span.byteLength);\n const spanId = (this.findSpan.get(span.ownerRef, span.field, span.sourceId, span.byteOffset) as { span_id: number }).span_id;\n if (inserted.changes === 1) this.insertFts.run(spanId, text);\n return spanId;\n }\n\n /** FTS5 MATCH, joined through the span table so stranded FTS rows never surface. */\n search(query: string, limit = 50): SpanHit[] {\n return (this.searchStmt.all(query, limit) as RawSpanHit[]).map((r) => ({\n spanId: r.span_id,\n ownerRef: r.owner_ref,\n field: r.field,\n sourceId: r.source_id,\n byteOffset: r.byte_offset,\n byteLength: r.byte_length,\n rank: r.rank,\n }));\n }\n\n /** Drop an owner's spans. Their FTS rows become orphans until `rebuild`; the join hides them. */\n purgeOwner(ownerRef: string): number {\n return this.deleteSpansStmt.run(ownerRef).changes;\n }\n\n /** Fraction of FTS rows with no span behind them; a high value means it is time to rebuild. */\n orphanRatio(): number {\n const fts = (this.countFts.get() as { n: number }).n;\n if (fts === 0) return 0;\n const spans = (this.countSpans.get() as { n: number }).n;\n return Math.max(0, fts - spans) / fts;\n }\n\n /** Empty the FTS index. The caller re-streams text through `index` for every surviving span. */\n clearIndex(): void {\n this.deleteAllFts.run();\n }\n}\n","import { SQL_NOW, quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Incremental-ingest bookkeeping for any append-mostly source: where the reader\n * stopped, a hash of what it already consumed (so a rewrite is detected rather\n * than silently re-read from the wrong offset), and the size/mtime/content-hash\n * triple that makes \"source gone or altered\" a reportable state.\n */\nexport type WatermarkStatus = \"ok\" | \"missing\" | \"hash_mismatch\" | \"quarantined\";\n\nexport interface WatermarkOptions {\n name?: string;\n}\n\nexport function watermarkTableDdl({ name = \"watermark\" }: WatermarkOptions = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n source_id INTEGER PRIMARY KEY,\n source_key TEXT NOT NULL UNIQUE,\n last_offset INTEGER NOT NULL DEFAULT 0,\n prefix_hash TEXT,\n last_indexed_at TEXT,\n file_size INTEGER,\n file_mtime TEXT,\n content_hash TEXT,\n status TEXT NOT NULL DEFAULT 'ok',\n status_reason TEXT,\n created_at TEXT NOT NULL DEFAULT (${SQL_NOW})\n );\n `;\n}\n\nexport interface WatermarkRow {\n sourceId: number;\n sourceKey: string;\n lastOffset: number;\n prefixHash: string | null;\n lastIndexedAt: string | null;\n fileSize: number | null;\n fileMtime: string | null;\n contentHash: string | null;\n status: WatermarkStatus;\n statusReason: string | null;\n}\n\ninterface RawWatermarkRow {\n source_id: number;\n source_key: string;\n last_offset: number;\n prefix_hash: string | null;\n last_indexed_at: string | null;\n file_size: number | null;\n file_mtime: string | null;\n content_hash: string | null;\n status: WatermarkStatus;\n status_reason: string | null;\n}\n\nfunction toRow(raw: RawWatermarkRow): WatermarkRow {\n return {\n sourceId: raw.source_id,\n sourceKey: raw.source_key,\n lastOffset: raw.last_offset,\n prefixHash: raw.prefix_hash,\n lastIndexedAt: raw.last_indexed_at,\n fileSize: raw.file_size,\n fileMtime: raw.file_mtime,\n contentHash: raw.content_hash,\n status: raw.status,\n statusReason: raw.status_reason,\n };\n}\n\nexport interface WatermarkAdvance {\n lastOffset: number;\n prefixHash?: string | null;\n fileSize?: number | null;\n fileMtime?: string | null;\n /** Null keeps any previously stored hash; computing it is O(file) and optional. */\n contentHash?: string | null;\n}\n\nexport class WatermarkTable {\n private readonly ensureStmt;\n private readonly getStmt;\n private readonly advanceStmt;\n private readonly statusStmt;\n private readonly rewindStmt;\n private readonly listStmt;\n\n constructor(db: Db, { name = \"watermark\" }: WatermarkOptions = {}) {\n const t = quoteIdent(name);\n this.ensureStmt = db.prepare(`INSERT INTO ${t} (source_key) VALUES (?) ON CONFLICT (source_key) DO NOTHING`);\n this.getStmt = db.prepare(`SELECT * FROM ${t} WHERE source_key = ?`);\n this.advanceStmt = db.prepare(\n `UPDATE ${t} SET last_offset = @lastOffset, prefix_hash = @prefixHash, last_indexed_at = ${SQL_NOW},\n file_size = @fileSize, file_mtime = @fileMtime, content_hash = COALESCE(@contentHash, content_hash),\n status = 'ok', status_reason = NULL\n WHERE source_key = @sourceKey`,\n );\n this.statusStmt = db.prepare(`UPDATE ${t} SET status = ?, status_reason = ? WHERE source_key = ?`);\n this.rewindStmt = db.prepare(`UPDATE ${t} SET last_offset = 0, prefix_hash = NULL WHERE source_key = ?`);\n this.listStmt = db.prepare(`SELECT * FROM ${t} ORDER BY source_id`);\n }\n\n /** The row for a source, created at offset 0 on first sight. The id is stable for the table's life. */\n ensure(sourceKey: string): WatermarkRow {\n this.ensureStmt.run(sourceKey);\n return toRow(this.getStmt.get(sourceKey) as RawWatermarkRow);\n }\n\n get(sourceKey: string): WatermarkRow | undefined {\n const raw = this.getStmt.get(sourceKey) as RawWatermarkRow | undefined;\n return raw === undefined ? undefined : toRow(raw);\n }\n\n advance(sourceKey: string, update: WatermarkAdvance): void {\n this.advanceStmt.run({\n sourceKey,\n lastOffset: update.lastOffset,\n prefixHash: update.prefixHash ?? null,\n fileSize: update.fileSize ?? null,\n fileMtime: update.fileMtime ?? null,\n contentHash: update.contentHash ?? null,\n });\n }\n\n /** Back to byte 0, for a source that was rewritten rather than appended to. */\n rewind(sourceKey: string): void {\n this.rewindStmt.run(sourceKey);\n }\n\n markStatus(sourceKey: string, status: WatermarkStatus, reason: string | null = null): void {\n this.statusStmt.run(status, reason, sourceKey);\n }\n\n list(): WatermarkRow[] {\n return (this.listStmt.all() as RawWatermarkRow[]).map(toRow);\n }\n}\n","import type { Migration } from \"./migrations.js\";\nimport { cacheBlobTableDdl } from \"./tables/cache-blob.js\";\nimport { edgeTableDdl } from \"./tables/edge.js\";\nimport { entitySnapTableDdl, entityTableDdl, snapshotTableDdl } from \"./tables/entity.js\";\nimport { spanFtsTablesDdl } from \"./tables/span-fts.js\";\nimport { watermarkTableDdl } from \"./tables/watermark.js\";\n\n/** Which kit tables a store wants, by name override or `true` for the default name. */\nexport interface KitSelection {\n snapshot?: string | true;\n entitySnap?: string | true;\n entity?: string | true;\n edge?: string | true;\n cacheBlob?: string | true;\n spanFts?: string | true;\n watermark?: string | true;\n}\n\ntype Factory = (options: { name?: string }) => string;\n\nconst FACTORIES: [keyof KitSelection, Factory][] = [\n [\"snapshot\", snapshotTableDdl],\n [\"entitySnap\", entitySnapTableDdl],\n [\"entity\", entityTableDdl],\n [\"edge\", edgeTableDdl],\n [\"cacheBlob\", cacheBlobTableDdl],\n [\"spanFts\", spanFtsTablesDdl],\n [\"watermark\", watermarkTableDdl],\n];\n\n/** The DDL for a selection of kit tables, in dependency-safe order. Every statement is `IF NOT EXISTS`. */\nexport function kitDdl(selection: KitSelection): string {\n return FACTORIES.filter(([key]) => selection[key] !== undefined)\n .map(([key, factory]) => factory(selection[key] === true ? {} : { name: selection[key] as string }))\n .join(\"\\n\");\n}\n\n/** A migration that installs the selected kit tables, for use as a store's version 1. */\nexport function kitMigration(version: number, selection: KitSelection, name = \"kit tables\"): Migration {\n return { version, name, up: (db) => db.exec(kitDdl(selection)) };\n}\n"],"mappings":"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","names":["ref"]}
1
+ {"version":3,"sources":["../src/open.ts","../src/migrations.ts","../src/ref.ts","../src/tables/cache-blob.ts","../src/tables/edge.ts","../src/tables/entity.ts","../src/tables/span-fts.ts","../src/tables/watermark.ts","../src/kit.ts"],"sourcesContent":["import Database from \"better-sqlite3\";\nimport { mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport type Db = Database.Database;\n\nexport interface OpenOptions {\n readonly?: boolean;\n /** WAL keeps readers from blocking a writer. On by default for file databases. */\n wal?: boolean;\n /** SQLite leaves foreign keys off per connection; the kit turns them on. */\n foreignKeys?: boolean;\n}\n\n/** Open (creating if absent) a database with the kit's pragmas. `:memory:` works for tests. */\nexport function openDatabase(dbPath: string, options: OpenOptions = {}): Db {\n const inMemory = dbPath === \":memory:\";\n if (!inMemory && !options.readonly) mkdirSync(path.dirname(dbPath), { recursive: true });\n const db = new Database(dbPath, { readonly: options.readonly ?? false });\n if (!inMemory && !options.readonly && (options.wal ?? true)) db.pragma(\"journal_mode = WAL\");\n if (options.foreignKeys ?? true) db.pragma(\"foreign_keys = ON\");\n return db;\n}\n\n/** ISO-8601 with milliseconds, the timestamp shape every kit table stores. */\nexport function nowIso(): string {\n return new Date().toISOString();\n}\n\n/** SQL expression producing the same shape as `nowIso()` inside the database. */\nexport const SQL_NOW = \"strftime('%Y-%m-%dT%H:%M:%fZ','now')\";\n\nexport function hasTable(db: Db, table: string): boolean {\n const row = db.prepare(\"SELECT 1 FROM sqlite_master WHERE type IN ('table','view') AND name = ?\").get(table);\n return row !== undefined;\n}\n\nexport function hasColumn(db: Db, table: string, column: string): boolean {\n const cols = db.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all() as { name: string }[];\n return cols.some((c) => c.name === column);\n}\n\n/** Double-quote an identifier so table factories can take caller-chosen names safely. */\nexport function quoteIdent(name: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`invalid SQL identifier: ${name}`);\n return `\"${name}\"`;\n}\n","import type { Db } from \"./open.js\";\nimport { nowIso } from \"./open.js\";\n\nexport interface Migration {\n /** Positive, unique, applied in ascending order. */\n version: number;\n name?: string;\n up: (db: Db) => void;\n}\n\nconst MIGRATION_TABLE = `\n CREATE TABLE IF NOT EXISTS _migration (\n version INTEGER PRIMARY KEY,\n name TEXT,\n applied_at TEXT NOT NULL\n )`;\n\nexport function appliedVersions(db: Db): number[] {\n db.exec(MIGRATION_TABLE);\n const rows = db.prepare(\"SELECT version FROM _migration ORDER BY version\").all() as { version: number }[];\n return rows.map((r) => r.version);\n}\n\n/**\n * Apply every migration not yet recorded in `_migration`, each in its own\n * transaction so a failure leaves the database at the last good version.\n * Returns the versions applied this call.\n */\nexport function runMigrations(db: Db, migrations: readonly Migration[]): number[] {\n assertWellFormed(migrations);\n const applied = new Set(appliedVersions(db));\n const record = db.prepare(\"INSERT INTO _migration (version, name, applied_at) VALUES (?, ?, ?)\");\n const applyOne = db.transaction((m: Migration) => {\n m.up(db);\n record.run(m.version, m.name ?? null, nowIso());\n });\n const done: number[] = [];\n for (const m of [...migrations].sort((a, b) => a.version - b.version)) {\n if (applied.has(m.version)) continue;\n applyOne(m);\n done.push(m.version);\n }\n return done;\n}\n\nfunction assertWellFormed(migrations: readonly Migration[]): void {\n const seen = new Set<number>();\n for (const m of migrations) {\n if (!Number.isInteger(m.version) || m.version <= 0) throw new Error(`migration version must be a positive integer: ${m.version}`);\n if (seen.has(m.version)) throw new Error(`duplicate migration version: ${m.version}`);\n seen.add(m.version);\n }\n}\n","/**\n * The cross-domain identity grammar: `<kind>:<id>`, where `kind` is a short\n * lowercase token and `id` is anything the domain needs (paths, `repo#node`,\n * numbers). Every domain mints refs for its own entities and the shared edge\n * table links them, so `session:abc` can point at `codewatch:repo#src/a.ts::Foo`\n * without either side knowing the other's schema.\n */\nexport interface ParsedRef {\n kind: string;\n id: string;\n}\n\nconst KIND = /^[a-z][a-z0-9_-]*$/;\n\nexport function ref(kind: string, id: string): string {\n if (!KIND.test(kind)) throw new Error(`invalid ref kind: ${JSON.stringify(kind)}`);\n if (id.length === 0) throw new Error(\"ref id must not be empty\");\n return `${kind}:${id}`;\n}\n\nexport function parseRef(value: string): ParsedRef {\n const at = value.indexOf(\":\");\n if (at <= 0 || at === value.length - 1 || !KIND.test(value.slice(0, at))) {\n throw new Error(`invalid ref: ${JSON.stringify(value)}`);\n }\n return { kind: value.slice(0, at), id: value.slice(at + 1) };\n}\n\nexport function isRef(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n try {\n parseRef(value);\n return true;\n } catch {\n return false;\n }\n}\n\n/** A typed minter for one kind: `const session = refKind(\"session\"); session(\"abc\")`. */\nexport function refKind(kind: string): (id: string) => string {\n ref(kind, \"probe\");\n return (id) => ref(kind, id);\n}\n","import { createHash } from \"node:crypto\";\nimport { SQL_NOW, quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Content-addressed cache for anything that is a pure function of some text\n * and a model: embeddings, summaries, extracted structure. Keyed by\n * `(namespace, model, content_hash)`, so unchanged inputs across re-indexes\n * are cache hits rather than re-computation, and identical text is stored once.\n */\nexport interface CacheBlobOptions {\n name?: string;\n}\n\nexport function cacheBlobTableDdl({ name = \"cache_blob\" }: CacheBlobOptions = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n namespace TEXT NOT NULL,\n model TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n value BLOB NOT NULL,\n meta TEXT,\n created_at TEXT NOT NULL DEFAULT (${SQL_NOW}),\n PRIMARY KEY (namespace, model, content_hash)\n );\n `;\n}\n\nexport function contentHashOf(text: string): string {\n return createHash(\"sha256\").update(text, \"utf8\").digest(\"hex\");\n}\n\nexport interface CacheKey {\n namespace: string;\n model: string;\n /** Either the raw text (hashed for you) or a precomputed `contentHash`. */\n text?: string;\n contentHash?: string;\n}\n\nexport interface CacheHit {\n value: Buffer;\n meta: Record<string, unknown> | null;\n}\n\nfunction hashFor(key: CacheKey): string {\n if (key.contentHash !== undefined) return key.contentHash;\n if (key.text !== undefined) return contentHashOf(key.text);\n throw new Error(\"cache key needs text or contentHash\");\n}\n\nexport class CacheBlobTable {\n private readonly getStmt;\n private readonly putStmt;\n private readonly countStmt;\n\n constructor(db: Db, { name = \"cache_blob\" }: CacheBlobOptions = {}) {\n const t = quoteIdent(name);\n this.getStmt = db.prepare(`SELECT value, meta FROM ${t} WHERE namespace = ? AND model = ? AND content_hash = ?`);\n this.putStmt = db.prepare(\n `INSERT INTO ${t} (namespace, model, content_hash, value, meta) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (namespace, model, content_hash) DO NOTHING`,\n );\n this.countStmt = db.prepare(`SELECT count(*) AS n FROM ${t} WHERE namespace = ? AND model = ?`);\n }\n\n get(key: CacheKey): CacheHit | undefined {\n const raw = this.getStmt.get(key.namespace, key.model, hashFor(key)) as { value: Buffer; meta: string | null } | undefined;\n if (raw === undefined) return undefined;\n return { value: raw.value, meta: raw.meta === null ? null : (JSON.parse(raw.meta) as Record<string, unknown>) };\n }\n\n /** Store once; a second put for the same key is a no-op. Returns true when written. */\n put(key: CacheKey, value: Buffer | string, meta?: Record<string, unknown>): boolean {\n const bytes = typeof value === \"string\" ? Buffer.from(value, \"utf8\") : value;\n const info = this.putStmt.run(key.namespace, key.model, hashFor(key), bytes, meta === undefined ? null : JSON.stringify(meta));\n return info.changes === 1;\n }\n\n /** Get, or compute and store. The compute runs only on a miss. */\n getOrCompute(key: CacheKey, compute: () => Buffer | string, meta?: Record<string, unknown>): Buffer {\n const hit = this.get(key);\n if (hit) return hit.value;\n const value = compute();\n this.put(key, value, meta);\n return typeof value === \"string\" ? Buffer.from(value, \"utf8\") : value;\n }\n\n count(namespace: string, model: string): number {\n return (this.countStmt.get(namespace, model) as { n: number }).n;\n }\n}\n","import { SQL_NOW, nowIso, quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Interval bi-temporal edge table: `t_valid`/`t_invalid` is when the relation\n * held in the world, `t_created`/`t_expired` is when this row was believed.\n * Corrections expire a row and insert a replacement, never delete, so history\n * stays auditable while partial indexes keep live lookups cheap. This is the\n * cross-domain outer graph; relation names are free strings by design.\n */\nexport interface EdgeTableOptions {\n name?: string;\n}\n\nexport function edgeTableDdl({ name = \"edge\" }: EdgeTableOptions = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n edge_id INTEGER PRIMARY KEY,\n source_ref TEXT NOT NULL,\n relation TEXT NOT NULL,\n target_ref TEXT NOT NULL,\n t_valid TEXT NOT NULL,\n t_invalid TEXT,\n t_created TEXT NOT NULL DEFAULT (${SQL_NOW}),\n t_expired TEXT,\n fact_id INTEGER,\n confidence REAL NOT NULL DEFAULT 1.0,\n attrs TEXT\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_source`)} ON ${t}(source_ref, relation) WHERE t_expired IS NULL;\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_target`)} ON ${t}(target_ref, relation) WHERE t_expired IS NULL;\n CREATE UNIQUE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_current`)} ON ${t}(source_ref, relation, target_ref) WHERE t_expired IS NULL;\n `;\n}\n\nexport interface EdgeInput {\n sourceRef: string;\n relation: string;\n targetRef: string;\n tValid?: string;\n tInvalid?: string | null;\n factId?: number | null;\n confidence?: number;\n attrs?: Record<string, unknown> | null;\n}\n\nexport interface EdgeRow {\n edgeId: number;\n sourceRef: string;\n relation: string;\n targetRef: string;\n tValid: string;\n tInvalid: string | null;\n tCreated: string;\n tExpired: string | null;\n factId: number | null;\n confidence: number;\n attrs: Record<string, unknown> | null;\n}\n\ninterface RawEdgeRow {\n edge_id: number;\n source_ref: string;\n relation: string;\n target_ref: string;\n t_valid: string;\n t_invalid: string | null;\n t_created: string;\n t_expired: string | null;\n fact_id: number | null;\n confidence: number;\n attrs: string | null;\n}\n\nfunction toEdgeRow(raw: RawEdgeRow): EdgeRow {\n return {\n edgeId: raw.edge_id,\n sourceRef: raw.source_ref,\n relation: raw.relation,\n targetRef: raw.target_ref,\n tValid: raw.t_valid,\n tInvalid: raw.t_invalid,\n tCreated: raw.t_created,\n tExpired: raw.t_expired,\n factId: raw.fact_id,\n confidence: raw.confidence,\n attrs: raw.attrs === null ? null : (JSON.parse(raw.attrs) as Record<string, unknown>),\n };\n}\n\n/** Prepared helpers over one edge table. Construct once per connection. */\nexport class EdgeTable {\n private readonly insertStmt;\n private readonly expireStmt;\n private readonly currentStmt;\n private readonly fromStmt;\n private readonly toStmt;\n\n constructor(db: Db, { name = \"edge\" }: EdgeTableOptions = {}) {\n const t = quoteIdent(name);\n this.insertStmt = db.prepare(\n `INSERT INTO ${t} (source_ref, relation, target_ref, t_valid, t_invalid, fact_id, confidence, attrs)\n VALUES (@sourceRef, @relation, @targetRef, @tValid, @tInvalid, @factId, @confidence, @attrs)\n ON CONFLICT (source_ref, relation, target_ref) WHERE t_expired IS NULL DO NOTHING`,\n );\n this.expireStmt = db.prepare(\n `UPDATE ${t} SET t_expired = ? WHERE source_ref = ? AND relation = ? AND target_ref = ? AND t_expired IS NULL`,\n );\n this.currentStmt = db.prepare(\n `SELECT * FROM ${t} WHERE source_ref = ? AND relation = ? AND target_ref = ? AND t_expired IS NULL`,\n );\n this.fromStmt = db.prepare(`SELECT * FROM ${t} WHERE source_ref = ? AND t_expired IS NULL ORDER BY edge_id`);\n this.toStmt = db.prepare(`SELECT * FROM ${t} WHERE target_ref = ? AND t_expired IS NULL ORDER BY edge_id`);\n }\n\n /** Assert an edge. A live identical edge is left alone (idempotent re-ingest). Returns true when inserted. */\n assert(edge: EdgeInput): boolean {\n const info = this.insertStmt.run({\n sourceRef: edge.sourceRef,\n relation: edge.relation,\n targetRef: edge.targetRef,\n tValid: edge.tValid ?? nowIso(),\n tInvalid: edge.tInvalid ?? null,\n factId: edge.factId ?? null,\n confidence: edge.confidence ?? 1,\n attrs: edge.attrs == null ? null : JSON.stringify(edge.attrs),\n });\n return info.changes === 1;\n }\n\n /** Retract the current belief in an edge (invalidate, never delete). Returns true when one was live. */\n expire(sourceRef: string, relation: string, targetRef: string, at: string = nowIso()): boolean {\n return this.expireStmt.run(at, sourceRef, relation, targetRef).changes === 1;\n }\n\n /** Replace the live edge with a corrected one in a single step. */\n supersede(edge: EdgeInput, at: string = nowIso()): void {\n this.expire(edge.sourceRef, edge.relation, edge.targetRef, at);\n this.assert(edge);\n }\n\n current(sourceRef: string, relation: string, targetRef: string): EdgeRow | undefined {\n const raw = this.currentStmt.get(sourceRef, relation, targetRef) as RawEdgeRow | undefined;\n return raw === undefined ? undefined : toEdgeRow(raw);\n }\n\n from(sourceRef: string): EdgeRow[] {\n return (this.fromStmt.all(sourceRef) as RawEdgeRow[]).map(toEdgeRow);\n }\n\n to(targetRef: string): EdgeRow[] {\n return (this.toStmt.all(targetRef) as RawEdgeRow[]).map(toEdgeRow);\n }\n}\n","import { SQL_NOW, nowIso, quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Two entity shapes, because the projects that converged on \"kind + name +\n * parent + JSON attrs\" genuinely diverge on time. Snapshot-scoped rows are\n * right when a whole population is re-indexed together (a code graph);\n * interval rows are right when facts change one at a time (memory, sessions).\n */\nexport interface TableName {\n name?: string;\n}\n\n/** Snapshot-scoped entity: every row belongs to one snapshot. */\nexport function entitySnapTableDdl({ name = \"entity_snap\" }: TableName = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n snapshot_id INTEGER NOT NULL,\n id TEXT NOT NULL,\n kind TEXT NOT NULL,\n name TEXT,\n parent_id TEXT,\n attrs TEXT,\n PRIMARY KEY (snapshot_id, id)\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_kind`)} ON ${t}(snapshot_id, kind);\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_parent`)} ON ${t}(snapshot_id, parent_id);\n `;\n}\n\n/** The snapshot registry the snapshot-scoped tables key on. */\nexport function snapshotTableDdl({ name = \"snapshot\" }: TableName = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n id INTEGER PRIMARY KEY,\n ref TEXT NOT NULL,\n taken_at TEXT NOT NULL DEFAULT (${SQL_NOW}),\n attrs TEXT\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_ref`)} ON ${t}(ref, taken_at);\n `;\n}\n\n/** Interval bi-temporal entity keyed by its cross-domain ref. */\nexport function entityTableDdl({ name = \"entity\" }: TableName = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n ref TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n name TEXT,\n parent_ref TEXT,\n attrs TEXT,\n t_valid TEXT NOT NULL,\n t_invalid TEXT,\n t_created TEXT NOT NULL DEFAULT (${SQL_NOW}),\n t_expired TEXT\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_kind`)} ON ${t}(kind) WHERE t_expired IS NULL;\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_parent`)} ON ${t}(parent_ref) WHERE t_expired IS NULL;\n `;\n}\n\nexport interface EntityInput {\n ref: string;\n kind: string;\n name?: string | null;\n parentRef?: string | null;\n attrs?: Record<string, unknown> | null;\n tValid?: string;\n}\n\nexport interface EntityRow {\n ref: string;\n kind: string;\n name: string | null;\n parentRef: string | null;\n attrs: Record<string, unknown> | null;\n tValid: string;\n tInvalid: string | null;\n tCreated: string;\n tExpired: string | null;\n}\n\ninterface RawEntityRow {\n ref: string;\n kind: string;\n name: string | null;\n parent_ref: string | null;\n attrs: string | null;\n t_valid: string;\n t_invalid: string | null;\n t_created: string;\n t_expired: string | null;\n}\n\nfunction toEntityRow(raw: RawEntityRow): EntityRow {\n return {\n ref: raw.ref,\n kind: raw.kind,\n name: raw.name,\n parentRef: raw.parent_ref,\n attrs: raw.attrs === null ? null : (JSON.parse(raw.attrs) as Record<string, unknown>),\n tValid: raw.t_valid,\n tInvalid: raw.t_invalid,\n tCreated: raw.t_created,\n tExpired: raw.t_expired,\n };\n}\n\n/** Prepared helpers over one interval entity table. */\nexport class EntityTable {\n private readonly upsertStmt;\n private readonly getStmt;\n private readonly expireStmt;\n private readonly byKindStmt;\n\n constructor(db: Db, { name = \"entity\" }: TableName = {}) {\n const t = quoteIdent(name);\n this.upsertStmt = db.prepare(\n `INSERT INTO ${t} (ref, kind, name, parent_ref, attrs, t_valid)\n VALUES (@ref, @kind, @name, @parentRef, @attrs, @tValid)\n ON CONFLICT (ref) DO UPDATE SET\n kind = excluded.kind, name = excluded.name, parent_ref = excluded.parent_ref,\n attrs = excluded.attrs, t_expired = NULL, t_invalid = NULL`,\n );\n this.getStmt = db.prepare(`SELECT * FROM ${t} WHERE ref = ?`);\n this.expireStmt = db.prepare(`UPDATE ${t} SET t_expired = ? WHERE ref = ? AND t_expired IS NULL`);\n this.byKindStmt = db.prepare(`SELECT * FROM ${t} WHERE kind = ? AND t_expired IS NULL ORDER BY ref`);\n }\n\n /** Insert or refresh the entity; a previously expired ref comes back live. */\n upsert(entity: EntityInput): void {\n this.upsertStmt.run({\n ref: entity.ref,\n kind: entity.kind,\n name: entity.name ?? null,\n parentRef: entity.parentRef ?? null,\n attrs: entity.attrs == null ? null : JSON.stringify(entity.attrs),\n tValid: entity.tValid ?? nowIso(),\n });\n }\n\n get(ref: string): EntityRow | undefined {\n const raw = this.getStmt.get(ref) as RawEntityRow | undefined;\n return raw === undefined ? undefined : toEntityRow(raw);\n }\n\n expire(ref: string, at: string = nowIso()): boolean {\n return this.expireStmt.run(at, ref).changes === 1;\n }\n\n listByKind(kind: string): EntityRow[] {\n return (this.byKindStmt.all(kind) as RawEntityRow[]).map(toEntityRow);\n }\n}\n","import { quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Full-text search without storing the text. `searchable_span` holds only a\n * locator into the source (a byte range in a transcript, a file, a blob); the\n * FTS5 table is genuinely contentless (`content=''`), so text is streamed in\n * at index time and discarded. Reads MUST join through the span table: a\n * contentless FTS5 table cannot delete rows without their original text, so\n * purges strand FTS rows, and the join is what makes those orphans invisible.\n */\nexport interface SpanFtsOptions {\n /** Base name; tables are `<name>_span` and `<name>_fts`. */\n name?: string;\n}\n\nfunction names(name: string): { span: string; fts: string } {\n return { span: quoteIdent(`${name}_span`), fts: quoteIdent(`${name}_fts`) };\n}\n\nexport function spanFtsTablesDdl({ name = \"search\" }: SpanFtsOptions = {}): string {\n const { span, fts } = names(name);\n return `\n CREATE TABLE IF NOT EXISTS ${span} (\n span_id INTEGER PRIMARY KEY,\n owner_ref TEXT NOT NULL,\n field TEXT NOT NULL,\n source_id INTEGER NOT NULL,\n byte_offset INTEGER NOT NULL,\n byte_length INTEGER NOT NULL,\n UNIQUE (owner_ref, field, source_id, byte_offset)\n );\n CREATE INDEX IF NOT EXISTS ${quoteIdent(`idx_${name}_span_owner`)} ON ${span}(owner_ref);\n CREATE VIRTUAL TABLE IF NOT EXISTS ${fts} USING fts5(text, content='');\n `;\n}\n\nexport interface SpanInput {\n /** The entity this text belongs to, e.g. `session:abc`. */\n ownerRef: string;\n /** Which part of the owner, e.g. `prompt` or `tool_result`. */\n field: string;\n /** Transcript or file id the byte range is relative to. */\n sourceId: number;\n byteOffset: number;\n byteLength: number;\n}\n\nexport interface SpanHit extends SpanInput {\n spanId: number;\n /** FTS5 bm25 rank; lower is better. */\n rank: number;\n}\n\n/**\n * Narrow a search to one class of owner, so one class cannot flood a result set.\n *\n * A span table that holds several entity classes holds them in wildly different\n * volumes. Measured on active-work's graph: 92,713 transcript spans against\n * 1,569 note spans, 59 to 1. Ranking one pooled query returns transcript\n * chatter and the notes never surface, so the fix is a separate ranked list per\n * class, which needs a way to ask for one class.\n *\n * `ownerPrefix` and `fields` are both needed, not either. active-work keys both\n * a mined transcript and a workspace session record under `session:`, and they\n * are separable only by field — `body` is the record, `prompt` and the\n * `tool_*` fields are the transcript. A prefix-only filter would put 92,713\n * transcript spans into the records' list and look like it was working.\n */\nexport interface SpanScope {\n /** Match owners whose ref starts with this, e.g. `note:`. A range scan, so the owner index applies. */\n ownerPrefix?: string;\n /** Match only these fields, e.g. `[\"title\", \"body\"]`. Empty means no field matches. */\n fields?: string[];\n}\n\ninterface RawSpanHit {\n span_id: number;\n owner_ref: string;\n field: string;\n source_id: number;\n byte_offset: number;\n byte_length: number;\n rank: number;\n}\n\n/** The string just past the prefix, so a prefix match is a half-open range the index can serve. */\nfunction prefixUpperBound(prefix: string): string {\n const last = prefix.charCodeAt(prefix.length - 1);\n return prefix.slice(0, -1) + String.fromCharCode(last + 1);\n}\n\n/** An empty prefix means \"every owner\", which is what omitting it already means. */\nfunction hasPrefix(scope: SpanScope): boolean {\n return scope.ownerPrefix !== undefined && scope.ownerPrefix.length > 0;\n}\n\n/** Bind values for `scopedStatement`'s predicates, in the order it writes them. */\nfunction scopeValues(scope: SpanScope): (string | number)[] {\n const values: (string | number)[] = [];\n if (hasPrefix(scope)) values.push(scope.ownerPrefix!, prefixUpperBound(scope.ownerPrefix!));\n if (scope.fields !== undefined) values.push(...scope.fields);\n return values;\n}\n\nexport class SpanFtsTables {\n private readonly db: Db;\n private readonly tableNames: { span: string; fts: string };\n private readonly insertSpan;\n private readonly findSpan;\n private readonly insertFts;\n private readonly searchStmt;\n /** One prepared statement per scope shape, since the predicate list varies. */\n private readonly scopedSearchStmts = new Map<string, ReturnType<Db[\"prepare\"]>>();\n private readonly deleteAllFts;\n private readonly deleteSpansStmt;\n private readonly countSpans;\n private readonly countFts;\n\n constructor(db: Db, { name = \"search\" }: SpanFtsOptions = {}) {\n const { span, fts } = names(name);\n this.db = db;\n this.tableNames = { span, fts };\n this.insertSpan = db.prepare(\n `INSERT INTO ${span} (owner_ref, field, source_id, byte_offset, byte_length) VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (owner_ref, field, source_id, byte_offset) DO NOTHING`,\n );\n this.findSpan = db.prepare(\n `SELECT span_id FROM ${span} WHERE owner_ref = ? AND field = ? AND source_id = ? AND byte_offset = ?`,\n );\n this.insertFts = db.prepare(`INSERT INTO ${fts} (rowid, text) VALUES (?, ?)`);\n this.searchStmt = db.prepare(\n `SELECT s.span_id, s.owner_ref, s.field, s.source_id, s.byte_offset, s.byte_length, f.rank\n FROM ${fts} f JOIN ${span} s ON s.span_id = f.rowid\n WHERE ${fts} MATCH ? ORDER BY f.rank LIMIT ?`,\n );\n this.deleteAllFts = db.prepare(`INSERT INTO ${fts}(${fts}) VALUES ('delete-all')`);\n this.deleteSpansStmt = db.prepare(`DELETE FROM ${span} WHERE owner_ref = ?`);\n this.countSpans = db.prepare(`SELECT count(*) AS n FROM ${span}`);\n this.countFts = db.prepare(`SELECT count(*) AS n FROM ${fts}`);\n }\n\n /** Index `text` for a span, storing only the locator. Re-indexing the same span is a no-op. Returns the span id. */\n index(span: SpanInput, text: string): number {\n const inserted = this.insertSpan.run(span.ownerRef, span.field, span.sourceId, span.byteOffset, span.byteLength);\n const spanId = (this.findSpan.get(span.ownerRef, span.field, span.sourceId, span.byteOffset) as { span_id: number }).span_id;\n if (inserted.changes === 1) this.insertFts.run(spanId, text);\n return spanId;\n }\n\n /**\n * FTS5 MATCH, joined through the span table so stranded FTS rows never surface.\n *\n * `scope` narrows to one class of owner; `limit` is applied after it, so a\n * scoped search returns its own top-N rather than whatever survives the\n * filter out of a global top-N.\n */\n search(query: string, limit = 50, scope?: SpanScope): SpanHit[] {\n const rows = (\n scope ? this.scopedStatement(scope).all([query, ...scopeValues(scope), limit]) : this.searchStmt.all(query, limit)\n ) as RawSpanHit[];\n return rows.map((r) => ({\n spanId: r.span_id,\n ownerRef: r.owner_ref,\n field: r.field,\n sourceId: r.source_id,\n byteOffset: r.byte_offset,\n byteLength: r.byte_length,\n rank: r.rank,\n }));\n }\n\n /**\n * Prepared statement for one scope shape, cached.\n *\n * Cached by shape rather than by values, because a product registers a fixed\n * handful of class retrievers and then queries them for the life of the\n * process. Keyed on the field *count* and whether a prefix is present, which\n * is everything that changes the SQL.\n */\n private scopedStatement(scope: SpanScope): ReturnType<Db[\"prepare\"]> {\n const fieldCount = scope.fields?.length ?? 0;\n const key = `${hasPrefix(scope) ? 1 : 0}:${scope.fields === undefined ? \"all\" : fieldCount}`;\n const cached = this.scopedSearchStmts.get(key);\n if (cached) return cached;\n\n const { span, fts } = this.tableNames;\n const predicates = [`${fts} MATCH ?`];\n // A half-open range rather than LIKE or GLOB, so the owner index applies.\n if (hasPrefix(scope)) predicates.push(\"s.owner_ref >= ? AND s.owner_ref < ?\");\n if (scope.fields !== undefined) {\n predicates.push(fieldCount === 0 ? \"0\" : `s.field IN (${new Array(fieldCount).fill(\"?\").join(\", \")})`);\n }\n const statement = this.db.prepare(\n `SELECT s.span_id, s.owner_ref, s.field, s.source_id, s.byte_offset, s.byte_length, f.rank\n FROM ${fts} f JOIN ${span} s ON s.span_id = f.rowid\n WHERE ${predicates.join(\" AND \")} ORDER BY f.rank LIMIT ?`,\n );\n this.scopedSearchStmts.set(key, statement);\n return statement;\n }\n\n /** Drop an owner's spans. Their FTS rows become orphans until `rebuild`; the join hides them. */\n purgeOwner(ownerRef: string): number {\n return this.deleteSpansStmt.run(ownerRef).changes;\n }\n\n /** Fraction of FTS rows with no span behind them; a high value means it is time to rebuild. */\n orphanRatio(): number {\n const fts = (this.countFts.get() as { n: number }).n;\n if (fts === 0) return 0;\n const spans = (this.countSpans.get() as { n: number }).n;\n return Math.max(0, fts - spans) / fts;\n }\n\n /** Empty the FTS index. The caller re-streams text through `index` for every surviving span. */\n clearIndex(): void {\n this.deleteAllFts.run();\n }\n}\n","import { SQL_NOW, quoteIdent, type Db } from \"../open.js\";\n\n/**\n * Incremental-ingest bookkeeping for any append-mostly source: where the reader\n * stopped, a hash of what it already consumed (so a rewrite is detected rather\n * than silently re-read from the wrong offset), and the size/mtime/content-hash\n * triple that makes \"source gone or altered\" a reportable state.\n */\nexport type WatermarkStatus = \"ok\" | \"missing\" | \"hash_mismatch\" | \"quarantined\";\n\nexport interface WatermarkOptions {\n name?: string;\n}\n\nexport function watermarkTableDdl({ name = \"watermark\" }: WatermarkOptions = {}): string {\n const t = quoteIdent(name);\n return `\n CREATE TABLE IF NOT EXISTS ${t} (\n source_id INTEGER PRIMARY KEY,\n source_key TEXT NOT NULL UNIQUE,\n last_offset INTEGER NOT NULL DEFAULT 0,\n prefix_hash TEXT,\n last_indexed_at TEXT,\n file_size INTEGER,\n file_mtime TEXT,\n content_hash TEXT,\n status TEXT NOT NULL DEFAULT 'ok',\n status_reason TEXT,\n created_at TEXT NOT NULL DEFAULT (${SQL_NOW})\n );\n `;\n}\n\nexport interface WatermarkRow {\n sourceId: number;\n sourceKey: string;\n lastOffset: number;\n prefixHash: string | null;\n lastIndexedAt: string | null;\n fileSize: number | null;\n fileMtime: string | null;\n contentHash: string | null;\n status: WatermarkStatus;\n statusReason: string | null;\n}\n\ninterface RawWatermarkRow {\n source_id: number;\n source_key: string;\n last_offset: number;\n prefix_hash: string | null;\n last_indexed_at: string | null;\n file_size: number | null;\n file_mtime: string | null;\n content_hash: string | null;\n status: WatermarkStatus;\n status_reason: string | null;\n}\n\nfunction toRow(raw: RawWatermarkRow): WatermarkRow {\n return {\n sourceId: raw.source_id,\n sourceKey: raw.source_key,\n lastOffset: raw.last_offset,\n prefixHash: raw.prefix_hash,\n lastIndexedAt: raw.last_indexed_at,\n fileSize: raw.file_size,\n fileMtime: raw.file_mtime,\n contentHash: raw.content_hash,\n status: raw.status,\n statusReason: raw.status_reason,\n };\n}\n\nexport interface WatermarkAdvance {\n lastOffset: number;\n prefixHash?: string | null;\n fileSize?: number | null;\n fileMtime?: string | null;\n /** Null keeps any previously stored hash; computing it is O(file) and optional. */\n contentHash?: string | null;\n}\n\nexport class WatermarkTable {\n private readonly ensureStmt;\n private readonly getStmt;\n private readonly advanceStmt;\n private readonly statusStmt;\n private readonly rewindStmt;\n private readonly listStmt;\n\n constructor(db: Db, { name = \"watermark\" }: WatermarkOptions = {}) {\n const t = quoteIdent(name);\n this.ensureStmt = db.prepare(`INSERT INTO ${t} (source_key) VALUES (?) ON CONFLICT (source_key) DO NOTHING`);\n this.getStmt = db.prepare(`SELECT * FROM ${t} WHERE source_key = ?`);\n this.advanceStmt = db.prepare(\n `UPDATE ${t} SET last_offset = @lastOffset, prefix_hash = @prefixHash, last_indexed_at = ${SQL_NOW},\n file_size = @fileSize, file_mtime = @fileMtime, content_hash = COALESCE(@contentHash, content_hash),\n status = 'ok', status_reason = NULL\n WHERE source_key = @sourceKey`,\n );\n this.statusStmt = db.prepare(`UPDATE ${t} SET status = ?, status_reason = ? WHERE source_key = ?`);\n this.rewindStmt = db.prepare(`UPDATE ${t} SET last_offset = 0, prefix_hash = NULL WHERE source_key = ?`);\n this.listStmt = db.prepare(`SELECT * FROM ${t} ORDER BY source_id`);\n }\n\n /** The row for a source, created at offset 0 on first sight. The id is stable for the table's life. */\n ensure(sourceKey: string): WatermarkRow {\n this.ensureStmt.run(sourceKey);\n return toRow(this.getStmt.get(sourceKey) as RawWatermarkRow);\n }\n\n get(sourceKey: string): WatermarkRow | undefined {\n const raw = this.getStmt.get(sourceKey) as RawWatermarkRow | undefined;\n return raw === undefined ? undefined : toRow(raw);\n }\n\n advance(sourceKey: string, update: WatermarkAdvance): void {\n this.advanceStmt.run({\n sourceKey,\n lastOffset: update.lastOffset,\n prefixHash: update.prefixHash ?? null,\n fileSize: update.fileSize ?? null,\n fileMtime: update.fileMtime ?? null,\n contentHash: update.contentHash ?? null,\n });\n }\n\n /** Back to byte 0, for a source that was rewritten rather than appended to. */\n rewind(sourceKey: string): void {\n this.rewindStmt.run(sourceKey);\n }\n\n markStatus(sourceKey: string, status: WatermarkStatus, reason: string | null = null): void {\n this.statusStmt.run(status, reason, sourceKey);\n }\n\n list(): WatermarkRow[] {\n return (this.listStmt.all() as RawWatermarkRow[]).map(toRow);\n }\n}\n","import type { Migration } from \"./migrations.js\";\nimport { cacheBlobTableDdl } from \"./tables/cache-blob.js\";\nimport { edgeTableDdl } from \"./tables/edge.js\";\nimport { entitySnapTableDdl, entityTableDdl, snapshotTableDdl } from \"./tables/entity.js\";\nimport { spanFtsTablesDdl } from \"./tables/span-fts.js\";\nimport { watermarkTableDdl } from \"./tables/watermark.js\";\n\n/** Which kit tables a store wants, by name override or `true` for the default name. */\nexport interface KitSelection {\n snapshot?: string | true;\n entitySnap?: string | true;\n entity?: string | true;\n edge?: string | true;\n cacheBlob?: string | true;\n spanFts?: string | true;\n watermark?: string | true;\n}\n\ntype Factory = (options: { name?: string }) => string;\n\nconst FACTORIES: [keyof KitSelection, Factory][] = [\n [\"snapshot\", snapshotTableDdl],\n [\"entitySnap\", entitySnapTableDdl],\n [\"entity\", entityTableDdl],\n [\"edge\", edgeTableDdl],\n [\"cacheBlob\", cacheBlobTableDdl],\n [\"spanFts\", spanFtsTablesDdl],\n [\"watermark\", watermarkTableDdl],\n];\n\n/** The DDL for a selection of kit tables, in dependency-safe order. Every statement is `IF NOT EXISTS`. */\nexport function kitDdl(selection: KitSelection): string {\n return FACTORIES.filter(([key]) => selection[key] !== undefined)\n .map(([key, factory]) => factory(selection[key] === true ? {} : { name: selection[key] as string }))\n .join(\"\\n\");\n}\n\n/** A migration that installs the selected kit tables, for use as a store's version 1. */\nexport function kitMigration(version: number, selection: KitSelection, name = \"kit tables\"): Migration {\n return { version, name, up: (db) => db.exec(kitDdl(selection)) };\n}\n"],"mappings":";AAAA,OAAO,cAAc;AACrB,SAAS,iBAAiB;AAC1B,OAAO,UAAU;AAaV,SAAS,aAAa,QAAgB,UAAuB,CAAC,GAAO;AAC1E,QAAM,WAAW,WAAW;AAC5B,MAAI,CAAC,YAAY,CAAC,QAAQ,SAAU,WAAU,KAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACvF,QAAM,KAAK,IAAI,SAAS,QAAQ,EAAE,UAAU,QAAQ,YAAY,MAAM,CAAC;AACvE,MAAI,CAAC,YAAY,CAAC,QAAQ,aAAa,QAAQ,OAAO,MAAO,IAAG,OAAO,oBAAoB;AAC3F,MAAI,QAAQ,eAAe,KAAM,IAAG,OAAO,mBAAmB;AAC9D,SAAO;AACT;AAGO,SAAS,SAAiB;AAC/B,UAAO,oBAAI,KAAK,GAAE,YAAY;AAChC;AAGO,IAAM,UAAU;AAEhB,SAAS,SAAS,IAAQ,OAAwB;AACvD,QAAM,MAAM,GAAG,QAAQ,yEAAyE,EAAE,IAAI,KAAK;AAC3G,SAAO,QAAQ;AACjB;AAEO,SAAS,UAAU,IAAQ,OAAe,QAAyB;AACxE,QAAM,OAAO,GAAG,QAAQ,qBAAqB,WAAW,KAAK,CAAC,GAAG,EAAE,IAAI;AACvE,SAAO,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC3C;AAGO,SAAS,WAAW,MAAsB;AAC/C,MAAI,CAAC,2BAA2B,KAAK,IAAI,EAAG,OAAM,IAAI,MAAM,2BAA2B,IAAI,EAAE;AAC7F,SAAO,IAAI,IAAI;AACjB;;;ACpCA,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAOjB,SAAS,gBAAgB,IAAkB;AAChD,KAAG,KAAK,eAAe;AACvB,QAAM,OAAO,GAAG,QAAQ,iDAAiD,EAAE,IAAI;AAC/E,SAAO,KAAK,IAAI,CAAC,MAAM,EAAE,OAAO;AAClC;AAOO,SAAS,cAAc,IAAQ,YAA4C;AAChF,mBAAiB,UAAU;AAC3B,QAAM,UAAU,IAAI,IAAI,gBAAgB,EAAE,CAAC;AAC3C,QAAM,SAAS,GAAG,QAAQ,qEAAqE;AAC/F,QAAM,WAAW,GAAG,YAAY,CAAC,MAAiB;AAChD,MAAE,GAAG,EAAE;AACP,WAAO,IAAI,EAAE,SAAS,EAAE,QAAQ,MAAM,OAAO,CAAC;AAAA,EAChD,CAAC;AACD,QAAM,OAAiB,CAAC;AACxB,aAAW,KAAK,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG;AACrE,QAAI,QAAQ,IAAI,EAAE,OAAO,EAAG;AAC5B,aAAS,CAAC;AACV,SAAK,KAAK,EAAE,OAAO;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,YAAwC;AAChE,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,KAAK,YAAY;AAC1B,QAAI,CAAC,OAAO,UAAU,EAAE,OAAO,KAAK,EAAE,WAAW,EAAG,OAAM,IAAI,MAAM,iDAAiD,EAAE,OAAO,EAAE;AAChI,QAAI,KAAK,IAAI,EAAE,OAAO,EAAG,OAAM,IAAI,MAAM,gCAAgC,EAAE,OAAO,EAAE;AACpF,SAAK,IAAI,EAAE,OAAO;AAAA,EACpB;AACF;;;ACxCA,IAAM,OAAO;AAEN,SAAS,IAAI,MAAc,IAAoB;AACpD,MAAI,CAAC,KAAK,KAAK,IAAI,EAAG,OAAM,IAAI,MAAM,qBAAqB,KAAK,UAAU,IAAI,CAAC,EAAE;AACjF,MAAI,GAAG,WAAW,EAAG,OAAM,IAAI,MAAM,0BAA0B;AAC/D,SAAO,GAAG,IAAI,IAAI,EAAE;AACtB;AAEO,SAAS,SAAS,OAA0B;AACjD,QAAM,KAAK,MAAM,QAAQ,GAAG;AAC5B,MAAI,MAAM,KAAK,OAAO,MAAM,SAAS,KAAK,CAAC,KAAK,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG;AACxE,UAAM,IAAI,MAAM,gBAAgB,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,EACzD;AACA,SAAO,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,MAAM,MAAM,KAAK,CAAC,EAAE;AAC7D;AAEO,SAAS,MAAM,OAAiC;AACrD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACF,aAAS,KAAK;AACd,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,QAAQ,MAAsC;AAC5D,MAAI,MAAM,OAAO;AACjB,SAAO,CAAC,OAAO,IAAI,MAAM,EAAE;AAC7B;;;AC1CA,SAAS,kBAAkB;AAapB,SAAS,kBAAkB,EAAE,OAAO,aAAa,IAAsB,CAAC,GAAW;AACxF,QAAM,IAAI,WAAW,IAAI;AACzB,SAAO;AAAA,iCACwB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4CAMU,OAAO;AAAA;AAAA;AAAA;AAInD;AAEO,SAAS,cAAc,MAAsB;AAClD,SAAO,WAAW,QAAQ,EAAE,OAAO,MAAM,MAAM,EAAE,OAAO,KAAK;AAC/D;AAeA,SAAS,QAAQ,KAAuB;AACtC,MAAI,IAAI,gBAAgB,OAAW,QAAO,IAAI;AAC9C,MAAI,IAAI,SAAS,OAAW,QAAO,cAAc,IAAI,IAAI;AACzD,QAAM,IAAI,MAAM,qCAAqC;AACvD;AAEO,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,IAAQ,EAAE,OAAO,aAAa,IAAsB,CAAC,GAAG;AAClE,UAAM,IAAI,WAAW,IAAI;AACzB,SAAK,UAAU,GAAG,QAAQ,2BAA2B,CAAC,yDAAyD;AAC/G,SAAK,UAAU,GAAG;AAAA,MAChB,eAAe,CAAC;AAAA;AAAA,IAElB;AACA,SAAK,YAAY,GAAG,QAAQ,6BAA6B,CAAC,oCAAoC;AAAA,EAChG;AAAA,EAEA,IAAI,KAAqC;AACvC,UAAM,MAAM,KAAK,QAAQ,IAAI,IAAI,WAAW,IAAI,OAAO,QAAQ,GAAG,CAAC;AACnE,QAAI,QAAQ,OAAW,QAAO;AAC9B,WAAO,EAAE,OAAO,IAAI,OAAO,MAAM,IAAI,SAAS,OAAO,OAAQ,KAAK,MAAM,IAAI,IAAI,EAA8B;AAAA,EAChH;AAAA;AAAA,EAGA,IAAI,KAAe,OAAwB,MAAyC;AAClF,UAAM,QAAQ,OAAO,UAAU,WAAW,OAAO,KAAK,OAAO,MAAM,IAAI;AACvE,UAAM,OAAO,KAAK,QAAQ,IAAI,IAAI,WAAW,IAAI,OAAO,QAAQ,GAAG,GAAG,OAAO,SAAS,SAAY,OAAO,KAAK,UAAU,IAAI,CAAC;AAC7H,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA,EAGA,aAAa,KAAe,SAAgC,MAAwC;AAClG,UAAM,MAAM,KAAK,IAAI,GAAG;AACxB,QAAI,IAAK,QAAO,IAAI;AACpB,UAAM,QAAQ,QAAQ;AACtB,SAAK,IAAI,KAAK,OAAO,IAAI;AACzB,WAAO,OAAO,UAAU,WAAW,OAAO,KAAK,OAAO,MAAM,IAAI;AAAA,EAClE;AAAA,EAEA,MAAM,WAAmB,OAAuB;AAC9C,WAAQ,KAAK,UAAU,IAAI,WAAW,KAAK,EAAoB;AAAA,EACjE;AACF;;;AC9EO,SAAS,aAAa,EAAE,OAAO,OAAO,IAAsB,CAAC,GAAW;AAC7E,QAAM,IAAI,WAAW,IAAI;AACzB,SAAO;AAAA,iCACwB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0CAOQ,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iCAMhB,WAAW,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC;AAAA,iCACxC,WAAW,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC;AAAA,wCACjC,WAAW,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC;AAAA;AAEjF;AAyCA,SAAS,UAAU,KAA0B;AAC3C,SAAO;AAAA,IACL,QAAQ,IAAI;AAAA,IACZ,WAAW,IAAI;AAAA,IACf,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,QAAQ,IAAI;AAAA,IACZ,UAAU,IAAI;AAAA,IACd,UAAU,IAAI;AAAA,IACd,UAAU,IAAI;AAAA,IACd,QAAQ,IAAI;AAAA,IACZ,YAAY,IAAI;AAAA,IAChB,OAAO,IAAI,UAAU,OAAO,OAAQ,KAAK,MAAM,IAAI,KAAK;AAAA,EAC1D;AACF;AAGO,IAAM,YAAN,MAAgB;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,IAAQ,EAAE,OAAO,OAAO,IAAsB,CAAC,GAAG;AAC5D,UAAM,IAAI,WAAW,IAAI;AACzB,SAAK,aAAa,GAAG;AAAA,MACnB,eAAe,CAAC;AAAA;AAAA;AAAA,IAGlB;AACA,SAAK,aAAa,GAAG;AAAA,MACnB,UAAU,CAAC;AAAA,IACb;AACA,SAAK,cAAc,GAAG;AAAA,MACpB,iBAAiB,CAAC;AAAA,IACpB;AACA,SAAK,WAAW,GAAG,QAAQ,iBAAiB,CAAC,8DAA8D;AAC3G,SAAK,SAAS,GAAG,QAAQ,iBAAiB,CAAC,8DAA8D;AAAA,EAC3G;AAAA;AAAA,EAGA,OAAO,MAA0B;AAC/B,UAAM,OAAO,KAAK,WAAW,IAAI;AAAA,MAC/B,WAAW,KAAK;AAAA,MAChB,UAAU,KAAK;AAAA,MACf,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,UAAU,OAAO;AAAA,MAC9B,UAAU,KAAK,YAAY;AAAA,MAC3B,QAAQ,KAAK,UAAU;AAAA,MACvB,YAAY,KAAK,cAAc;AAAA,MAC/B,OAAO,KAAK,SAAS,OAAO,OAAO,KAAK,UAAU,KAAK,KAAK;AAAA,IAC9D,CAAC;AACD,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA,EAGA,OAAO,WAAmB,UAAkB,WAAmB,KAAa,OAAO,GAAY;AAC7F,WAAO,KAAK,WAAW,IAAI,IAAI,WAAW,UAAU,SAAS,EAAE,YAAY;AAAA,EAC7E;AAAA;AAAA,EAGA,UAAU,MAAiB,KAAa,OAAO,GAAS;AACtD,SAAK,OAAO,KAAK,WAAW,KAAK,UAAU,KAAK,WAAW,EAAE;AAC7D,SAAK,OAAO,IAAI;AAAA,EAClB;AAAA,EAEA,QAAQ,WAAmB,UAAkB,WAAwC;AACnF,UAAM,MAAM,KAAK,YAAY,IAAI,WAAW,UAAU,SAAS;AAC/D,WAAO,QAAQ,SAAY,SAAY,UAAU,GAAG;AAAA,EACtD;AAAA,EAEA,KAAK,WAA8B;AACjC,WAAQ,KAAK,SAAS,IAAI,SAAS,EAAmB,IAAI,SAAS;AAAA,EACrE;AAAA,EAEA,GAAG,WAA8B;AAC/B,WAAQ,KAAK,OAAO,IAAI,SAAS,EAAmB,IAAI,SAAS;AAAA,EACnE;AACF;;;AC5IO,SAAS,mBAAmB,EAAE,OAAO,cAAc,IAAe,CAAC,GAAW;AACnF,QAAM,IAAI,WAAW,IAAI;AACzB,SAAO;AAAA,iCACwB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iCASD,WAAW,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;AAAA,iCACtC,WAAW,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC;AAAA;AAEzE;AAGO,SAAS,iBAAiB,EAAE,OAAO,WAAW,IAAe,CAAC,GAAW;AAC9E,QAAM,IAAI,WAAW,IAAI;AACzB,SAAO;AAAA,iCACwB,CAAC;AAAA;AAAA;AAAA,wCAGM,OAAO;AAAA;AAAA;AAAA,iCAGd,WAAW,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC;AAAA;AAEtE;AAGO,SAAS,eAAe,EAAE,OAAO,SAAS,IAAe,CAAC,GAAW;AAC1E,QAAM,IAAI,WAAW,IAAI;AACzB,SAAO;AAAA,iCACwB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0CAQQ,OAAO;AAAA;AAAA;AAAA,iCAGhB,WAAW,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;AAAA,iCACtC,WAAW,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC;AAAA;AAEzE;AAmCA,SAAS,YAAY,KAA8B;AACjD,SAAO;AAAA,IACL,KAAK,IAAI;AAAA,IACT,MAAM,IAAI;AAAA,IACV,MAAM,IAAI;AAAA,IACV,WAAW,IAAI;AAAA,IACf,OAAO,IAAI,UAAU,OAAO,OAAQ,KAAK,MAAM,IAAI,KAAK;AAAA,IACxD,QAAQ,IAAI;AAAA,IACZ,UAAU,IAAI;AAAA,IACd,UAAU,IAAI;AAAA,IACd,UAAU,IAAI;AAAA,EAChB;AACF;AAGO,IAAM,cAAN,MAAkB;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,IAAQ,EAAE,OAAO,SAAS,IAAe,CAAC,GAAG;AACvD,UAAM,IAAI,WAAW,IAAI;AACzB,SAAK,aAAa,GAAG;AAAA,MACnB,eAAe,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlB;AACA,SAAK,UAAU,GAAG,QAAQ,iBAAiB,CAAC,gBAAgB;AAC5D,SAAK,aAAa,GAAG,QAAQ,UAAU,CAAC,wDAAwD;AAChG,SAAK,aAAa,GAAG,QAAQ,iBAAiB,CAAC,oDAAoD;AAAA,EACrG;AAAA;AAAA,EAGA,OAAO,QAA2B;AAChC,SAAK,WAAW,IAAI;AAAA,MAClB,KAAK,OAAO;AAAA,MACZ,MAAM,OAAO;AAAA,MACb,MAAM,OAAO,QAAQ;AAAA,MACrB,WAAW,OAAO,aAAa;AAAA,MAC/B,OAAO,OAAO,SAAS,OAAO,OAAO,KAAK,UAAU,OAAO,KAAK;AAAA,MAChE,QAAQ,OAAO,UAAU,OAAO;AAAA,IAClC,CAAC;AAAA,EACH;AAAA,EAEA,IAAIA,MAAoC;AACtC,UAAM,MAAM,KAAK,QAAQ,IAAIA,IAAG;AAChC,WAAO,QAAQ,SAAY,SAAY,YAAY,GAAG;AAAA,EACxD;AAAA,EAEA,OAAOA,MAAa,KAAa,OAAO,GAAY;AAClD,WAAO,KAAK,WAAW,IAAI,IAAIA,IAAG,EAAE,YAAY;AAAA,EAClD;AAAA,EAEA,WAAW,MAA2B;AACpC,WAAQ,KAAK,WAAW,IAAI,IAAI,EAAqB,IAAI,WAAW;AAAA,EACtE;AACF;;;AC7IA,SAAS,MAAM,MAA6C;AAC1D,SAAO,EAAE,MAAM,WAAW,GAAG,IAAI,OAAO,GAAG,KAAK,WAAW,GAAG,IAAI,MAAM,EAAE;AAC5E;AAEO,SAAS,iBAAiB,EAAE,OAAO,SAAS,IAAoB,CAAC,GAAW;AACjF,QAAM,EAAE,MAAM,IAAI,IAAI,MAAM,IAAI;AAChC,SAAO;AAAA,iCACwB,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iCASJ,WAAW,OAAO,IAAI,aAAa,CAAC,OAAO,IAAI;AAAA,yCACvC,GAAG;AAAA;AAE5C;AAoDA,SAAS,iBAAiB,QAAwB;AAChD,QAAM,OAAO,OAAO,WAAW,OAAO,SAAS,CAAC;AAChD,SAAO,OAAO,MAAM,GAAG,EAAE,IAAI,OAAO,aAAa,OAAO,CAAC;AAC3D;AAGA,SAAS,UAAU,OAA2B;AAC5C,SAAO,MAAM,gBAAgB,UAAa,MAAM,YAAY,SAAS;AACvE;AAGA,SAAS,YAAY,OAAuC;AAC1D,QAAM,SAA8B,CAAC;AACrC,MAAI,UAAU,KAAK,EAAG,QAAO,KAAK,MAAM,aAAc,iBAAiB,MAAM,WAAY,CAAC;AAC1F,MAAI,MAAM,WAAW,OAAW,QAAO,KAAK,GAAG,MAAM,MAAM;AAC3D,SAAO;AACT;AAEO,IAAM,gBAAN,MAAoB;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,oBAAoB,oBAAI,IAAuC;AAAA,EAC/D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,IAAQ,EAAE,OAAO,SAAS,IAAoB,CAAC,GAAG;AAC5D,UAAM,EAAE,MAAM,IAAI,IAAI,MAAM,IAAI;AAChC,SAAK,KAAK;AACV,SAAK,aAAa,EAAE,MAAM,IAAI;AAC9B,SAAK,aAAa,GAAG;AAAA,MACnB,eAAe,IAAI;AAAA;AAAA,IAErB;AACA,SAAK,WAAW,GAAG;AAAA,MACjB,uBAAuB,IAAI;AAAA,IAC7B;AACA,SAAK,YAAY,GAAG,QAAQ,eAAe,GAAG,8BAA8B;AAC5E,SAAK,aAAa,GAAG;AAAA,MACnB;AAAA,cACQ,GAAG,WAAW,IAAI;AAAA,eACjB,GAAG;AAAA,IACd;AACA,SAAK,eAAe,GAAG,QAAQ,eAAe,GAAG,IAAI,GAAG,yBAAyB;AACjF,SAAK,kBAAkB,GAAG,QAAQ,eAAe,IAAI,sBAAsB;AAC3E,SAAK,aAAa,GAAG,QAAQ,6BAA6B,IAAI,EAAE;AAChE,SAAK,WAAW,GAAG,QAAQ,6BAA6B,GAAG,EAAE;AAAA,EAC/D;AAAA;AAAA,EAGA,MAAM,MAAiB,MAAsB;AAC3C,UAAM,WAAW,KAAK,WAAW,IAAI,KAAK,UAAU,KAAK,OAAO,KAAK,UAAU,KAAK,YAAY,KAAK,UAAU;AAC/G,UAAM,SAAU,KAAK,SAAS,IAAI,KAAK,UAAU,KAAK,OAAO,KAAK,UAAU,KAAK,UAAU,EAA0B;AACrH,QAAI,SAAS,YAAY,EAAG,MAAK,UAAU,IAAI,QAAQ,IAAI;AAC3D,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,OAAe,QAAQ,IAAI,OAA8B;AAC9D,UAAM,OACJ,QAAQ,KAAK,gBAAgB,KAAK,EAAE,IAAI,CAAC,OAAO,GAAG,YAAY,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,KAAK;AAEnH,WAAO,KAAK,IAAI,CAAC,OAAO;AAAA,MACtB,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE;AAAA,MACZ,OAAO,EAAE;AAAA,MACT,UAAU,EAAE;AAAA,MACZ,YAAY,EAAE;AAAA,MACd,YAAY,EAAE;AAAA,MACd,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,gBAAgB,OAA6C;AACnE,UAAM,aAAa,MAAM,QAAQ,UAAU;AAC3C,UAAM,MAAM,GAAG,UAAU,KAAK,IAAI,IAAI,CAAC,IAAI,MAAM,WAAW,SAAY,QAAQ,UAAU;AAC1F,UAAM,SAAS,KAAK,kBAAkB,IAAI,GAAG;AAC7C,QAAI,OAAQ,QAAO;AAEnB,UAAM,EAAE,MAAM,IAAI,IAAI,KAAK;AAC3B,UAAM,aAAa,CAAC,GAAG,GAAG,UAAU;AAEpC,QAAI,UAAU,KAAK,EAAG,YAAW,KAAK,sCAAsC;AAC5E,QAAI,MAAM,WAAW,QAAW;AAC9B,iBAAW,KAAK,eAAe,IAAI,MAAM,eAAe,IAAI,MAAM,UAAU,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI,CAAC,GAAG;AAAA,IACvG;AACA,UAAM,YAAY,KAAK,GAAG;AAAA,MACxB;AAAA,cACQ,GAAG,WAAW,IAAI;AAAA,eACjB,WAAW,KAAK,OAAO,CAAC;AAAA,IACnC;AACA,SAAK,kBAAkB,IAAI,KAAK,SAAS;AACzC,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,WAAW,UAA0B;AACnC,WAAO,KAAK,gBAAgB,IAAI,QAAQ,EAAE;AAAA,EAC5C;AAAA;AAAA,EAGA,cAAsB;AACpB,UAAM,MAAO,KAAK,SAAS,IAAI,EAAoB;AACnD,QAAI,QAAQ,EAAG,QAAO;AACtB,UAAM,QAAS,KAAK,WAAW,IAAI,EAAoB;AACvD,WAAO,KAAK,IAAI,GAAG,MAAM,KAAK,IAAI;AAAA,EACpC;AAAA;AAAA,EAGA,aAAmB;AACjB,SAAK,aAAa,IAAI;AAAA,EACxB;AACF;;;AC5MO,SAAS,kBAAkB,EAAE,OAAO,YAAY,IAAsB,CAAC,GAAW;AACvF,QAAM,IAAI,WAAW,IAAI;AACzB,SAAO;AAAA,iCACwB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAWa,OAAO;AAAA;AAAA;AAGtD;AA4BA,SAAS,MAAM,KAAoC;AACjD,SAAO;AAAA,IACL,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,YAAY,IAAI;AAAA,IAChB,YAAY,IAAI;AAAA,IAChB,eAAe,IAAI;AAAA,IACnB,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,cAAc,IAAI;AAAA,EACpB;AACF;AAWO,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,IAAQ,EAAE,OAAO,YAAY,IAAsB,CAAC,GAAG;AACjE,UAAM,IAAI,WAAW,IAAI;AACzB,SAAK,aAAa,GAAG,QAAQ,eAAe,CAAC,8DAA8D;AAC3G,SAAK,UAAU,GAAG,QAAQ,iBAAiB,CAAC,uBAAuB;AACnE,SAAK,cAAc,GAAG;AAAA,MACpB,UAAU,CAAC,gFAAgF,OAAO;AAAA;AAAA;AAAA;AAAA,IAIpG;AACA,SAAK,aAAa,GAAG,QAAQ,UAAU,CAAC,yDAAyD;AACjG,SAAK,aAAa,GAAG,QAAQ,UAAU,CAAC,+DAA+D;AACvG,SAAK,WAAW,GAAG,QAAQ,iBAAiB,CAAC,qBAAqB;AAAA,EACpE;AAAA;AAAA,EAGA,OAAO,WAAiC;AACtC,SAAK,WAAW,IAAI,SAAS;AAC7B,WAAO,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAoB;AAAA,EAC7D;AAAA,EAEA,IAAI,WAA6C;AAC/C,UAAM,MAAM,KAAK,QAAQ,IAAI,SAAS;AACtC,WAAO,QAAQ,SAAY,SAAY,MAAM,GAAG;AAAA,EAClD;AAAA,EAEA,QAAQ,WAAmB,QAAgC;AACzD,SAAK,YAAY,IAAI;AAAA,MACnB;AAAA,MACA,YAAY,OAAO;AAAA,MACnB,YAAY,OAAO,cAAc;AAAA,MACjC,UAAU,OAAO,YAAY;AAAA,MAC7B,WAAW,OAAO,aAAa;AAAA,MAC/B,aAAa,OAAO,eAAe;AAAA,IACrC,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,OAAO,WAAyB;AAC9B,SAAK,WAAW,IAAI,SAAS;AAAA,EAC/B;AAAA,EAEA,WAAW,WAAmB,QAAyB,SAAwB,MAAY;AACzF,SAAK,WAAW,IAAI,QAAQ,QAAQ,SAAS;AAAA,EAC/C;AAAA,EAEA,OAAuB;AACrB,WAAQ,KAAK,SAAS,IAAI,EAAwB,IAAI,KAAK;AAAA,EAC7D;AACF;;;ACxHA,IAAM,YAA6C;AAAA,EACjD,CAAC,YAAY,gBAAgB;AAAA,EAC7B,CAAC,cAAc,kBAAkB;AAAA,EACjC,CAAC,UAAU,cAAc;AAAA,EACzB,CAAC,QAAQ,YAAY;AAAA,EACrB,CAAC,aAAa,iBAAiB;AAAA,EAC/B,CAAC,WAAW,gBAAgB;AAAA,EAC5B,CAAC,aAAa,iBAAiB;AACjC;AAGO,SAAS,OAAO,WAAiC;AACtD,SAAO,UAAU,OAAO,CAAC,CAAC,GAAG,MAAM,UAAU,GAAG,MAAM,MAAS,EAC5D,IAAI,CAAC,CAAC,KAAK,OAAO,MAAM,QAAQ,UAAU,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,MAAM,UAAU,GAAG,EAAY,CAAC,CAAC,EAClG,KAAK,IAAI;AACd;AAGO,SAAS,aAAa,SAAiB,WAAyB,OAAO,cAAyB;AACrG,SAAO,EAAE,SAAS,MAAM,IAAI,CAAC,OAAO,GAAG,KAAK,OAAO,SAAS,CAAC,EAAE;AACjE;","names":["ref"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@titan-design/store-sqlite",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "SQLite table-factory kit: entity/edge (bi-temporal), content-addressed cache, contentless FTS5, watermark, migrations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",