@haverstack/record-adapter-do-sqlite 0.8.0 → 0.10.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
@@ -110,7 +110,7 @@ declare class DoSQLiteRecordAdapter implements StackRecordAdapter {
110
110
  snapshot?: RecordVersion;
111
111
  } & ActorOptions): Promise<StackRecord>;
112
112
  queryRecords(query: StackQuery): Promise<QueryResult>;
113
- deleteUnreferencedAttachmentRecords(fileId: FileId, metadataTypeId: TypeId): Promise<StackRecord[]>;
113
+ deleteUnreferencedAttachmentRecords(fileId: FileId, metadataTypeIds: TypeId[]): Promise<StackRecord[]>;
114
114
  getVersions(id: string): Promise<RecordVersion[]>;
115
115
  getVersion(id: string, version: number): Promise<RecordVersion | null>;
116
116
  saveVersion(id: string, version: RecordVersion): Promise<void>;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StackVersionConflictError, StackNotFoundError, StackConflictError, applyMergePatch, StackQueryError } from '@haverstack/core';
2
- import { parseContentFilterKey } from '@haverstack/core/adapter';
2
+ import { contentSortEntry, parseContentFilterKey, contentSortKey } from '@haverstack/core/adapter';
3
3
 
4
4
  // ../sqlite-shared/dist/schema.js
5
5
  var RECORD_SCHEMA_SQL = `
@@ -73,6 +73,22 @@ var RECORD_SCHEMA_SQL = `
73
73
  PRIMARY KEY (record_id, field)
74
74
  ) STRICT;
75
75
 
76
+ -- One row per top-level scalar content field on a record, rewritten
77
+ -- beside file_refs on every content/typeId write. Exactly one of
78
+ -- num_value / text_key+text_value is set, by the field's declared kind:
79
+ -- what a field orders as must not vary with the value a given record
80
+ -- happens to hold. text_key is the folded form ordering compares
81
+ -- (@haverstack/core's contentSortKey), text_value the stored one, which
82
+ -- breaks a tie between two values that fold together.
83
+ CREATE TABLE IF NOT EXISTS content_sort (
84
+ record_id TEXT NOT NULL REFERENCES records(id),
85
+ field TEXT NOT NULL,
86
+ num_value REAL,
87
+ text_key TEXT,
88
+ text_value TEXT,
89
+ PRIMARY KEY (record_id, field)
90
+ ) STRICT;
91
+
76
92
  -- Indexes
77
93
  CREATE INDEX IF NOT EXISTS idx_records_type_id ON records(type_id);
78
94
  CREATE INDEX IF NOT EXISTS idx_records_parent_id ON records(parent_id);
@@ -89,6 +105,8 @@ var RECORD_SCHEMA_SQL = `
89
105
  CREATE INDEX IF NOT EXISTS idx_assoc_related ON associations(related_scope, related_ns, related_id);
90
106
  CREATE INDEX IF NOT EXISTS idx_types_base_id ON types(base_id);
91
107
  CREATE INDEX IF NOT EXISTS idx_file_refs_file_id ON file_refs(file_id);
108
+ CREATE INDEX IF NOT EXISTS idx_content_sort_num ON content_sort(field, num_value, record_id);
109
+ CREATE INDEX IF NOT EXISTS idx_content_sort_text ON content_sort(field, text_key, text_value, record_id);
92
110
  `;
93
111
  var FTS5_SCHEMA_SQL = `
94
112
  CREATE VIRTUAL TABLE IF NOT EXISTS records_fts USING fts5(
@@ -99,33 +117,69 @@ var FTS5_SCHEMA_SQL = `
99
117
  `;
100
118
  var PRAGMA_FOREIGN_KEYS_ON = `PRAGMA foreign_keys = ON;`;
101
119
  var SORT_FIELDS = ["createdAt", "updatedAt", "version"];
102
- var encodeCursor = (field, value, id) => btoa(`${field}|${value}|${id}`);
120
+ var toBase64 = (text) => {
121
+ const bytes = new TextEncoder().encode(text);
122
+ let binary = "";
123
+ for (const byte of bytes)
124
+ binary += String.fromCharCode(byte);
125
+ return btoa(binary);
126
+ };
127
+ var fromBase64 = (cursor) => {
128
+ const binary = atob(cursor);
129
+ const bytes = new Uint8Array(binary.length);
130
+ for (let i = 0; i < binary.length; i++)
131
+ bytes[i] = binary.charCodeAt(i);
132
+ return new TextDecoder().decode(bytes);
133
+ };
134
+ var encodeCursor = (decoded) => toBase64(JSON.stringify(decoded.kind === "absent" ? { k: decoded.kind, f: decoded.field, i: decoded.id } : { k: decoded.kind, f: decoded.field, i: decoded.id, v: decoded.value }));
103
135
  var decodeCursor = (cursor) => {
104
- let decoded;
136
+ const malformed = () => new StackQueryError(`Invalid cursor: malformed "${cursor}"`);
137
+ let parsed;
105
138
  try {
106
- decoded = atob(cursor);
139
+ parsed = JSON.parse(fromBase64(cursor));
107
140
  } catch {
108
- throw new StackQueryError(`Invalid cursor: malformed "${cursor}"`);
109
- }
110
- const parts = decoded.split("|");
111
- if (parts.length !== 3) {
112
- throw new StackQueryError(`Invalid cursor: malformed "${cursor}"`);
113
- }
114
- const [field, value, id] = parts;
115
- if (!SORT_FIELDS.includes(field)) {
116
- throw new StackQueryError(`Invalid cursor: unknown sort field "${field}"`);
117
- }
118
- const numericValue = Number(value);
119
- if (!isFinite(numericValue)) {
120
- throw new StackQueryError(`Invalid cursor: non-numeric sort value`);
141
+ throw malformed();
142
+ }
143
+ if (!parsed || typeof parsed !== "object")
144
+ throw malformed();
145
+ const { k, f, i, v } = parsed;
146
+ if (typeof f !== "string" || !f || typeof i !== "string")
147
+ throw malformed();
148
+ if (k === "absent")
149
+ return { kind: "absent", field: f, id: i };
150
+ if (k === "text") {
151
+ if (typeof v !== "string")
152
+ throw malformed();
153
+ return { kind: "text", field: f, value: v, id: i };
154
+ }
155
+ if (k === "num" || k === "native") {
156
+ if (typeof v !== "number" || !isFinite(v)) {
157
+ throw new StackQueryError(`Invalid cursor: non-numeric sort value`);
158
+ }
159
+ if (k === "native") {
160
+ if (!SORT_FIELDS.includes(f)) {
161
+ throw new StackQueryError(`Invalid cursor: unknown sort field "${f}"`);
162
+ }
163
+ return { kind: "native", field: f, value: v, id: i };
164
+ }
165
+ return { kind: "num", field: f, value: v, id: i };
121
166
  }
122
- return { field, value: numericValue, id };
167
+ throw malformed();
123
168
  };
124
169
  var getSortField = (query) => query.sort?.field ?? "createdAt";
125
170
  var getSortColumn = (field) => field === "createdAt" ? "created_at" : field === "updatedAt" ? "updated_at" : "version";
126
- var makeCursor = (record, field) => {
127
- const value = field === "updatedAt" ? record.updatedAt.getTime() : field === "version" ? record.version : record.createdAt.getTime();
128
- return encodeCursor(field, value, record.id);
171
+ var makeCursor = (record, sort, kindOf) => {
172
+ const contentField = sort?.contentField;
173
+ if (contentField === void 0) {
174
+ const field = getSortField({ sort });
175
+ const value = field === "updatedAt" ? record.updatedAt.getTime() : field === "version" ? record.version : record.createdAt.getTime();
176
+ return encodeCursor({ kind: "native", field, value, id: record.id });
177
+ }
178
+ const kind = kindOf?.(record, contentField);
179
+ const entry = kind ? contentSortEntry(kind, record.content[contentField]) : null;
180
+ if (!entry)
181
+ return encodeCursor({ kind: "absent", field: contentField, id: record.id });
182
+ return entry.kind === "num" ? encodeCursor({ kind: "num", field: contentField, value: entry.num, id: record.id }) : encodeCursor({ kind: "text", field: contentField, value: entry.text, id: record.id });
129
183
  };
130
184
 
131
185
  // ../sqlite-shared/dist/fts5.js
@@ -257,6 +311,49 @@ var contentPathExists = (segments, leaf, params, nextAlias) => {
257
311
  return `EXISTS (SELECT 1 FROM ${from.join(", ")} WHERE ${where.join(" AND ")})`;
258
312
  };
259
313
  var equalsLeaf = (alias) => `${alias}.type NOT IN ('object', 'array') AND ${alias}.value = ?`;
314
+ var SORT_ALIAS = "cs";
315
+ var buildFromClause = (query) => {
316
+ const field = query.sort?.contentField;
317
+ if (field === void 0)
318
+ return { sql: "records r", params: [] };
319
+ return {
320
+ sql: `records r LEFT JOIN content_sort ${SORT_ALIAS} ON ${SORT_ALIAS}.record_id = r.id AND ${SORT_ALIAS}.field = ?`,
321
+ params: [field]
322
+ };
323
+ };
324
+ var assertCursorMatchesSort = (cursor, query) => {
325
+ const contentField = query.sort?.contentField;
326
+ if (cursor.kind === "native") {
327
+ const sortField = getSortField(query);
328
+ if (contentField !== void 0 || cursor.field !== sortField) {
329
+ throw new StackQueryError(`Cursor sort field "${cursor.field}" does not match query sort field "${contentField ?? sortField}"`);
330
+ }
331
+ return;
332
+ }
333
+ if (contentField === void 0 || cursor.field !== contentField) {
334
+ throw new StackQueryError(`Cursor sort field "${cursor.field}" does not match query sort field "${contentField ?? getSortField(query)}"`);
335
+ }
336
+ };
337
+ var contentCursorCondition = (cursor, op, params) => {
338
+ const ascending = op === ">";
339
+ const numRank = ascending ? 0 : 1;
340
+ const textRank = ascending ? 1 : 0;
341
+ const rank = `CASE WHEN ${SORT_ALIAS}.record_id IS NULL THEN 2 WHEN ${SORT_ALIAS}.num_value IS NULL THEN ${textRank} ELSE ${numRank} END`;
342
+ const cursorRank = cursor.kind === "absent" ? 2 : cursor.kind === "num" ? numRank : textRank;
343
+ let tail;
344
+ if (cursor.kind === "num") {
345
+ tail = `(${SORT_ALIAS}.num_value ${op} ? OR (${SORT_ALIAS}.num_value = ? AND r.id ${op} ?))`;
346
+ params.push(cursor.value, cursor.value, cursor.id);
347
+ } else if (cursor.kind === "text") {
348
+ const key = contentSortKey(cursor.value);
349
+ tail = `(${SORT_ALIAS}.text_key ${op} ? OR (${SORT_ALIAS}.text_key = ? AND (${SORT_ALIAS}.text_value ${op} ? OR (${SORT_ALIAS}.text_value = ? AND r.id ${op} ?))))`;
350
+ params.push(key, key, cursor.value, cursor.value, cursor.id);
351
+ } else {
352
+ tail = `r.id ${op} ?`;
353
+ params.push(cursor.id);
354
+ }
355
+ return `((${rank}) > ${cursorRank} OR ((${rank}) = ${cursorRank} AND ${tail}))`;
356
+ };
260
357
  var buildWhereClause = (query) => {
261
358
  const conditions = ["r.id != '_config'"];
262
359
  const params = [];
@@ -369,6 +466,13 @@ var buildWhereClause = (query) => {
369
466
  }
370
467
  }
371
468
  }
469
+ if (f.contentPresent?.length) {
470
+ let aliasSeq = 0;
471
+ const nextAlias = () => `pp${aliasSeq++}`;
472
+ for (const key of f.contentPresent) {
473
+ conditions.push(contentPathExists(parseContentFilterKey(key), (a) => `${a}.value IS NOT NULL`, params, nextAlias));
474
+ }
475
+ }
372
476
  if (f.search) {
373
477
  const sanitized = sanitizeFts5Query(f.search);
374
478
  if (sanitized) {
@@ -379,15 +483,16 @@ var buildWhereClause = (query) => {
379
483
  }
380
484
  }
381
485
  if (query.cursor) {
382
- const { field: cursorField, value: numericValue, id: cursorId } = decodeCursor(query.cursor);
383
- const sortField = getSortField(query);
384
- if (cursorField !== sortField) {
385
- throw new StackQueryError(`Cursor sort field "${cursorField}" does not match query sort field "${sortField}"`);
386
- }
387
- const col = getSortColumn(cursorField);
486
+ const cursor = decodeCursor(query.cursor);
487
+ assertCursorMatchesSort(cursor, query);
388
488
  const op = sqlDirection(query) === "ASC" ? ">" : "<";
389
- conditions.push(`(r.${col} ${op} ? OR (r.${col} = ? AND r.id ${op} ?))`);
390
- params.push(numericValue, numericValue, cursorId);
489
+ if (cursor.kind === "native") {
490
+ const col = getSortColumn(cursor.field);
491
+ conditions.push(`(r.${col} ${op} ? OR (r.${col} = ? AND r.id ${op} ?))`);
492
+ params.push(cursor.value, cursor.value, cursor.id);
493
+ } else {
494
+ conditions.push(contentCursorCondition(cursor, op, params));
495
+ }
391
496
  }
392
497
  return {
393
498
  sql: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "",
@@ -395,9 +500,11 @@ var buildWhereClause = (query) => {
395
500
  };
396
501
  };
397
502
  var buildOrderClause = (query) => {
398
- const field = getSortField(query);
399
503
  const dir = sqlDirection(query);
400
- return `ORDER BY r.${getSortColumn(field)} ${dir}, r.id ${dir}`;
504
+ if (query.sort?.contentField === void 0) {
505
+ return `ORDER BY r.${getSortColumn(getSortField(query))} ${dir}, r.id ${dir}`;
506
+ }
507
+ return `ORDER BY (${SORT_ALIAS}.record_id IS NULL) ASC, (${SORT_ALIAS}.num_value IS NULL) ${dir}, ${SORT_ALIAS}.num_value ${dir}, ${SORT_ALIAS}.text_key ${dir}, ${SORT_ALIAS}.text_value ${dir}, r.id ${dir}`;
401
508
  };
402
509
 
403
510
  // ../sqlite-shared/dist/mappers.js
@@ -534,6 +641,15 @@ var asStackQueryError = (query, run) => {
534
641
  }
535
642
  };
536
643
  var fileRefFieldNames = (schema) => Object.keys(schema).filter((field) => schema[field].kind === "file-ref");
644
+ var sortableFieldKinds = (schema) => {
645
+ const kinds = /* @__PURE__ */ new Map();
646
+ for (const [field, def] of Object.entries(schema)) {
647
+ if (!def.kind || def.kind === "array" || def.kind === "object")
648
+ continue;
649
+ kinds.set(field, def.kind);
650
+ }
651
+ return kinds;
652
+ };
537
653
  var SharedSqlRecordLogic = class {
538
654
  deps;
539
655
  /**
@@ -542,6 +658,8 @@ var SharedSqlRecordLogic = class {
542
658
  * Populated eagerly in saveType() and lazily via getFileRefFields().
543
659
  */
544
660
  fileRefFieldsByType = /* @__PURE__ */ new Map();
661
+ /** Per-typeId cache of sortable field kinds, as fileRefFieldsByType is. */
662
+ sortFieldsByType = /* @__PURE__ */ new Map();
545
663
  constructor(deps) {
546
664
  this.deps = deps;
547
665
  }
@@ -623,6 +741,7 @@ var SharedSqlRecordLogic = class {
623
741
  }
624
742
  fts5Strategy.insert(this.exec, record.id, JSON.stringify(record.content));
625
743
  this.syncFileRefs(record.id, record.typeId, record.content);
744
+ this.syncContentSort(record.id, record.typeId, record.content);
626
745
  return record;
627
746
  }
628
747
  async getRecord(id) {
@@ -668,6 +787,7 @@ var SharedSqlRecordLogic = class {
668
787
  ]);
669
788
  fts5Strategy.insert(this.exec, id, JSON.stringify(merged));
670
789
  this.syncFileRefs(id, existing.typeId, merged);
790
+ this.syncContentSort(id, existing.typeId, merged);
671
791
  });
672
792
  const updated = await this.getRecord(id);
673
793
  if (!updated)
@@ -700,8 +820,8 @@ var SharedSqlRecordLogic = class {
700
820
  return updated;
701
821
  }
702
822
  /**
703
- * Deletes a record's FTS entry, associations, versions, file-refs, and
704
- * row, returning the record as it stood — the last copy that will ever
823
+ * Deletes a record's FTS entry, associations, versions, file-refs, sort
824
+ * index rows, and row, returning the record as it stood — the last copy that will ever
705
825
  * exist, read here so that nothing can overtake it between the read and
706
826
  * the deletes. Null when there was no such record. No write() call —
707
827
  * callers batch it. expectedVersion is checked first so a lost CAS race
@@ -723,6 +843,7 @@ var SharedSqlRecordLogic = class {
723
843
  this.exec.run("DELETE FROM associations WHERE record_id = ?", [id]);
724
844
  this.exec.run("DELETE FROM versions WHERE record_id = ?", [id]);
725
845
  this.exec.run("DELETE FROM file_refs WHERE record_id = ?", [id]);
846
+ this.exec.run("DELETE FROM content_sort WHERE record_id = ?", [id]);
726
847
  this.exec.run("DELETE FROM records WHERE id = ?", [id]);
727
848
  return purged;
728
849
  }
@@ -810,6 +931,7 @@ var SharedSqlRecordLogic = class {
810
931
  }
811
932
  fts5Strategy.insert(this.exec, id, JSON.stringify(target.content));
812
933
  this.syncFileRefs(id, target.typeId, target.content);
934
+ this.syncContentSort(id, target.typeId, target.content);
813
935
  });
814
936
  const updated = await this.getRecord(id);
815
937
  if (!updated)
@@ -835,6 +957,7 @@ var SharedSqlRecordLogic = class {
835
957
  ]);
836
958
  fts5Strategy.insert(this.exec, id, JSON.stringify(content));
837
959
  this.syncFileRefs(id, toTypeId, content);
960
+ this.syncContentSort(id, toTypeId, content);
838
961
  });
839
962
  const updated = await this.getRecord(id);
840
963
  if (!updated)
@@ -842,29 +965,35 @@ var SharedSqlRecordLogic = class {
842
965
  return updated;
843
966
  }
844
967
  async queryRecords(query) {
845
- const { sql: where, params } = buildWhereClause(query);
968
+ const { sql: from, params: fromParams } = buildFromClause(query);
969
+ const { sql: where, params: whereParams } = buildWhereClause(query);
970
+ const params = [...fromParams, ...whereParams];
846
971
  const order = buildOrderClause(query);
847
972
  const limit = query.limit ?? 50;
848
- const rows = asStackQueryError(query, () => this.exec.all(`SELECT r.* FROM records r ${where} ${order} LIMIT ?`, [...params, limit + 1]));
973
+ const rows = asStackQueryError(query, () => this.exec.all(`SELECT r.* FROM ${from} ${where} ${order} LIMIT ?`, [
974
+ ...params,
975
+ limit + 1
976
+ ]));
849
977
  const hasMore = rows.length > limit;
850
978
  const page = hasMore ? rows.slice(0, limit) : rows;
851
979
  const records = page.map((row) => {
852
980
  const associations = this.getAssociationsForRecord(row.id);
853
981
  return rowToRecord(row, associations);
854
982
  });
855
- const countRows = asStackQueryError(query, () => this.exec.all(`SELECT COUNT(*) as total FROM records r ${where}`, params));
983
+ const countRows = asStackQueryError(query, () => this.exec.all(`SELECT COUNT(*) as total FROM ${from} ${where}`, params));
856
984
  const total = countRows[0]?.total ?? 0;
857
985
  const lastRecord = records[records.length - 1];
858
- const cursor = hasMore && lastRecord ? makeCursor(lastRecord, getSortField(query)) : null;
986
+ const cursor = hasMore && lastRecord ? makeCursor(lastRecord, query.sort, (record, field) => this.sortFieldKind(record, field)) : null;
859
987
  return { records, cursor, total };
860
988
  }
861
989
  /**
862
990
  * Atomically verify fileId is unreferenced, then hard-delete its
863
- * metadata records (see deleteUnreferencedAttachmentRecords on the
864
- * adapter contract). A real SQL transaction of synchronous calls — no
865
- * `await` between the check and the deletes, so nothing interleaves.
991
+ * metadata records across every typeId in `metadataTypeIds` (see
992
+ * deleteUnreferencedAttachmentRecords on the adapter contract). A real
993
+ * SQL transaction of synchronous calls — no `await` between the check
994
+ * and the deletes, so nothing interleaves.
866
995
  */
867
- async deleteUnreferencedAttachmentRecords(fileId, metadataTypeId) {
996
+ async deleteUnreferencedAttachmentRecords(fileId, metadataTypeIds) {
868
997
  return this.exec.transaction(() => {
869
998
  const referenced = this.exec.all(`SELECT 1 as found FROM associations WHERE kind = 'attachment' AND file_id = ?
870
999
  UNION ALL
@@ -873,7 +1002,11 @@ var SharedSqlRecordLogic = class {
873
1002
  if (referenced.length) {
874
1003
  throw new StackConflictError("Attachment is still referenced by one or more records");
875
1004
  }
876
- const metaRows = this.exec.all(`SELECT id FROM records WHERE type_id = ? AND json_extract(content, '$.fileId') = ?`, [metadataTypeId, fileId]);
1005
+ if (metadataTypeIds.length === 0)
1006
+ return [];
1007
+ const placeholders = metadataTypeIds.map(() => "?").join(", ");
1008
+ const metaRows = this.exec.all(`SELECT id FROM records
1009
+ WHERE type_id IN (${placeholders}) AND json_extract(content, '$.fileId') = ?`, [...metadataTypeIds, fileId]);
877
1010
  const deleted = [];
878
1011
  for (const row of metaRows) {
879
1012
  const purged = this.hardDeleteRecord(row.id);
@@ -983,7 +1116,9 @@ var SharedSqlRecordLogic = class {
983
1116
  type.migratesFrom ?? null,
984
1117
  toMs(type.createdAt)
985
1118
  ]);
986
- this.fileRefFieldsByType.set(type.id, fileRefFieldNames(type.schema));
1119
+ const schema = type.schema;
1120
+ this.fileRefFieldsByType.set(type.id, fileRefFieldNames(schema));
1121
+ this.sortFieldsByType.set(type.id, sortableFieldKinds(schema));
987
1122
  }
988
1123
  async getType(id) {
989
1124
  const rows = this.exec.all("SELECT * FROM types WHERE id = ?", [id]);
@@ -1080,6 +1215,37 @@ var SharedSqlRecordLogic = class {
1080
1215
  * content or typeId, so the index never drifts. Only top-level scalars
1081
1216
  * are indexed; the schema lookup is cached (fileRefFieldsByType).
1082
1217
  */
1218
+ getSortFields(typeId) {
1219
+ const cached = this.sortFieldsByType.get(typeId);
1220
+ if (cached)
1221
+ return cached;
1222
+ const typeRow = this.exec.get("SELECT schema FROM types WHERE id = ?", [
1223
+ typeId
1224
+ ]);
1225
+ const fields = typeRow ? sortableFieldKinds(JSON.parse(typeRow.schema)) : /* @__PURE__ */ new Map();
1226
+ this.sortFieldsByType.set(typeId, fields);
1227
+ return fields;
1228
+ }
1229
+ /**
1230
+ * Replace a record's content_sort rows with what its content currently
1231
+ * holds, on every write that can change content or typeId. A field
1232
+ * holding no orderable value gets no row at all, which is what puts the
1233
+ * record at the end of a sort on that field.
1234
+ */
1235
+ syncContentSort(recordId, typeId, content) {
1236
+ this.exec.run("DELETE FROM content_sort WHERE record_id = ?", [recordId]);
1237
+ for (const [field, kind] of this.getSortFields(typeId)) {
1238
+ const entry = contentSortEntry(kind, content[field]);
1239
+ if (!entry)
1240
+ continue;
1241
+ this.exec.run(`INSERT OR IGNORE INTO content_sort (record_id, field, num_value, text_key, text_value)
1242
+ VALUES (?, ?, ?, ?, ?)`, entry.kind === "num" ? [recordId, field, entry.num, null, null] : [recordId, field, null, entry.key, entry.text]);
1243
+ }
1244
+ }
1245
+ /** The declared kind of a record's top-level `field`, if it has one. */
1246
+ sortFieldKind(record, field) {
1247
+ return this.getSortFields(record.typeId).get(field);
1248
+ }
1083
1249
  syncFileRefs(recordId, typeId, content) {
1084
1250
  this.exec.run("DELETE FROM file_refs WHERE record_id = ?", [recordId]);
1085
1251
  const fields = this.getFileRefFields(typeId);
@@ -1131,6 +1297,8 @@ var DoSQLiteRecordAdapter = class _DoSQLiteRecordAdapter {
1131
1297
  fullTextSearch: true,
1132
1298
  contentFieldQuery: true,
1133
1299
  nestedContentQuery: true,
1300
+ contentPresenceQuery: true,
1301
+ contentFieldSort: true,
1134
1302
  sortableFields: ["createdAt", "updatedAt", "version"],
1135
1303
  maxAttachmentBytes: null,
1136
1304
  maxContentBytes: null
@@ -1200,8 +1368,8 @@ var DoSQLiteRecordAdapter = class _DoSQLiteRecordAdapter {
1200
1368
  queryRecords(query) {
1201
1369
  return this.record.queryRecords(query);
1202
1370
  }
1203
- deleteUnreferencedAttachmentRecords(fileId, metadataTypeId) {
1204
- return this.record.deleteUnreferencedAttachmentRecords(fileId, metadataTypeId);
1371
+ deleteUnreferencedAttachmentRecords(fileId, metadataTypeIds) {
1372
+ return this.record.deleteUnreferencedAttachmentRecords(fileId, metadataTypeIds);
1205
1373
  }
1206
1374
  // -------------------------------------------------------
1207
1375
  // Versions
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../sqlite-shared/src/schema.ts","../../sqlite-shared/src/cursor.ts","../../sqlite-shared/src/fts5.ts","../../sqlite-shared/src/query.ts","../../sqlite-shared/src/mappers.ts","../../sqlite-shared/src/executor.ts","../../sqlite-shared/src/config.ts","../../sqlite-shared/src/record-logic.ts","../src/executor.ts","../src/index.ts"],"names":["StackQueryError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Schema DDL shared by SQLite-backed record adapters. TOKENS_SCHEMA_SQL\n * is split out from RECORD_SCHEMA_SQL because token storage lives in its\n * own file, never bundled with records — see NativeTokenStore in\n * record-adapter-sqlite and the StackTokenStore portability rationale\n * (docs/spec/wire-format.md § Authentication).\n */\n\nexport const RECORD_SCHEMA_SQL = `\n CREATE TABLE IF NOT EXISTS records (\n id TEXT PRIMARY KEY,\n type_id TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n content TEXT NOT NULL CHECK (json_valid(content)),\n version INTEGER NOT NULL DEFAULT 1,\n parent_id TEXT,\n entity_id TEXT,\n app_id TEXT,\n principal_id TEXT,\n updated_by TEXT,\n updated_via TEXT,\n deleted_at INTEGER,\n unlisted_at INTEGER,\n permissions TEXT CHECK (permissions IS NULL OR json_valid(permissions))\n ) STRICT;\n\n -- A relationship's target is (related_scope, related_id) plus one\n -- qualifier: related_stack for a record in another stack, related_ns for\n -- a foreign namespace. All four are in the primary key, so two targets\n -- differing only by namespace are two associations.\n CREATE TABLE IF NOT EXISTS associations (\n record_id TEXT NOT NULL REFERENCES records(id),\n kind TEXT NOT NULL CHECK (kind IN ('tag', 'attachment', 'relationship')),\n label TEXT NOT NULL,\n file_id TEXT NOT NULL DEFAULT '',\n related_scope TEXT NOT NULL DEFAULT '' CHECK (related_scope IN ('', 'record', 'entity', 'external')),\n related_id TEXT NOT NULL DEFAULT '',\n related_ns TEXT NOT NULL DEFAULT '',\n related_stack TEXT NOT NULL DEFAULT '',\n PRIMARY KEY (record_id, kind, label, file_id, related_scope, related_id, related_ns, related_stack)\n ) STRICT;\n\n CREATE TABLE IF NOT EXISTS versions (\n record_id TEXT NOT NULL REFERENCES records(id),\n version INTEGER NOT NULL,\n type_id TEXT NOT NULL,\n content TEXT NOT NULL CHECK (json_valid(content)),\n updated_at INTEGER NOT NULL,\n entity_id TEXT,\n updated_by TEXT,\n updated_via TEXT,\n associations TEXT CHECK (associations IS NULL OR json_valid(associations)),\n permissions TEXT CHECK (permissions IS NULL OR json_valid(permissions)),\n PRIMARY KEY (record_id, version)\n ) STRICT;\n\n CREATE TABLE IF NOT EXISTS types (\n id TEXT PRIMARY KEY,\n base_id TEXT NOT NULL,\n version INTEGER NOT NULL,\n name TEXT NOT NULL,\n schema TEXT NOT NULL CHECK (json_valid(schema)),\n schema_hash TEXT NOT NULL,\n migrates_from TEXT,\n created_at INTEGER NOT NULL\n ) STRICT;\n\n -- One row per top-level file-ref content field on a record, kept in sync\n -- on every content/typeId write. Lets the attachmentFileId query filter\n -- and deleteAttachment()'s reference check see content-held file\n -- references, not just attachment associations.\n CREATE TABLE IF NOT EXISTS file_refs (\n record_id TEXT NOT NULL REFERENCES records(id),\n field TEXT NOT NULL,\n file_id TEXT NOT NULL,\n PRIMARY KEY (record_id, field)\n ) STRICT;\n\n -- Indexes\n CREATE INDEX IF NOT EXISTS idx_records_type_id ON records(type_id);\n CREATE INDEX IF NOT EXISTS idx_records_parent_id ON records(parent_id);\n CREATE INDEX IF NOT EXISTS idx_records_entity_id ON records(entity_id);\n CREATE INDEX IF NOT EXISTS idx_records_app_id ON records(app_id);\n CREATE INDEX IF NOT EXISTS idx_records_principal_id ON records(principal_id);\n CREATE INDEX IF NOT EXISTS idx_records_deleted_at ON records(deleted_at);\n CREATE INDEX IF NOT EXISTS idx_records_unlisted_at ON records(unlisted_at);\n CREATE INDEX IF NOT EXISTS idx_records_created_at ON records(created_at);\n CREATE INDEX IF NOT EXISTS idx_records_updated_at ON records(updated_at);\n CREATE INDEX IF NOT EXISTS idx_assoc_record_id ON associations(record_id);\n CREATE INDEX IF NOT EXISTS idx_assoc_kind_label ON associations(kind, label);\n CREATE INDEX IF NOT EXISTS idx_assoc_kind_file_id ON associations(kind, file_id);\n CREATE INDEX IF NOT EXISTS idx_assoc_related ON associations(related_scope, related_ns, related_id);\n CREATE INDEX IF NOT EXISTS idx_types_base_id ON types(base_id);\n CREATE INDEX IF NOT EXISTS idx_file_refs_file_id ON file_refs(file_id);\n`;\n\n/**\n * Bearer-token storage backing StackTokenStore. Kept separate from\n * RECORD_SCHEMA_SQL so an adapter can put it in its own file — the\n * portable stack file shouldn't also carry a server's auth material.\n */\nexport const TOKENS_SCHEMA_SQL = `\n CREATE TABLE IF NOT EXISTS tokens (\n id TEXT PRIMARY KEY,\n token_hash TEXT NOT NULL UNIQUE,\n principal_id TEXT NOT NULL,\n subject_id TEXT NOT NULL,\n label TEXT,\n created_at INTEGER NOT NULL,\n expires_at INTEGER\n ) STRICT;\n\n CREATE INDEX IF NOT EXISTS idx_tokens_hash ON tokens(token_hash);\n`;\n\n/** The full-text index behind `StackQuery.filter.search` (see fts5.ts). */\nexport const FTS5_SCHEMA_SQL = `\n CREATE VIRTUAL TABLE IF NOT EXISTS records_fts USING fts5(\n content,\n content='records',\n content_rowid='rowid'\n );\n`;\n\n/**\n * Enforces the REFERENCES constraints declared above (off by default per\n * SQLite connection), so touching a nonexistent record fails loudly\n * instead of creating an orphan row. Adapters map the constraint\n * violation to StackNotFoundError at the call sites that can trigger it.\n */\nexport const PRAGMA_FOREIGN_KEYS_ON = `PRAGMA foreign_keys = ON;`;\n\n/**\n * WAL journaling: page-level writes, crash-safe without our own\n * temp-file-and-rename dance, and real SQLite file locking. Only\n * meaningful for a real file (a :memory: database silently ignores it).\n */\nexport const PRAGMA_JOURNAL_MODE_WAL = `PRAGMA journal_mode = WAL;`;\n","/**\n * Cursor encode/decode for keyset pagination. Uses btoa/atob rather than\n * Node's Buffer so the codec stays runtime-agnostic — cursor payloads\n * here are plain ASCII (\"field|value|id\"), so no UTF-8 encoding step is\n * needed.\n */\n\nimport { StackQueryError } from '@haverstack/core';\nimport type { StackRecord } from '@haverstack/core';\n\nexport type SortField = 'createdAt' | 'updatedAt' | 'version';\n\nexport const SORT_FIELDS: readonly SortField[] = ['createdAt', 'updatedAt', 'version'];\n\nexport const encodeCursor = (field: SortField, value: number, id: string): string =>\n btoa(`${field}|${value}|${id}`);\n\nexport type DecodedCursor = { field: SortField; value: number; id: string };\n\n/** Throws StackQueryError for any malformed, corrupt, or unrecognized cursor. */\nexport const decodeCursor = (cursor: string): DecodedCursor => {\n let decoded: string;\n try {\n decoded = atob(cursor);\n } catch {\n throw new StackQueryError(`Invalid cursor: malformed \"${cursor}\"`);\n }\n const parts = decoded.split('|');\n if (parts.length !== 3) {\n throw new StackQueryError(`Invalid cursor: malformed \"${cursor}\"`);\n }\n const [field, value, id] = parts as [string, string, string];\n if (!SORT_FIELDS.includes(field as SortField)) {\n throw new StackQueryError(`Invalid cursor: unknown sort field \"${field}\"`);\n }\n const numericValue = Number(value);\n if (!isFinite(numericValue)) {\n throw new StackQueryError(`Invalid cursor: non-numeric sort value`);\n }\n return { field: field as SortField, value: numericValue, id };\n};\n\nexport const getSortField = (query: { sort?: { field?: SortField } }): SortField =>\n query.sort?.field ?? 'createdAt';\n\nexport const getSortColumn = (field: SortField): string =>\n field === 'createdAt' ? 'created_at' : field === 'updatedAt' ? 'updated_at' : 'version';\n\nexport const makeCursor = (record: StackRecord, field: SortField): string => {\n const value =\n field === 'updatedAt'\n ? record.updatedAt.getTime()\n : field === 'version'\n ? record.version\n : record.createdAt.getTime();\n return encodeCursor(field, value, record.id);\n};\n","/**\n * FTS5 query sanitization and indexing — the full-text search half of the\n * shared record logic.\n *\n * FTS5's query language supports operators (AND/OR/NOT, phrases, NEAR,\n * wildcards) that can be expensive or cause parse errors with untrusted\n * input, so a search string is rewritten before it reaches the engine:\n *\n * kept: AND/OR/NOT (with a left operand), phrase queries (\"…\"), implicit AND\n * removed: wildcards (*), NEAR(...) (terms kept, wrapper dropped), every\n * character outside the allow-list, bare NOT (no left operand),\n * operators with no right operand\n * capped: parenthesis nesting depth (default: 2)\n * closed: an odd trailing quote\n *\n * Two FTS5 grammar details drive the less obvious rules:\n *\n * - FTS5's NEAR is a function-call form — `NEAR(a b, 10)`. Deleting the\n * whole span would also delete the terms inside it, which can leave a\n * dangling operator when NEAR(...) sits next to AND/OR (e.g. \"x AND\n * NEAR(a b, 5)\" -> \"x AND\" — a syntax error). So this keeps the terms\n * and drops only the NEAR(...) wrapper and distance.\n * - FTS5 has real column-filter syntax and errors hard on any name but\n * the table's actual columns. It is wider than `colname:term`: `-name`\n * and `{a b}` filter columns with no colon at all, so an ordinary\n * hyphenated word is read as one. Since records_fts has exactly one\n * column and callers have no business targeting columns explicitly,\n * everything outside a phrase is reduced to an allow-list rather than\n * validated — see OUTSIDE_PHRASE_DISALLOWED.\n *\n * The rewrite is best-effort and makes no completeness claim against\n * FTS5's grammar — which is why the caller wraps execution as well, so a\n * case this misses surfaces as a StackQueryError rather than a raw engine\n * error. Search text is what a person typed into a box: an unbalanced\n * quote or a trailing \"AND\" is ordinary input, not a malformed request.\n *\n * What this bounds is the grammar, not the cost: a syntactically modest\n * search over a large index can still be expensive, and node:sqlite blocks\n * the calling thread, so there is no timeout to set from inside the call.\n * Bounding execution time belongs to whoever drives the engine under load\n * — see docs/spec/wire-format.md § Bounding query cost for the server-side\n * expectation and the `timeout` wire error that goes with it.\n */\n\nimport type { SqlExecutor } from './executor.js';\n\n/**\n * Outside a phrase, everything but a letter, digit, mark, underscore,\n * whitespace, paren or quote becomes a separator.\n *\n * An allow-list rather than a list of metacharacters to strip, because\n * FTS5's column-filter syntax is wider than it looks and an enumeration\n * kept missing parts of it: `-name` and `{a b}` filter columns with no\n * colon in sight, so `-cats` and even the hyphen inside `cats-dogs` were\n * read as column names and answered \"no such column\". `+` is a bare\n * syntax error. Listing what may pass means the next piece of syntax\n * nobody here anticipated arrives as a separator instead of as an error.\n *\n * Nothing is lost by it: FTS5's default tokenizer already splits terms on\n * these characters, so `cats-dogs` searches for the same two tokens it was\n * indexed as.\n */\nconst OUTSIDE_PHRASE_DISALLOWED = /[^\\p{L}\\p{N}\\p{M}_\\s()\"]/gu;\n\nexport const sanitizeFts5Query = (query: string, maxDepth = 2): string => {\n if (!query) return '';\n\n // Drop control characters, inside a phrase as well as outside. A NUL in\n // particular ends the string early for SQLite's C API, so the engine sees\n // an opening quote whose closing one is past the truncation and reports an\n // unterminated string; none of them can match an indexed token anyway.\n let clean = query.replace(/[\\p{Cc}\\p{Cf}]/gu, ' ');\n\n // Remove wildcards\n clean = clean.replace(/\\*/g, '');\n\n // Replace NEAR(terms, distance) with just its terms — see note above.\n clean = clean.replace(/\\bNEAR\\s*\\(\\s*([^,)]*)(?:,[^)]*)?\\)/gi, '$1');\n\n // A NEAR( with no closing paren isn't the function-call form the rule\n // above rewrites, and the paren auto-close below would hand FTS5 a NEAR\n // call it never meant to write. Keep the grouping, drop the keyword.\n clean = clean.replace(/\\bNEAR\\s*\\(/gi, '(');\n\n // Reduce to the characters that carry meaning outside a phrase — see note\n // above. Everything else becomes a separator, which is what FTS5's own\n // tokenizer would do with it anyway.\n clean = clean.replace(/\"[^\"]*\"|[^\"]+/g, (span) =>\n span.startsWith('\"') ? span : span.replace(OUTSIDE_PHRASE_DISALLOWED, ' '),\n );\n\n // Strip bare NOT with no left operand — FTS5 requires \"term NOT term\", not \"NOT term\"\n clean = clean.replace(/(?:^|\\(\\s*)NOT\\s+/gi, (m) => m.replace(/NOT\\s+/i, ''));\n\n // Enforce max nesting depth; replace excess ( with a space and discard\n // unmatched ). Parens inside a phrase are literal text to FTS5, so the\n // depth count steps over quoted spans rather than reading them as syntax.\n let currentDepth = 0;\n let inPhrase = false;\n let result = '';\n for (const char of clean) {\n if (char === '\"') {\n inPhrase = !inPhrase;\n result += char;\n } else if (inPhrase) {\n result += char;\n } else if (char === '(') {\n if (currentDepth < maxDepth) {\n currentDepth++;\n result += char;\n } else result += ' ';\n } else if (char === ')') {\n if (currentDepth > 0) {\n currentDepth--;\n result += char;\n } else result += ' ';\n } else {\n result += char;\n }\n }\n\n // Close an odd trailing quote before the parens, so the added ) lands\n // outside the phrase rather than becoming a literal character in it.\n if (inPhrase) result += '\"';\n\n // Auto-close any unclosed parens\n if (currentDepth > 0) result += ')'.repeat(currentDepth);\n\n // Remove empty paren pairs left behind by NEAR/NOT stripping, and drop\n // operators left without an operand. Looped together because each can\n // create work for the other: removing an operator can empty a paren pair\n // (\"(a AND)\" -> \"(a)\" -> ...), and removing a pair can strand the\n // operator that pointed at it (\"x AND ()\" -> \"x AND\").\n let prev: string;\n do {\n prev = result;\n // An empty phrase matches nothing and is the one construct whose\n // quotes can re-pair differently once a rule inserts text beside it.\n result = result.replace(/\"\"/g, ' ');\n result = result.replace(/\\(\\s*\\)/g, ' ');\n result = dropDanglingOperators(result);\n result = restoreImplicitAnd(result);\n } while (result !== prev);\n\n return result.replace(/\\s+/g, ' ').replace(/\\(\\s+/g, '(').replace(/\\s+\\)/g, ')').trim();\n};\n\n/** An operator FTS5 requires a term on both sides of. */\nconst OPERATOR = String.raw`(?:AND|OR|NOT)`;\n\n/**\n * Stands in for a phrase while operators are rewritten. A private-use\n * character is not a word character, so `\\b` still sees the operator\n * beside it, while `^`, `$` and the paren anchors see an operand — which\n * is what a phrase is. Nothing reaches here holding one: `clean` is\n * rewritten from the caller's string, and a literal U+E000 in it would be\n * a term character FTS5 tokenizes away regardless.\n */\nconst PHRASE_HOLE = '\\u{E000}';\n\n/**\n * Drop operators left without an operand — the shape a person types on\n * their way to a longer query (\"cats AND\") or lands on once the rules\n * above have removed a term. FTS5 rejects each of them outright, so what\n * would otherwise be a syntax error becomes the search for the terms that\n * are actually there.\n *\n * Phrases are masked out first: `\"cats AND dogs\"` is literal text to FTS5,\n * and rewriting inside it would change what the user asked for.\n */\nconst dropDanglingOperators = (query: string): string =>\n withPhrasesMasked(query, (masked) => {\n let out = masked;\n let prev: string;\n do {\n prev = out;\n // A run of operators keeps only the first — the rest have no left operand.\n out = out.replace(\n new RegExp(String.raw`\\b(${OPERATOR})\\b(?:\\s+\\b${OPERATOR}\\b)+`, 'gi'),\n '$1',\n );\n // No left operand: at the start of the query or straight after \"(\".\n out = out.replace(new RegExp(String.raw`(^|\\()\\s*\\b${OPERATOR}\\b\\s*`, 'gi'), '$1');\n // No right operand: at the end of the query or straight before \")\".\n out = out.replace(new RegExp(String.raw`\\s*\\b${OPERATOR}\\b\\s*($|\\))`, 'gi'), '$1');\n } while (out !== prev);\n return out;\n });\n\n/**\n * Run `rewrite` over the query with each phrase swapped for PHRASE_HOLE,\n * then put the phrases back in order. Every rule that reasons about what\n * sits beside an operator or a paren needs this: inside a phrase the text\n * is literal, and from outside one the phrase is a single operand.\n */\nconst withPhrasesMasked = (query: string, rewrite: (masked: string) => string): string => {\n const phrases: string[] = [];\n const masked = query.replace(/\"[^\"]*\"/g, (phrase) => {\n phrases.push(phrase);\n return PHRASE_HOLE;\n });\n let i = 0;\n return rewrite(masked).replace(new RegExp(PHRASE_HOLE, 'g'), () => phrases[i++]);\n};\n\n/**\n * Put back the operator FTS5 requires around a group. Two terms side by\n * side are an implicit AND (`cats dogs`), but a group is not: `cats (dogs)`\n * is a syntax error where `cats AND (dogs)` is fine. Grouping survives a\n * rewrite that strips the operator which used to sit beside it, so this\n * names the AND that was always meant rather than dropping the parens.\n *\n * Both rules test a *whole* word against the operator list. Matching a\n * prefix instead reads the `D` of a preceding `AND` as a term and inserts\n * a second one beside it.\n *\n * Phrases are masked for the same reason as elsewhere — a phrase is an\n * operand, so `\"a b\" (c)` needs the operator too.\n */\nconst OPERAND_CHAR = String.raw`[\\p{L}\\p{N}_${PHRASE_HOLE}]`;\n\nconst isOperator = (word: string): boolean => /^(?:AND|OR|NOT)$/i.test(word);\n\nconst restoreImplicitAnd = (query: string): string =>\n withPhrasesMasked(query, (masked) =>\n masked\n // \")\" then an operand — \") cats\", \") (\", \") <phrase>\" — unless that\n // operand is an operator, which already joins the two sides.\n .replace(\n new RegExp(String.raw`\\)\\s*(?=${OPERAND_CHAR}|\\()`, 'gu'),\n (match, offset: number, whole: string) => {\n const next = /^\\s*([\\p{L}\\p{N}_]+)/u.exec(whole.slice(offset + match.length));\n return next && isOperator(next[1]) ? match : ') AND ';\n },\n )\n // An operand then \"(\", unless the operand is an operator.\n .replace(new RegExp(String.raw`(${OPERAND_CHAR}+)\\s*(?=\\()`, 'gu'), (match, word: string) =>\n isOperator(word) ? match : `${word} AND `,\n ),\n );\n\n// -------------------------------------------------------\n// Indexing strategy\n// -------------------------------------------------------\n\n/**\n * FTS5's external-content table needs its special `('delete', rowid,\n * content)` command — with the *old* content — to unindex a row; a plain\n * DELETE leaves stale, still-searchable entries. remove() therefore reads\n * the current rowid/content and must run before the caller's change.\n */\nexport const fts5Strategy = {\n /** Index a record that has no FTS entry yet (used right after an INSERT). */\n insert(exec: SqlExecutor, recordId: string, content: string): void {\n exec.run(`INSERT INTO records_fts(rowid, content) SELECT rowid, ? FROM records WHERE id = ?`, [\n content,\n recordId,\n ]);\n },\n /**\n * Remove a record's FTS entry. MUST be called before the records row's\n * content changes or the row is deleted — see the note above.\n */\n remove(exec: SqlExecutor, recordId: string): void {\n const row = exec.get<{ rowid: number; content: string }>(\n 'SELECT rowid, content FROM records WHERE id = ?',\n [recordId],\n );\n if (!row) return;\n exec.run(`INSERT INTO records_fts(records_fts, rowid, content) VALUES('delete', ?, ?)`, [\n row.rowid,\n row.content,\n ]);\n },\n};\n","/**\n * WHERE/ORDER clause building for StackQuery. Engine-independent SQL —\n * shared verbatim between SQLite-backed record adapters. The one caveat is\n * full-text search: `f.search` here assumes a `records_fts` virtual table\n * with a MATCH-able `content` column (see fts5.ts).\n */\n\nimport { StackQueryError, type StackQuery } from '@haverstack/core';\nimport { parseContentFilterKey } from '@haverstack/core/adapter';\nimport { decodeCursor, getSortColumn, getSortField, type SortField } from './cursor.js';\nimport { sanitizeFts5Query } from './fts5.js';\n\nexport { getSortField, getSortColumn };\nexport type { SortField };\n\n/**\n * Reduce `sort.direction` to a keyword this module may interpolate into\n * SQL. Core's assertValidSort() already rejects anything else at the\n * invariant layer, but this builder writes the value straight into\n * `ORDER BY` and the cursor comparison, so it re-checks rather than trust\n * an upstream that a future refactor could bypass: an unvalidated direction\n * here is a SQL-injection sink. Anything but \"asc\" is treated as the\n * default \"desc\" only after passing the guard.\n */\nconst sqlDirection = (query: StackQuery): 'ASC' | 'DESC' => {\n const dir = query.sort?.direction ?? 'desc';\n if (dir !== 'asc' && dir !== 'desc') {\n throw new StackQueryError(`Invalid sort direction \"${dir}\": expected \"asc\" or \"desc\".`);\n }\n return dir === 'asc' ? 'ASC' : 'DESC';\n};\n\n/**\n * Normalize a json_each row's value so one array element and one bare\n * value are walked by the same clause: an array stands for its elements,\n * anything else for itself. See docs/spec/data-model.md § Nested content\n * paths.\n */\nconst spread = (alias: string): string =>\n `CASE WHEN ${alias}.type = 'array' THEN ${alias}.value ` +\n `WHEN ${alias}.type = 'object' THEN json_array(json(${alias}.value)) ` +\n `ELSE json_array(${alias}.value) END`;\n\n/**\n * The members of a json_each row that is an object, and none for a row\n * that is not — a path descending through a scalar reaches no value rather\n * than reaching an error.\n */\nconst members = (alias: string): string =>\n `CASE WHEN ${alias}.type = 'object' THEN ${alias}.value ELSE '{}' END`;\n\n/**\n * True when some value reached by `segments` satisfies `leaf`, applied to\n * the alias holding that value. Each segment is a bound parameter matched\n * against json_each's `key`, never interpolated into a path expression, so\n * no key can be read as syntax. See docs/spec/data-model.md § Filter.\n */\nconst contentPathExists = (\n segments: string[],\n leaf: (alias: string) => string,\n params: unknown[],\n nextAlias: () => string,\n): string => {\n // The walk is a flat join list, not a subquery per segment: both SQLite\n // builds cap expression-tree depth far below the segment cap, and\n // nesting spends that budget while a join list spends the join budget\n // the cap is actually sized against.\n let key = nextAlias();\n let value = nextAlias();\n const from = [`json_each(r.content) AS ${key}`, `json_each(${spread(key)}) AS ${value}`];\n const where = [`${key}.key = ?`];\n params.push(segments[0]);\n\n for (const segment of segments.slice(1)) {\n key = nextAlias();\n from.push(`json_each(${members(value)}) AS ${key}`);\n value = nextAlias();\n from.push(`json_each(${spread(key)}) AS ${value}`);\n where.push(`${key}.key = ?`);\n params.push(segment);\n }\n\n where.push(leaf(value));\n return `EXISTS (SELECT 1 FROM ${from.join(', ')} WHERE ${where.join(' AND ')})`;\n};\n\n/**\n * A scalar filter value never matches an object or array stored at the\n * path: json_each exposes those as their JSON text, which would compare\n * equal to a string spelling the same document while every non-SQL adapter\n * compares the value itself.\n */\nconst equalsLeaf = (alias: string): string =>\n `${alias}.type NOT IN ('object', 'array') AND ${alias}.value = ?`;\n\nexport const buildWhereClause = (query: StackQuery): { sql: string; params: unknown[] } => {\n const conditions: string[] = [\"r.id != '_config'\"];\n const params: unknown[] = [];\n const f = query.filter ?? {};\n\n if (!f.includeDeleted) {\n conditions.push('r.deleted_at IS NULL');\n }\n\n if (!f.includeUnlisted) {\n conditions.push('r.unlisted_at IS NULL');\n }\n\n if (f.typeId !== undefined) {\n const ids = Array.isArray(f.typeId) ? f.typeId : [f.typeId];\n conditions.push(`r.type_id IN (${ids.map(() => '?').join(',')})`);\n params.push(...ids);\n }\n\n if (f.parentId !== undefined) {\n if (f.parentId === null) {\n conditions.push('r.parent_id IS NULL');\n } else {\n conditions.push('r.parent_id = ?');\n params.push(f.parentId);\n }\n }\n\n if (f.appId !== undefined) {\n const ids = Array.isArray(f.appId) ? f.appId : [f.appId];\n conditions.push(`r.app_id IN (${ids.map(() => '?').join(',')})`);\n params.push(...ids);\n }\n\n if (f.entityId !== undefined) {\n const ids = Array.isArray(f.entityId) ? f.entityId : [f.entityId];\n conditions.push(`r.entity_id IN (${ids.map(() => '?').join(',')})`);\n params.push(...ids);\n }\n\n if (f.principalId !== undefined) {\n const ids = Array.isArray(f.principalId) ? f.principalId : [f.principalId];\n conditions.push(`r.principal_id IN (${ids.map(() => '?').join(',')})`);\n params.push(...ids);\n }\n\n if (f.createdAt?.after) {\n conditions.push('r.created_at > ?');\n params.push(f.createdAt.after.getTime());\n }\n if (f.createdAt?.before) {\n conditions.push('r.created_at < ?');\n params.push(f.createdAt.before.getTime());\n }\n if (f.updatedAt?.after) {\n conditions.push('r.updated_at > ?');\n params.push(f.updatedAt.after.getTime());\n }\n if (f.updatedAt?.before) {\n conditions.push('r.updated_at < ?');\n params.push(f.updatedAt.before.getTime());\n }\n\n // Every association filter below is a semi-join rather than a correlated\n // EXISTS, so the planner drives from the association side — reading the\n // matching rows through idx_assoc_kind_label / idx_assoc_kind_file_id /\n // idx_file_refs_file_id and looking up those records — instead of\n // scanning every record and probing for each. The work is proportional\n // to how many records match, not to how many the stack holds.\n\n // Tag filter — record must have ALL specified tags\n if (f.tags?.length) {\n for (const tag of f.tags) {\n conditions.push(\n `r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = 'tag' AND a.label = ?)`,\n );\n params.push(tag);\n }\n }\n\n // Attachment label filter\n if (f.hasAttachment) {\n conditions.push(\n `r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = 'attachment' AND a.label = ?)`,\n );\n params.push(f.hasAttachment);\n }\n\n // Attachment file ID filter — find records that reference a specific file,\n // either via an attachment association or a top-level file-ref content field\n if (f.attachmentFileId) {\n conditions.push(\n `(r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = 'attachment' AND a.file_id = ?)\n OR r.id IN (SELECT fr.record_id FROM file_refs fr WHERE fr.file_id = ?))`,\n );\n params.push(f.attachmentFileId, f.attachmentFileId);\n }\n\n // Relationship filter — each clause is an optional pattern, so a bare\n // label matches every target under it and an external target with no\n // `id` matches its whole namespace (docs/spec/data-model.md § Filter).\n // A target-bearing pattern reads through idx_assoc_related; a bare label\n // through idx_assoc_kind_label.\n if (f.relatedTo) {\n const clauses: string[] = [];\n const target = f.relatedTo.target;\n if (f.relatedTo.label !== undefined) {\n clauses.push('a.label = ?');\n params.push(f.relatedTo.label);\n }\n if (target) {\n clauses.push('a.related_scope = ?');\n params.push(target.scope);\n if (target.scope === 'record') {\n clauses.push('a.related_id = ?', 'a.related_stack = ?');\n params.push(target.recordId, target.stackUrl ?? '');\n } else if (target.scope === 'entity') {\n clauses.push('a.related_id = ?');\n params.push(target.entityId);\n } else {\n clauses.push('a.related_ns = ?');\n params.push(target.ns);\n if (target.id !== undefined) {\n clauses.push('a.related_id = ?');\n params.push(target.id);\n }\n }\n }\n conditions.push(\n `r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = 'relationship'` +\n clauses.map((c) => ` AND ${c}`).join('') +\n `)`,\n );\n }\n\n // Content field filters — a dot-separated path, matched element-wise\n // through arrays. A `null` value means \"no value at the path, or a value\n // that is null\" (docs/spec/data-model.md § Filter), which is the second\n // arm below: \"some value there is null\" OR \"nothing is there at all\".\n if (f.content) {\n let aliasSeq = 0;\n const nextAlias = () => `cp${aliasSeq++}`;\n for (const [key, value] of Object.entries(f.content)) {\n const segments = parseContentFilterKey(key);\n // undefined is the same absence null names — JSON drops it on the\n // way over the wire, so treating it as a value would make an\n // in-process query and its wire equivalent disagree.\n if (value === null || value === undefined) {\n const isNull = contentPathExists(segments, (a) => `${a}.value IS NULL`, params, nextAlias);\n const anyValue = contentPathExists(\n segments,\n (a) => `${a}.value IS NOT NULL`,\n params,\n nextAlias,\n );\n conditions.push(`(${isNull} OR NOT ${anyValue})`);\n } else {\n const match = contentPathExists(segments, equalsLeaf, params, nextAlias);\n params.push(value);\n conditions.push(match);\n }\n }\n }\n\n if (f.search) {\n const sanitized = sanitizeFts5Query(f.search);\n if (sanitized) {\n conditions.push(`r.rowid IN (SELECT rowid FROM records_fts WHERE records_fts MATCH ?)`);\n params.push(sanitized);\n } else {\n // A search that sanitizes to nothing (e.g. \"*\", punctuation-only) is\n // an honest zero-match result, not \"no filter\" — omitting the clause\n // would silently return the full table as the \"search result\".\n conditions.push('0');\n }\n }\n\n // Cursor (sort-field value + id for stable pagination)\n if (query.cursor) {\n const { field: cursorField, value: numericValue, id: cursorId } = decodeCursor(query.cursor);\n const sortField = getSortField(query);\n if (cursorField !== sortField) {\n throw new StackQueryError(\n `Cursor sort field \"${cursorField}\" does not match query sort field \"${sortField}\"`,\n );\n }\n const col = getSortColumn(cursorField);\n const op = sqlDirection(query) === 'ASC' ? '>' : '<';\n conditions.push(`(r.${col} ${op} ? OR (r.${col} = ? AND r.id ${op} ?))`);\n params.push(numericValue, numericValue, cursorId);\n }\n\n return {\n sql: conditions.length ? `WHERE ${conditions.join(' AND ')}` : '',\n params,\n };\n};\n\nexport const buildOrderClause = (query: StackQuery): string => {\n const field = getSortField(query);\n const dir = sqlDirection(query);\n return `ORDER BY r.${getSortColumn(field)} ${dir}, r.id ${dir}`;\n};\n","/**\n * Row <-> domain object mappers shared by every SQLite-backed record\n * adapter. Column names and JSON-encoding choices are the storage\n * contract; keeping one copy means the adapters can't drift on them.\n */\n\nimport type {\n StackRecord,\n StackType,\n RecordVersion,\n Association,\n RelationshipTarget,\n} from '@haverstack/core';\n\nexport const toMs = (d: Date): number => d.getTime();\nexport const fromMs = (ms: number): Date => new Date(ms);\n\nexport const rowToRecord = (\n row: Record<string, unknown>,\n associations: Association[],\n): StackRecord => {\n const record: StackRecord = {\n id: row.id as string,\n typeId: row.type_id as string,\n createdAt: fromMs(row.created_at as number),\n updatedAt: fromMs(row.updated_at as number),\n content: JSON.parse(row.content as string),\n version: row.version as number,\n };\n if (row.parent_id) record.parentId = row.parent_id as string;\n if (row.entity_id) record.entityId = row.entity_id as string;\n if (row.app_id) record.appId = row.app_id as string;\n if (row.principal_id) record.principalId = row.principal_id as string;\n if (row.updated_by) record.updatedBy = row.updated_by as string;\n if (row.updated_via) record.updatedVia = row.updated_via as string;\n if (row.deleted_at) record.deletedAt = fromMs(row.deleted_at as number);\n if (row.unlisted_at) record.unlistedAt = fromMs(row.unlisted_at as number);\n if (row.permissions) record.permissions = JSON.parse(row.permissions as string);\n if (associations.length) record.associations = associations;\n return record;\n};\n\nexport const rowToAssociation = (row: Record<string, unknown>): Association => {\n if (row.kind === 'tag') {\n return { kind: 'tag', label: row.label as string };\n }\n if (row.kind === 'attachment') {\n return {\n kind: 'attachment',\n label: row.label as string,\n fileId: row.file_id as string,\n };\n }\n // relationship\n return {\n kind: 'relationship',\n label: row.label as string,\n target: rowToTarget(row),\n };\n};\n\n/**\n * The five columns that identify an association, in the order every\n * INSERT and DELETE below binds them. One helper because the two must\n * agree exactly — a dissociate that bound them differently would delete\n * nothing and report success.\n */\nexport const associationKeyColumns = (a: Association): [string, string, string, string, string] => {\n if (a.kind === 'attachment') return [a.fileId, '', '', '', ''];\n if (a.kind !== 'relationship') return ['', '', '', '', ''];\n const t = a.target;\n if (t.scope === 'entity') return ['', 'entity', t.entityId, '', ''];\n if (t.scope === 'external') return ['', 'external', t.id, t.ns, ''];\n return ['', 'record', t.recordId, '', t.stackUrl ?? ''];\n};\n\nconst rowToTarget = (row: Record<string, unknown>): RelationshipTarget => {\n const id = row.related_id as string;\n if (row.related_scope === 'entity') return { scope: 'entity', entityId: id };\n if (row.related_scope === 'external') {\n return { scope: 'external', ns: row.related_ns as string, id };\n }\n const stackUrl = row.related_stack as string;\n return { scope: 'record', recordId: id, ...(stackUrl && { stackUrl }) };\n};\n\nexport const rowToType = (row: Record<string, unknown>): StackType => {\n const t: StackType = {\n id: row.id as string,\n baseId: row.base_id as string,\n version: row.version as number,\n name: row.name as string,\n schema: JSON.parse(row.schema as string),\n schemaHash: row.schema_hash as string,\n createdAt: fromMs(row.created_at as number),\n };\n if (row.migrates_from) t.migratesFrom = row.migrates_from as string;\n return t;\n};\n\nexport const rowToVersion = (row: Record<string, unknown>): RecordVersion => {\n const v: RecordVersion = {\n version: row.version as number,\n typeId: row.type_id as string,\n content: JSON.parse(row.content as string),\n updatedAt: fromMs(row.updated_at as number),\n };\n if (row.entity_id) v.entityId = row.entity_id as string;\n if (row.updated_by) v.updatedBy = row.updated_by as string;\n if (row.updated_via) v.updatedVia = row.updated_via as string;\n if (row.associations) v.associations = JSON.parse(row.associations as string);\n if (row.permissions) v.permissions = JSON.parse(row.permissions as string);\n return v;\n};\n","/**\n * Normalizes a SQLite binding's call convention (array-bind-then-step,\n * spread-args run/get/all, cursor-returning exec) behind one interface,\n * so the actual CRUD/query/version/type/association/token logic can live\n * once in this package instead of being re-typed per engine. Each adapter\n * package provides a thin SqlExecutor implementation over its own\n * database handle.\n *\n * Deliberately synchronous: every binding this targets executes queries\n * in-process without yielding, and an async interface would force the\n * shared logic's transaction sequences to interleave.\n */\nexport interface SqlExecutor {\n /** Run DDL, pragmas, or any statement(s) with no bound params and no results needed. */\n exec(sql: string): void;\n /** Run a single parameterized statement. Returns the number of rows affected (INSERT/UPDATE/DELETE). */\n run(sql: string, params?: readonly unknown[]): number;\n /** Run a parameterized statement and return the first result row, or undefined. */\n get<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): T | undefined;\n /** Run a parameterized statement and return all result rows. */\n all<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): T[];\n /**\n * Run `fn` atomically: every statement it issues commits together, or\n * none do if it throws. `fn` must be synchronous and call back into\n * this executor only — not every engine's transaction primitive can\n * straddle a suspended call. A binding with a real multi-statement\n * transaction (BEGIN/COMMIT/ROLLBACK) implements this with one; a\n * binding whose transaction primitive is itself a callback wrapper\n * (e.g. a Durable Object's storage.transactionSync) can pass `fn`\n * straight through to it.\n */\n transaction<T>(fn: () => T): T;\n}\n\n/** SQLite reports FK violations with this exact message; verify it holds when adding an engine. */\nexport const isForeignKeyViolation = (err: unknown): boolean =>\n err instanceof Error && err.message.includes('FOREIGN KEY constraint failed');\n\n/** SQLite reports a PRIMARY KEY/UNIQUE collision with this exact message. */\nexport const isUniqueConstraintViolation = (err: unknown): boolean =>\n err instanceof Error && err.message.includes('UNIQUE constraint failed');\n","import type { SqlExecutor } from './executor.js';\n\n/** timezone is optional passthrough app metadata — no default. */\nexport type StackConfig = { entityId: string; timezone: string | undefined };\n\n/** Inserts the singleton _config@1 record. Only valid on a freshly-created database. */\nexport const insertConfigRecord = (\n exec: SqlExecutor,\n entityId: string,\n timezone: string | undefined,\n): void => {\n const now = Date.now();\n exec.run(\n `INSERT INTO records (id, type_id, created_at, updated_at, content, version)\n VALUES ('_config', '_config@1', ?, ?, ?, 1)`,\n [now, now, JSON.stringify({ entityId, timezone })],\n );\n};\n\n/** Reads the singleton _config@1 record. Throws if the database is missing it. */\nexport const readStackConfig = (exec: SqlExecutor): StackConfig => {\n const row = exec.get<{ content: string }>(`SELECT content FROM records WHERE id = '_config'`);\n if (!row) throw new Error('Stack database is missing its config record.');\n const content = JSON.parse(row.content) as { entityId: string; timezone?: string };\n return { entityId: content.entityId, timezone: content.timezone };\n};\n","/**\n * The actual StackRecordAdapter logic (minus lifecycle: initialize/open/\n * close/flush stay per-engine, since schema setup, pragmas, locking, and\n * durability differ there). Every CRUD/query/version/type/association\n * operation lives here exactly once, parametrized over a SqlExecutor\n * (which normalizes each engine's call conventions). Concrete adapters\n * construct one of these and delegate their StackRecordAdapter methods\n * to it — see record-adapter-sqlite.\n */\n\nimport {\n StackConflictError,\n StackVersionConflictError,\n StackNotFoundError,\n StackQueryError,\n applyMergePatch,\n} from '@haverstack/core';\nimport type {\n StackRecord,\n StackType,\n TypeId,\n FileId,\n RecordVersion,\n StackQuery,\n QueryResult,\n Association,\n Permission,\n ActorOptions,\n ExpectedVersionOptions,\n} from '@haverstack/core';\nimport type { SqlExecutor } from './executor.js';\nimport { isForeignKeyViolation, isUniqueConstraintViolation } from './executor.js';\nimport { buildWhereClause, buildOrderClause, getSortField } from './query.js';\nimport { fts5Strategy } from './fts5.js';\nimport {\n rowToRecord,\n rowToAssociation,\n rowToType,\n rowToVersion,\n toMs,\n associationKeyColumns,\n} from './mappers.js';\nimport { makeCursor } from './cursor.js';\n\n/**\n * Run a query statement, restating an engine parse error as the wire\n * taxonomy's `bad_request` rather than letting it escape as a raw engine\n * error (which a server has no code to map, so it becomes a 500).\n *\n * Scoped to the one filter that carries a query language: `filter.search`\n * is FTS5 source text, and sanitizeFts5Query() repairs the common shapes\n * but claims no completeness against the grammar. Every other clause here\n * is built from parameters, so a parse error in one is this module's bug —\n * rethrown untouched, where it belongs.\n */\nconst asStackQueryError = <T>(query: StackQuery, run: () => T): T => {\n try {\n return run();\n } catch (err) {\n if (!query.filter?.search) throw err;\n throw new StackQueryError(\n `Search text could not be parsed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n};\n\nexport type SharedSqlRecordLogicDeps = {\n exec: SqlExecutor;\n};\n\n/** Top-level field names in `schema` whose kind is 'file-ref'. */\nconst fileRefFieldNames = (schema: Record<string, { kind?: string }>): string[] =>\n Object.keys(schema).filter((field) => schema[field].kind === 'file-ref');\n\nexport class SharedSqlRecordLogic {\n /**\n * Per-typeId cache of file-ref field names, so syncFileRefs() doesn't\n * hit the `types` table and re-parse schema JSON on every write.\n * Populated eagerly in saveType() and lazily via getFileRefFields().\n */\n private readonly fileRefFieldsByType = new Map<string, string[]>();\n\n constructor(private readonly deps: SharedSqlRecordLogicDeps) {}\n\n private get exec(): SqlExecutor {\n return this.deps.exec;\n }\n\n /**\n * SQL fragment (plus its bind params) that gates a records-table\n * UPDATE/DELETE on the opt-in `expectedVersion` precondition. Empty when\n * expectedVersion is omitted, keeping today's unconditional behavior.\n */\n private versionGuard(expectedVersion: number | undefined): { clause: string; params: unknown[] } {\n return expectedVersion === undefined\n ? { clause: '', params: [] }\n : { clause: ' AND version = ?', params: [expectedVersion] };\n }\n\n /**\n * Precondition check for mutations that can't fold expectedVersion into\n * their primary UPDATE's WHERE clause: patchContent/restoreVersion need\n * fts.remove() to run *before* the records-table content changes, so\n * the check happens first, standalone.\n */\n private checkExpectedVersion(record: StackRecord, expectedVersion: number | undefined): void {\n if (expectedVersion === undefined || record.version === expectedVersion) return;\n throw new StackVersionConflictError(\n `Record \"${record.id}\" is at version ${record.version}, expected ${expectedVersion}`,\n record.id,\n expectedVersion,\n record.version,\n );\n }\n\n /**\n * Called after a versionGuard()-gated statement affected zero rows.\n * Distinguishes \"record doesn't exist\" (StackNotFoundError) from \"it\n * exists but isn't at expectedVersion\" (StackVersionConflictError, with\n * the actual current version for the caller to act on).\n */\n private throwVersionConflict(id: string, expectedVersion: number | undefined): never {\n const row = this.exec.get<{ version: number }>('SELECT version FROM records WHERE id = ?', [\n id,\n ]);\n if (!row) throw new StackNotFoundError(`Record not found: \"${id}\"`);\n throw new StackVersionConflictError(\n `Record \"${id}\" is at version ${row.version}, expected ${expectedVersion}`,\n id,\n expectedVersion as number,\n row.version,\n );\n }\n\n // -------------------------------------------------------\n // Records\n // -------------------------------------------------------\n\n /**\n * A duplicate id hits the `records` PK — mapped to StackConflictError\n * instead of surfacing the raw engine exception, mirroring saveVersion's\n * collision mapping below.\n */\n async createRecord(record: StackRecord): Promise<StackRecord> {\n try {\n this.exec.run(\n `INSERT INTO records\n (id, type_id, created_at, updated_at, content, version,\n parent_id, entity_id, app_id, principal_id, updated_by, updated_via,\n deleted_at, unlisted_at, permissions)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n record.id,\n record.typeId,\n toMs(record.createdAt),\n toMs(record.updatedAt),\n JSON.stringify(record.content),\n record.version,\n record.parentId ?? null,\n record.entityId ?? null,\n record.appId ?? null,\n record.principalId ?? null,\n record.updatedBy ?? null,\n record.updatedVia ?? null,\n record.deletedAt ? toMs(record.deletedAt) : null,\n record.unlistedAt ? toMs(record.unlistedAt) : null,\n record.permissions ? JSON.stringify(record.permissions) : null,\n ],\n );\n } catch (err) {\n if (isUniqueConstraintViolation(err)) {\n throw new StackConflictError(`Record already exists: \"${record.id}\"`);\n }\n throw err;\n }\n\n if (record.associations?.length) {\n this.insertAssociations(record.id, record.associations);\n }\n\n fts5Strategy.insert(this.exec, record.id, JSON.stringify(record.content));\n this.syncFileRefs(record.id, record.typeId, record.content);\n return record;\n }\n\n async getRecord(id: string): Promise<StackRecord | null> {\n return this.readRecord(id);\n }\n\n /**\n * The synchronous read behind getRecord(). Callers inside exec.transaction()\n * use this one: an `await` inside that callback yields the microtask queue\n * mid-transaction, and the next operation to run would try to open one of\n * its own — transaction() requires a synchronous callback for exactly this\n * reason (see SqlExecutor.transaction).\n *\n * The same synchrony is what lets the post-commit reads below report the\n * version their own mutation produced: `getRecord()` runs its body before\n * the `await` yields, so nothing interleaves between the commit and the\n * read. A backend that made these reads genuinely asynchronous would open\n * that window, and a concurrent mutation could be reported under this\n * one's verb.\n */\n private readRecord(id: string): StackRecord | null {\n const row = this.exec.get<Record<string, unknown>>('SELECT * FROM records WHERE id = ?', [id]);\n if (!row) return null;\n const associations = this.getAssociationsForRecord(id);\n return rowToRecord(row, associations);\n }\n\n async patchContent(\n id: string,\n patch: Record<string, unknown | null>,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n const existing = await this.getRecord(id);\n if (!existing) throw new Error(`Record not found: \"${id}\"`);\n this.checkExpectedVersion(existing, opts.expectedVersion);\n\n const merged = applyMergePatch(existing.content, patch);\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n fts5Strategy.remove(this.exec, id);\n this.exec.run(\n `UPDATE records SET content = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?`,\n [\n JSON.stringify(merged),\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ],\n );\n fts5Strategy.insert(this.exec, id, JSON.stringify(merged));\n this.syncFileRefs(id, existing.typeId, merged);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after patchContent: \"${id}\"`);\n return updated;\n }\n\n async deleteRecord(\n id: string,\n opts: {\n hard?: boolean;\n expectedVersion?: number;\n snapshot?: RecordVersion;\n } & ActorOptions = {},\n ): Promise<StackRecord | null> {\n if (opts.hard) {\n return this.exec.transaction(() => this.hardDeleteRecord(id, opts.expectedVersion));\n } else {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const changed = this.exec.run(\n `UPDATE records SET deleted_at = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [\n toMs(new Date()),\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ...verParams,\n ],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n });\n }\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after deleteRecord: \"${id}\"`);\n return updated;\n }\n\n /**\n * Deletes a record's FTS entry, associations, versions, file-refs, and\n * row, returning the record as it stood — the last copy that will ever\n * exist, read here so that nothing can overtake it between the read and\n * the deletes. Null when there was no such record. No write() call —\n * callers batch it. expectedVersion is checked first so a lost CAS race\n * leaves nothing touched (children reference the row, so the check can't\n * fold into the final DELETE).\n */\n private hardDeleteRecord(id: string, expectedVersion?: number): StackRecord | null {\n const purged = this.readRecord(id);\n if (expectedVersion !== undefined) {\n if (!purged) throw new StackNotFoundError(`Record not found: \"${id}\"`);\n if (purged.version !== expectedVersion) {\n throw new StackVersionConflictError(\n `Record \"${id}\" is at version ${purged.version}, expected ${expectedVersion}`,\n id,\n expectedVersion,\n purged.version,\n );\n }\n }\n if (!purged) return null;\n fts5Strategy.remove(this.exec, id);\n this.exec.run('DELETE FROM associations WHERE record_id = ?', [id]);\n this.exec.run('DELETE FROM versions WHERE record_id = ?', [id]);\n this.exec.run('DELETE FROM file_refs WHERE record_id = ?', [id]);\n this.exec.run('DELETE FROM records WHERE id = ?', [id]);\n return purged;\n }\n\n async undeleteRecord(\n id: string,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const changed = this.exec.run(\n `UPDATE records SET deleted_at = NULL, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [toMs(new Date()), opts.updatedBy ?? null, opts.updatedVia ?? null, id, ...verParams],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after undelete: \"${id}\"`);\n return updated;\n }\n\n async setPermissions(\n id: string,\n permissions: Permission[],\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const changed = this.exec.run(\n `UPDATE records SET permissions = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [\n permissions.length ? JSON.stringify(permissions) : null,\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ...verParams,\n ],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after setPermissions: \"${id}\"`);\n return updated;\n }\n\n async setUnlisted(\n id: string,\n unlisted: boolean,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const now = toMs(new Date());\n const changed = this.exec.run(\n `UPDATE records SET unlisted_at = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [\n unlisted ? now : null,\n now,\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ...verParams,\n ],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after setUnlisted: \"${id}\"`);\n return updated;\n }\n\n async restoreVersion(\n id: string,\n version: number,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n const existing = await this.getRecord(id);\n if (!existing) throw new Error(`Record not found: \"${id}\"`);\n this.checkExpectedVersion(existing, opts.expectedVersion);\n\n const target = await this.getVersion(id, version);\n if (!target) throw new Error(`Version not found: ${id}@${version}`);\n\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n fts5Strategy.remove(this.exec, id);\n this.exec.run(\n `UPDATE records SET type_id = ?, content = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?`,\n [\n target.typeId,\n JSON.stringify(target.content),\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ],\n );\n if (target.associations !== undefined) {\n this.exec.run('DELETE FROM associations WHERE record_id = ?', [id]);\n if (target.associations.length) this.insertAssociations(id, target.associations);\n }\n fts5Strategy.insert(this.exec, id, JSON.stringify(target.content));\n this.syncFileRefs(id, target.typeId, target.content);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after restoreVersion: \"${id}\"`);\n return updated;\n }\n\n async commitMigration(\n id: string,\n toTypeId: TypeId,\n content: Record<string, unknown>,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n // Checked here rather than folded into the UPDATE's WHERE clause:\n // fts5Strategy.remove() has to run before the content changes, so the\n // precondition has to settle first — same shape as patchContent() and\n // restoreVersion().\n const existing = await this.getRecord(id);\n if (!existing) throw new Error(`Record not found: \"${id}\"`);\n this.checkExpectedVersion(existing, opts.expectedVersion);\n\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n fts5Strategy.remove(this.exec, id);\n this.exec.run(\n `UPDATE records SET type_id = ?, content = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?`,\n [\n toTypeId,\n JSON.stringify(content),\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ],\n );\n fts5Strategy.insert(this.exec, id, JSON.stringify(content));\n this.syncFileRefs(id, toTypeId, content);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after commitMigration: \"${id}\"`);\n return updated;\n }\n\n async queryRecords(query: StackQuery): Promise<QueryResult> {\n const { sql: where, params } = buildWhereClause(query);\n const order = buildOrderClause(query);\n const limit = query.limit ?? 50;\n\n // Fetch one extra to determine if there's a next page\n const rows = asStackQueryError(query, () =>\n this.exec.all<Record<string, unknown>>(\n `SELECT r.* FROM records r ${where} ${order} LIMIT ?`,\n [...params, limit + 1],\n ),\n );\n\n const hasMore = rows.length > limit;\n const page = hasMore ? rows.slice(0, limit) : rows;\n\n const records = page.map((row) => {\n const associations = this.getAssociationsForRecord(row.id as string);\n return rowToRecord(row, associations);\n });\n\n const countRows = asStackQueryError(query, () =>\n this.exec.all<{ total: number }>(`SELECT COUNT(*) as total FROM records r ${where}`, params),\n );\n const total = countRows[0]?.total ?? 0;\n\n const lastRecord = records[records.length - 1];\n const cursor = hasMore && lastRecord ? makeCursor(lastRecord, getSortField(query)) : null;\n\n return { records, cursor, total };\n }\n\n /**\n * Atomically verify fileId is unreferenced, then hard-delete its\n * metadata records (see deleteUnreferencedAttachmentRecords on the\n * adapter contract). A real SQL transaction of synchronous calls — no\n * `await` between the check and the deletes, so nothing interleaves.\n */\n async deleteUnreferencedAttachmentRecords(\n fileId: FileId,\n metadataTypeId: TypeId,\n ): Promise<StackRecord[]> {\n return this.exec.transaction(() => {\n const referenced = this.exec.all<{ found: number }>(\n `SELECT 1 as found FROM associations WHERE kind = 'attachment' AND file_id = ?\n UNION ALL\n SELECT 1 FROM file_refs WHERE file_id = ?\n LIMIT 1`,\n [fileId, fileId],\n );\n if (referenced.length) {\n throw new StackConflictError('Attachment is still referenced by one or more records');\n }\n\n const metaRows = this.exec.all<{ id: string }>(\n `SELECT id FROM records WHERE type_id = ? AND json_extract(content, '$.fileId') = ?`,\n [metadataTypeId, fileId],\n );\n const deleted: StackRecord[] = [];\n for (const row of metaRows) {\n const purged = this.hardDeleteRecord(row.id);\n if (purged) deleted.push(purged);\n }\n\n return deleted;\n });\n }\n\n // -------------------------------------------------------\n // Versions\n // -------------------------------------------------------\n\n async getVersions(id: string): Promise<RecordVersion[]> {\n const rows = this.exec.all<Record<string, unknown>>(\n 'SELECT * FROM versions WHERE record_id = ? ORDER BY version DESC',\n [id],\n );\n return rows.map(rowToVersion);\n }\n\n async getVersion(id: string, version: number): Promise<RecordVersion | null> {\n const rows = this.exec.all<Record<string, unknown>>(\n 'SELECT * FROM versions WHERE record_id = ? AND version = ?',\n [id, version],\n );\n return rows.length ? rowToVersion(rows[0]) : null;\n }\n\n /**\n * A (record_id, version) collision is rejected loudly via the UNIQUE\n * constraint, mapped to StackConflictError — never a silently discarded\n * snapshot leaving a hole in rollback history. See\n * docs/spec/versioning.md § Optimistic concurrency (`ifVersion`).\n */\n private insertVersionRow(id: string, version: RecordVersion): void {\n try {\n this.exec.run(\n `INSERT INTO versions\n (record_id, version, type_id, content, updated_at, entity_id,\n updated_by, updated_via, associations, permissions)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n id,\n version.version,\n version.typeId,\n JSON.stringify(version.content),\n toMs(version.updatedAt),\n version.entityId ?? null,\n version.updatedBy ?? null,\n version.updatedVia ?? null,\n version.associations ? JSON.stringify(version.associations) : null,\n version.permissions ? JSON.stringify(version.permissions) : null,\n ],\n );\n } catch (err) {\n if (isUniqueConstraintViolation(err)) {\n throw new StackConflictError(\n `Version ${version.version} already exists for record \"${id}\" — a concurrent ` +\n `writer raced past this version. Use ifVersion to detect this before it happens.`,\n );\n }\n throw err;\n }\n }\n\n private overwriteVersionRow(id: string, version: RecordVersion): void {\n this.exec.run(\n `UPDATE versions\n SET type_id = ?, content = ?, updated_at = ?, entity_id = ?, associations = ?, permissions = ?\n WHERE record_id = ? AND version = ?`,\n [\n version.typeId,\n JSON.stringify(version.content),\n toMs(version.updatedAt),\n version.entityId ?? null,\n version.associations ? JSON.stringify(version.associations) : null,\n version.permissions ? JSON.stringify(version.permissions) : null,\n id,\n version.version,\n ],\n );\n }\n\n /**\n * Standalone snapshot write for tooling and tests — loud on any\n * collision, since nothing here is about to bump the version. Mutating\n * methods take a `snapshot` option instead; see snapshotBeforeMutation.\n */\n async saveVersion(id: string, version: RecordVersion): Promise<void> {\n this.insertVersionRow(id, version);\n }\n\n /**\n * The snapshot half of a mutating method's atomic snapshot-then-mutate\n * transaction. A collision is a racing-writer conflict, except an orphan\n * row at the record's *current* version — recognized by version number\n * alone, never content — which is overwritten so the interrupted write\n * can complete. See docs/spec/versioning.md § Snapshot atomicity.\n */\n private snapshotBeforeMutation(id: string, version: RecordVersion): void {\n try {\n this.insertVersionRow(id, version);\n } catch (err) {\n if (!(err instanceof StackConflictError)) throw err;\n\n const row = this.exec.get<{ version: number }>('SELECT version FROM records WHERE id = ?', [\n id,\n ]);\n if (!row || row.version !== version.version) throw err;\n\n this.overwriteVersionRow(id, version);\n }\n }\n\n // -------------------------------------------------------\n // Types\n // -------------------------------------------------------\n\n async saveType(type: StackType): Promise<void> {\n this.exec.run(\n `INSERT OR REPLACE INTO types\n (id, base_id, version, name, schema, schema_hash, migrates_from, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n type.id,\n type.baseId,\n type.version,\n type.name,\n JSON.stringify(type.schema),\n type.schemaHash,\n type.migratesFrom ?? null,\n toMs(type.createdAt),\n ],\n );\n this.fileRefFieldsByType.set(\n type.id,\n fileRefFieldNames(type.schema as Record<string, { kind?: string }>),\n );\n }\n\n async getType(id: TypeId): Promise<StackType | null> {\n const rows = this.exec.all<Record<string, unknown>>('SELECT * FROM types WHERE id = ?', [id]);\n return rows.length ? rowToType(rows[0]) : null;\n }\n\n async listTypes(): Promise<StackType[]> {\n const rows = this.exec.all<Record<string, unknown>>(\n 'SELECT * FROM types ORDER BY base_id, version',\n );\n return rows.map(rowToType);\n }\n\n // -------------------------------------------------------\n // Associations\n // -------------------------------------------------------\n\n async associate(\n recordId: string,\n association: Association,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(recordId, opts.snapshot);\n // Bump (and CAS-check) first, before the associations-table write, so\n // a lost race never partially applies.\n this.bumpVersion(recordId, opts);\n this.insertAssociations(recordId, [association]);\n });\n\n const updated = await this.getRecord(recordId);\n if (!updated) throw new Error(`Record not found after associate: \"${recordId}\"`);\n return updated;\n }\n\n async dissociate(\n recordId: string,\n association: Association,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(recordId, opts.snapshot);\n this.bumpVersion(recordId, opts);\n this.exec.run(\n `DELETE FROM associations\n WHERE record_id = ?\n AND kind = ?\n AND label = ?\n AND file_id = ?\n AND related_scope = ?\n AND related_id = ?\n AND related_ns = ?\n AND related_stack = ?`,\n [recordId, association.kind, association.label, ...associationKeyColumns(association)],\n );\n });\n\n const updated = await this.getRecord(recordId);\n if (!updated) throw new Error(`Record not found after dissociate: \"${recordId}\"`);\n return updated;\n }\n\n private bumpVersion(id: string, opts: ExpectedVersionOptions & ActorOptions = {}): void {\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const changed = this.exec.run(\n `UPDATE records SET version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [toMs(new Date()), opts.updatedBy ?? null, opts.updatedVia ?? null, id, ...verParams],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n }\n\n /**\n * FK enforcement (PRAGMA_FOREIGN_KEYS_ON) means inserting an\n * association against a record that doesn't exist throws — mapped\n * here to StackNotFoundError so associate() on a nonexistent record\n * fails loudly instead of silently creating an orphan row.\n */\n private insertAssociations(recordId: string, associations: Association[]): void {\n for (const assoc of associations) {\n try {\n this.exec.run(\n `INSERT OR IGNORE INTO associations\n (record_id, kind, label, file_id, related_scope, related_id, related_ns, related_stack)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [recordId, assoc.kind, assoc.label, ...associationKeyColumns(assoc)],\n );\n } catch (err) {\n if (isForeignKeyViolation(err)) {\n throw new StackNotFoundError(`Record not found: \"${recordId}\"`);\n }\n throw err;\n }\n }\n }\n\n private getAssociationsForRecord(recordId: string): Association[] {\n const rows = this.exec.all<Record<string, unknown>>(\n 'SELECT * FROM associations WHERE record_id = ?',\n [recordId],\n );\n return rows.map(rowToAssociation);\n }\n\n /**\n * File-ref field names for typeId, from the in-memory cache — falling\n * back to a `types` lookup (and populating the cache) only for a typeId\n * this process hasn't seen via saveType() yet, e.g. right after open().\n */\n private getFileRefFields(typeId: string): string[] {\n const cached = this.fileRefFieldsByType.get(typeId);\n if (cached) return cached;\n\n const typeRow = this.exec.get<{ schema: string }>('SELECT schema FROM types WHERE id = ?', [\n typeId,\n ]);\n const fields = typeRow\n ? fileRefFieldNames(JSON.parse(typeRow.schema) as Record<string, { kind?: string }>)\n : [];\n this.fileRefFieldsByType.set(typeId, fields);\n return fields;\n }\n\n /**\n * Replace a record's file_refs rows with whatever its content currently\n * holds in top-level file-ref fields, on every write that can change\n * content or typeId, so the index never drifts. Only top-level scalars\n * are indexed; the schema lookup is cached (fileRefFieldsByType).\n */\n private syncFileRefs(recordId: string, typeId: string, content: Record<string, unknown>): void {\n this.exec.run('DELETE FROM file_refs WHERE record_id = ?', [recordId]);\n\n const fields = this.getFileRefFields(typeId);\n for (const field of fields) {\n const value = content[field];\n if (typeof value === 'string') {\n this.exec.run(\n 'INSERT OR IGNORE INTO file_refs (record_id, field, file_id) VALUES (?, ?, ?)',\n [recordId, field, value],\n );\n }\n }\n }\n}\n","import type { SqlExecutor } from '@haverstack/sqlite-shared/record';\n\n/**\n * Normalizes a Durable Object's ctx.storage.sql to SqlExecutor.\n *\n * Two things don't map onto node:sqlite's shape the way the rest of this\n * interface does:\n *\n * - There is no get/all/run split — sql.exec() always returns a cursor.\n * get()/all() read it via .toArray(); run() reads .rowsWritten for the\n * affected-row count run()'s contract promises.\n * - Raw BEGIN/COMMIT/ROLLBACK are rejected outright by DO SQLite (verified\n * against the real Workers runtime, not assumed) — its storage has no\n * notion of a transaction left open across separate exec() calls, and\n * each statement auto-commits the instant it runs, so no-op'ing those\n * three strings would silently break atomicity, not preserve it. The\n * platform's real primitive is ctx.storage.transactionSync(fn), which\n * SqlExecutor.transaction() reaches directly — see docs/spec/adapters.md\n * § Concurrency & storage ownership.\n */\nexport class DurableObjectSqliteExecutor implements SqlExecutor {\n constructor(private readonly storage: DurableObjectStorage) {}\n\n private get sql(): SqlStorage {\n return this.storage.sql;\n }\n\n exec(sql: string): void {\n this.sql.exec(sql);\n }\n\n run(sql: string, params: readonly unknown[] = []): number {\n const cursor = this.sql.exec(sql, ...(params as SqlStorageValue[]));\n return cursor.rowsWritten;\n }\n\n get<T = Record<string, unknown>>(sql: string, params: readonly unknown[] = []): T | undefined {\n const rows = this.sql.exec(sql, ...(params as SqlStorageValue[])).toArray();\n return rows[0] as T | undefined;\n }\n\n all<T = Record<string, unknown>>(sql: string, params: readonly unknown[] = []): T[] {\n return this.sql.exec(sql, ...(params as SqlStorageValue[])).toArray() as T[];\n }\n\n transaction<T>(fn: () => T): T {\n return this.storage.transactionSync(fn);\n }\n}\n","/**\n * Haverstack — Durable Object SQLite Record Adapter\n * -------------------------------------------------------\n * Implements StackRecordAdapter over a Cloudflare Durable Object's SQLite\n * storage (ctx.storage.sql). Full-text search uses FTS5, same as\n * record-adapter-sqlite — DO's SQLite build ships it.\n *\n * Ownership and durability come from the platform, not from anything this\n * class does: a Durable Object id maps to exactly one running instance,\n * so there is no separate lock file the way record-adapter-sqlite needs\n * one for real files (see docs/spec/adapters.md § Concurrency & storage\n * ownership) — the DO *is* the lock. There is likewise no persist/flush\n * step: every write through ctx.storage.sql is durable by the time the\n * call returns, so flush()/close() are no-ops kept only to satisfy the\n * optional StackRecordAdapter methods.\n *\n * This class itself is a thin binding: schema setup and the one piece of\n * genuinely engine-specific wiring — SqlExecutor.transaction() reaching\n * ctx.storage.transactionSync() instead of raw SQL BEGIN/COMMIT/ROLLBACK,\n * which DO SQLite rejects outright — live here (see executor.ts). The\n * actual StackRecordAdapter logic lives in @haverstack/sqlite-shared's\n * SharedSqlRecordLogic, exactly as it does for record-adapter-sqlite.\n */\n\nimport type { StackType, TypeId, FileId, RecordVersion, ActorOptions } from '@haverstack/core';\nimport type {\n StackRecord,\n StackQuery,\n QueryResult,\n Association,\n Permission,\n} from '@haverstack/core';\nimport type { StackRecordAdapter, AdapterCapabilities } from '@haverstack/core/adapter';\nimport {\n RECORD_SCHEMA_SQL,\n FTS5_SCHEMA_SQL,\n PRAGMA_FOREIGN_KEYS_ON,\n insertConfigRecord,\n readStackConfig,\n SharedSqlRecordLogic,\n} from '@haverstack/sqlite-shared/record';\nimport { DurableObjectSqliteExecutor } from './executor.js';\n\n// -------------------------------------------------------\n// Types\n// -------------------------------------------------------\n\nexport type DoRecordCreateOptions = {\n /** Entity ID of the stack owner. Ignored if the DO's storage already has a config record. */\n entityId: string;\n /** IANA timezone string e.g. \"America/New_York\". Optional passthrough app metadata — no default. */\n timezone?: string;\n};\n\n// -------------------------------------------------------\n// DoSQLiteRecordAdapter\n// -------------------------------------------------------\n\nexport class DoSQLiteRecordAdapter implements StackRecordAdapter {\n readonly capabilities: AdapterCapabilities = {\n fullTextSearch: true,\n contentFieldQuery: true,\n nestedContentQuery: true,\n sortableFields: ['createdAt', 'updatedAt', 'version'],\n maxAttachmentBytes: null,\n maxContentBytes: null,\n };\n\n ownerEntityId!: string;\n timezone: string | undefined;\n\n private readonly record: SharedSqlRecordLogic;\n\n private constructor(private readonly exec: DurableObjectSqliteExecutor) {\n this.record = new SharedSqlRecordLogic({ exec });\n }\n\n /**\n * Create (or reattach to) the adapter for a DO instance. There is no\n * initialize()/open() split the way file-based adapters need one: a DO\n * id either already has a config record (a previous call created it —\n * reattach, opts.entityId/timezone ignored in favor of what's stored)\n * or it doesn't (first call — opts.entityId/timezone become the config).\n * Schema DDL is `CREATE TABLE IF NOT EXISTS`, so running it every call\n * is idempotent and cheap.\n */\n static async create(\n storage: DurableObjectStorage,\n opts: DoRecordCreateOptions,\n ): Promise<DoSQLiteRecordAdapter> {\n const exec = new DurableObjectSqliteExecutor(storage);\n exec.exec(RECORD_SCHEMA_SQL);\n exec.exec(FTS5_SCHEMA_SQL);\n exec.exec(PRAGMA_FOREIGN_KEYS_ON);\n // DO SQLite manages its own durability and rejects PRAGMA journal_mode\n // outright (\"not authorized\") — verified against the real runtime, not\n // assumed — so unlike record-adapter-sqlite, no WAL pragma runs here.\n\n const adapter = new DoSQLiteRecordAdapter(exec);\n const existing = exec.get<{ content: string }>(\n `SELECT content FROM records WHERE id = '_config'`,\n );\n if (existing) {\n const config = readStackConfig(exec);\n adapter.ownerEntityId = config.entityId;\n adapter.timezone = config.timezone;\n } else {\n insertConfigRecord(exec, opts.entityId, opts.timezone);\n adapter.ownerEntityId = opts.entityId;\n adapter.timezone = opts.timezone;\n }\n return adapter;\n }\n\n // -------------------------------------------------------\n // Records\n // -------------------------------------------------------\n\n createRecord(record: StackRecord): Promise<StackRecord> {\n return this.record.createRecord(record);\n }\n\n getRecord(id: string): Promise<StackRecord | null> {\n return this.record.getRecord(id);\n }\n\n patchContent(\n id: string,\n patch: Record<string, unknown | null>,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.patchContent(id, patch, opts);\n }\n\n deleteRecord(\n id: string,\n opts?: { hard?: boolean; expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord | null> {\n return this.record.deleteRecord(id, opts);\n }\n\n undeleteRecord(\n id: string,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.undeleteRecord(id, opts);\n }\n\n setPermissions(\n id: string,\n permissions: Permission[],\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.setPermissions(id, permissions, opts);\n }\n\n setUnlisted(\n id: string,\n unlisted: boolean,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.setUnlisted(id, unlisted, opts);\n }\n\n restoreVersion(\n id: string,\n version: number,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.restoreVersion(id, version, opts);\n }\n\n commitMigration(\n id: string,\n toTypeId: TypeId,\n content: Record<string, unknown>,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.commitMigration(id, toTypeId, content, opts);\n }\n\n queryRecords(query: StackQuery): Promise<QueryResult> {\n return this.record.queryRecords(query);\n }\n\n deleteUnreferencedAttachmentRecords(\n fileId: FileId,\n metadataTypeId: TypeId,\n ): Promise<StackRecord[]> {\n return this.record.deleteUnreferencedAttachmentRecords(fileId, metadataTypeId);\n }\n\n // -------------------------------------------------------\n // Versions\n // -------------------------------------------------------\n\n getVersions(id: string): Promise<RecordVersion[]> {\n return this.record.getVersions(id);\n }\n\n getVersion(id: string, version: number): Promise<RecordVersion | null> {\n return this.record.getVersion(id, version);\n }\n\n saveVersion(id: string, version: RecordVersion): Promise<void> {\n return this.record.saveVersion(id, version);\n }\n\n // -------------------------------------------------------\n // Types\n // -------------------------------------------------------\n\n saveType(type: StackType): Promise<void> {\n return this.record.saveType(type);\n }\n\n getType(id: TypeId): Promise<StackType | null> {\n return this.record.getType(id);\n }\n\n listTypes(): Promise<StackType[]> {\n return this.record.listTypes();\n }\n\n // -------------------------------------------------------\n // Associations\n // -------------------------------------------------------\n\n associate(\n recordId: string,\n association: Association,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.associate(recordId, association, opts);\n }\n\n dissociate(\n recordId: string,\n association: Association,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.dissociate(recordId, association, opts);\n }\n\n // -------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------\n\n /** No-op: every write through ctx.storage.sql is already durable. */\n async flush(): Promise<void> {}\n\n /** No-op: no lock file, no connection to release — the DO's own lifecycle governs storage. */\n async close(): Promise<void> {}\n}\n\nexport { DurableObjectSqliteExecutor } from './executor.js';\n"]}
1
+ {"version":3,"sources":["../../sqlite-shared/src/schema.ts","../../sqlite-shared/src/cursor.ts","../../sqlite-shared/src/fts5.ts","../../sqlite-shared/src/query.ts","../../sqlite-shared/src/mappers.ts","../../sqlite-shared/src/executor.ts","../../sqlite-shared/src/config.ts","../../sqlite-shared/src/record-logic.ts","../src/executor.ts","../src/index.ts"],"names":["StackQueryError","contentSortEntry"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Schema DDL shared by SQLite-backed record adapters. TOKENS_SCHEMA_SQL\n * is split out from RECORD_SCHEMA_SQL because token storage lives in its\n * own file, never bundled with records — see NativeTokenStore in\n * record-adapter-sqlite and the StackTokenStore portability rationale\n * (docs/spec/wire-format.md § Authentication).\n */\n\nexport const RECORD_SCHEMA_SQL = `\n CREATE TABLE IF NOT EXISTS records (\n id TEXT PRIMARY KEY,\n type_id TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n content TEXT NOT NULL CHECK (json_valid(content)),\n version INTEGER NOT NULL DEFAULT 1,\n parent_id TEXT,\n entity_id TEXT,\n app_id TEXT,\n principal_id TEXT,\n updated_by TEXT,\n updated_via TEXT,\n deleted_at INTEGER,\n unlisted_at INTEGER,\n permissions TEXT CHECK (permissions IS NULL OR json_valid(permissions))\n ) STRICT;\n\n -- A relationship's target is (related_scope, related_id) plus one\n -- qualifier: related_stack for a record in another stack, related_ns for\n -- a foreign namespace. All four are in the primary key, so two targets\n -- differing only by namespace are two associations.\n CREATE TABLE IF NOT EXISTS associations (\n record_id TEXT NOT NULL REFERENCES records(id),\n kind TEXT NOT NULL CHECK (kind IN ('tag', 'attachment', 'relationship')),\n label TEXT NOT NULL,\n file_id TEXT NOT NULL DEFAULT '',\n related_scope TEXT NOT NULL DEFAULT '' CHECK (related_scope IN ('', 'record', 'entity', 'external')),\n related_id TEXT NOT NULL DEFAULT '',\n related_ns TEXT NOT NULL DEFAULT '',\n related_stack TEXT NOT NULL DEFAULT '',\n PRIMARY KEY (record_id, kind, label, file_id, related_scope, related_id, related_ns, related_stack)\n ) STRICT;\n\n CREATE TABLE IF NOT EXISTS versions (\n record_id TEXT NOT NULL REFERENCES records(id),\n version INTEGER NOT NULL,\n type_id TEXT NOT NULL,\n content TEXT NOT NULL CHECK (json_valid(content)),\n updated_at INTEGER NOT NULL,\n entity_id TEXT,\n updated_by TEXT,\n updated_via TEXT,\n associations TEXT CHECK (associations IS NULL OR json_valid(associations)),\n permissions TEXT CHECK (permissions IS NULL OR json_valid(permissions)),\n PRIMARY KEY (record_id, version)\n ) STRICT;\n\n CREATE TABLE IF NOT EXISTS types (\n id TEXT PRIMARY KEY,\n base_id TEXT NOT NULL,\n version INTEGER NOT NULL,\n name TEXT NOT NULL,\n schema TEXT NOT NULL CHECK (json_valid(schema)),\n schema_hash TEXT NOT NULL,\n migrates_from TEXT,\n created_at INTEGER NOT NULL\n ) STRICT;\n\n -- One row per top-level file-ref content field on a record, kept in sync\n -- on every content/typeId write. Lets the attachmentFileId query filter\n -- and deleteAttachment()'s reference check see content-held file\n -- references, not just attachment associations.\n CREATE TABLE IF NOT EXISTS file_refs (\n record_id TEXT NOT NULL REFERENCES records(id),\n field TEXT NOT NULL,\n file_id TEXT NOT NULL,\n PRIMARY KEY (record_id, field)\n ) STRICT;\n\n -- One row per top-level scalar content field on a record, rewritten\n -- beside file_refs on every content/typeId write. Exactly one of\n -- num_value / text_key+text_value is set, by the field's declared kind:\n -- what a field orders as must not vary with the value a given record\n -- happens to hold. text_key is the folded form ordering compares\n -- (@haverstack/core's contentSortKey), text_value the stored one, which\n -- breaks a tie between two values that fold together.\n CREATE TABLE IF NOT EXISTS content_sort (\n record_id TEXT NOT NULL REFERENCES records(id),\n field TEXT NOT NULL,\n num_value REAL,\n text_key TEXT,\n text_value TEXT,\n PRIMARY KEY (record_id, field)\n ) STRICT;\n\n -- Indexes\n CREATE INDEX IF NOT EXISTS idx_records_type_id ON records(type_id);\n CREATE INDEX IF NOT EXISTS idx_records_parent_id ON records(parent_id);\n CREATE INDEX IF NOT EXISTS idx_records_entity_id ON records(entity_id);\n CREATE INDEX IF NOT EXISTS idx_records_app_id ON records(app_id);\n CREATE INDEX IF NOT EXISTS idx_records_principal_id ON records(principal_id);\n CREATE INDEX IF NOT EXISTS idx_records_deleted_at ON records(deleted_at);\n CREATE INDEX IF NOT EXISTS idx_records_unlisted_at ON records(unlisted_at);\n CREATE INDEX IF NOT EXISTS idx_records_created_at ON records(created_at);\n CREATE INDEX IF NOT EXISTS idx_records_updated_at ON records(updated_at);\n CREATE INDEX IF NOT EXISTS idx_assoc_record_id ON associations(record_id);\n CREATE INDEX IF NOT EXISTS idx_assoc_kind_label ON associations(kind, label);\n CREATE INDEX IF NOT EXISTS idx_assoc_kind_file_id ON associations(kind, file_id);\n CREATE INDEX IF NOT EXISTS idx_assoc_related ON associations(related_scope, related_ns, related_id);\n CREATE INDEX IF NOT EXISTS idx_types_base_id ON types(base_id);\n CREATE INDEX IF NOT EXISTS idx_file_refs_file_id ON file_refs(file_id);\n CREATE INDEX IF NOT EXISTS idx_content_sort_num ON content_sort(field, num_value, record_id);\n CREATE INDEX IF NOT EXISTS idx_content_sort_text ON content_sort(field, text_key, text_value, record_id);\n`;\n\n/**\n * Bearer-token storage backing StackTokenStore. Kept separate from\n * RECORD_SCHEMA_SQL so an adapter can put it in its own file — the\n * portable stack file shouldn't also carry a server's auth material.\n */\nexport const TOKENS_SCHEMA_SQL = `\n CREATE TABLE IF NOT EXISTS tokens (\n id TEXT PRIMARY KEY,\n token_hash TEXT NOT NULL UNIQUE,\n principal_id TEXT NOT NULL,\n subject_id TEXT NOT NULL,\n label TEXT,\n created_at INTEGER NOT NULL,\n expires_at INTEGER\n ) STRICT;\n\n CREATE INDEX IF NOT EXISTS idx_tokens_hash ON tokens(token_hash);\n`;\n\n/** The full-text index behind `StackQuery.filter.search` (see fts5.ts). */\nexport const FTS5_SCHEMA_SQL = `\n CREATE VIRTUAL TABLE IF NOT EXISTS records_fts USING fts5(\n content,\n content='records',\n content_rowid='rowid'\n );\n`;\n\n/**\n * Enforces the REFERENCES constraints declared above (off by default per\n * SQLite connection), so touching a nonexistent record fails loudly\n * instead of creating an orphan row. Adapters map the constraint\n * violation to StackNotFoundError at the call sites that can trigger it.\n */\nexport const PRAGMA_FOREIGN_KEYS_ON = `PRAGMA foreign_keys = ON;`;\n\n/**\n * WAL journaling: page-level writes, crash-safe without our own\n * temp-file-and-rename dance, and real SQLite file locking. Only\n * meaningful for a real file (a :memory: database silently ignores it).\n */\nexport const PRAGMA_JOURNAL_MODE_WAL = `PRAGMA journal_mode = WAL;`;\n","/**\n * Cursor encode/decode for keyset pagination. The payload is JSON in\n * base64 rather than delimited fields: a content sort carries the field's\n * stored text, and neither a field name nor a text value is constrained\n * to avoid whatever character a delimiter would claim. Uses btoa/atob\n * with an explicit UTF-8 step rather than Node's Buffer, so the codec\n * stays runtime-agnostic and a non-Latin-1 value survives the round trip.\n */\n\nimport { StackQueryError } from '@haverstack/core';\nimport { contentSortEntry } from '@haverstack/core/adapter';\nimport type { StackRecord, NativeSortField, QuerySort, ScalarFieldKind } from '@haverstack/core';\n\nexport type SortField = NativeSortField;\n\nexport const SORT_FIELDS: readonly SortField[] = ['createdAt', 'updatedAt', 'version'];\n\n/**\n * Which partition of the ordering a cursor sits in: a native column or a\n * content field's number, text, or absent value. The three content cases\n * are the boundary keyset pagination has to hold — a page that ended on\n * the last numeric value resumes at the first text one, not back at the\n * top of either.\n */\nexport type CursorKind = 'native' | 'num' | 'text' | 'absent';\n\nexport type DecodedCursor =\n | { kind: 'native'; field: SortField; value: number; id: string }\n | { kind: 'num'; field: string; value: number; id: string }\n | { kind: 'text'; field: string; value: string; id: string }\n | { kind: 'absent'; field: string; id: string };\n\nconst toBase64 = (text: string): string => {\n const bytes = new TextEncoder().encode(text);\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n};\n\nconst fromBase64 = (cursor: string): string => {\n const binary = atob(cursor);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n};\n\nexport const encodeCursor = (decoded: DecodedCursor): string =>\n toBase64(\n JSON.stringify(\n decoded.kind === 'absent'\n ? { k: decoded.kind, f: decoded.field, i: decoded.id }\n : { k: decoded.kind, f: decoded.field, i: decoded.id, v: decoded.value },\n ),\n );\n\n/** Throws StackQueryError for any malformed, corrupt, or unrecognized cursor. */\nexport const decodeCursor = (cursor: string): DecodedCursor => {\n const malformed = () => new StackQueryError(`Invalid cursor: malformed \"${cursor}\"`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64(cursor));\n } catch {\n throw malformed();\n }\n if (!parsed || typeof parsed !== 'object') throw malformed();\n const { k, f, i, v } = parsed as Record<string, unknown>;\n if (typeof f !== 'string' || !f || typeof i !== 'string') throw malformed();\n\n if (k === 'absent') return { kind: 'absent', field: f, id: i };\n if (k === 'text') {\n if (typeof v !== 'string') throw malformed();\n return { kind: 'text', field: f, value: v, id: i };\n }\n if (k === 'num' || k === 'native') {\n if (typeof v !== 'number' || !isFinite(v)) {\n throw new StackQueryError(`Invalid cursor: non-numeric sort value`);\n }\n if (k === 'native') {\n if (!SORT_FIELDS.includes(f as SortField)) {\n throw new StackQueryError(`Invalid cursor: unknown sort field \"${f}\"`);\n }\n return { kind: 'native', field: f as SortField, value: v, id: i };\n }\n return { kind: 'num', field: f, value: v, id: i };\n }\n throw malformed();\n};\n\nexport const getSortField = (query: { sort?: QuerySort }): SortField =>\n query.sort?.field ?? 'createdAt';\n\nexport const getSortColumn = (field: SortField): string =>\n field === 'createdAt' ? 'created_at' : field === 'updatedAt' ? 'updated_at' : 'version';\n\n/**\n * The cursor naming the last record of a page: where the next page\n * resumes. A content sort re-derives the record's ordered value from its\n * stored content rather than reading it back out of the index, so the\n * cursor and the index can only ever disagree if the write path is\n * broken — in which case pagination is not the thing to paper over.\n */\nexport const makeCursor = (\n record: StackRecord,\n sort: QuerySort | undefined,\n kindOf?: (record: StackRecord, field: string) => ScalarFieldKind | undefined,\n): string => {\n const contentField = sort?.contentField;\n if (contentField === undefined) {\n const field = getSortField({ sort });\n const value =\n field === 'updatedAt'\n ? record.updatedAt.getTime()\n : field === 'version'\n ? record.version\n : record.createdAt.getTime();\n return encodeCursor({ kind: 'native', field, value, id: record.id });\n }\n\n const kind = kindOf?.(record, contentField);\n const entry = kind ? contentSortEntry(kind, record.content[contentField]) : null;\n if (!entry) return encodeCursor({ kind: 'absent', field: contentField, id: record.id });\n return entry.kind === 'num'\n ? encodeCursor({ kind: 'num', field: contentField, value: entry.num, id: record.id })\n : encodeCursor({ kind: 'text', field: contentField, value: entry.text, id: record.id });\n};\n","/**\n * FTS5 query sanitization and indexing — the full-text search half of the\n * shared record logic.\n *\n * FTS5's query language supports operators (AND/OR/NOT, phrases, NEAR,\n * wildcards) that can be expensive or cause parse errors with untrusted\n * input, so a search string is rewritten before it reaches the engine:\n *\n * kept: AND/OR/NOT (with a left operand), phrase queries (\"…\"), implicit AND\n * removed: wildcards (*), NEAR(...) (terms kept, wrapper dropped), every\n * character outside the allow-list, bare NOT (no left operand),\n * operators with no right operand\n * capped: parenthesis nesting depth (default: 2)\n * closed: an odd trailing quote\n *\n * Two FTS5 grammar details drive the less obvious rules:\n *\n * - FTS5's NEAR is a function-call form — `NEAR(a b, 10)`. Deleting the\n * whole span would also delete the terms inside it, which can leave a\n * dangling operator when NEAR(...) sits next to AND/OR (e.g. \"x AND\n * NEAR(a b, 5)\" -> \"x AND\" — a syntax error). So this keeps the terms\n * and drops only the NEAR(...) wrapper and distance.\n * - FTS5 has real column-filter syntax and errors hard on any name but\n * the table's actual columns. It is wider than `colname:term`: `-name`\n * and `{a b}` filter columns with no colon at all, so an ordinary\n * hyphenated word is read as one. Since records_fts has exactly one\n * column and callers have no business targeting columns explicitly,\n * everything outside a phrase is reduced to an allow-list rather than\n * validated — see OUTSIDE_PHRASE_DISALLOWED.\n *\n * The rewrite is best-effort and makes no completeness claim against\n * FTS5's grammar — which is why the caller wraps execution as well, so a\n * case this misses surfaces as a StackQueryError rather than a raw engine\n * error. Search text is what a person typed into a box: an unbalanced\n * quote or a trailing \"AND\" is ordinary input, not a malformed request.\n *\n * What this bounds is the grammar, not the cost: a syntactically modest\n * search over a large index can still be expensive, and node:sqlite blocks\n * the calling thread, so there is no timeout to set from inside the call.\n * Bounding execution time belongs to whoever drives the engine under load\n * — see docs/spec/wire-format.md § Bounding query cost for the server-side\n * expectation and the `timeout` wire error that goes with it.\n */\n\nimport type { SqlExecutor } from './executor.js';\n\n/**\n * Outside a phrase, everything but a letter, digit, mark, underscore,\n * whitespace, paren or quote becomes a separator.\n *\n * An allow-list rather than a list of metacharacters to strip, because\n * FTS5's column-filter syntax is wider than it looks and an enumeration\n * kept missing parts of it: `-name` and `{a b}` filter columns with no\n * colon in sight, so `-cats` and even the hyphen inside `cats-dogs` were\n * read as column names and answered \"no such column\". `+` is a bare\n * syntax error. Listing what may pass means the next piece of syntax\n * nobody here anticipated arrives as a separator instead of as an error.\n *\n * Nothing is lost by it: FTS5's default tokenizer already splits terms on\n * these characters, so `cats-dogs` searches for the same two tokens it was\n * indexed as.\n */\nconst OUTSIDE_PHRASE_DISALLOWED = /[^\\p{L}\\p{N}\\p{M}_\\s()\"]/gu;\n\nexport const sanitizeFts5Query = (query: string, maxDepth = 2): string => {\n if (!query) return '';\n\n // Drop control characters, inside a phrase as well as outside. A NUL in\n // particular ends the string early for SQLite's C API, so the engine sees\n // an opening quote whose closing one is past the truncation and reports an\n // unterminated string; none of them can match an indexed token anyway.\n let clean = query.replace(/[\\p{Cc}\\p{Cf}]/gu, ' ');\n\n // Remove wildcards\n clean = clean.replace(/\\*/g, '');\n\n // Replace NEAR(terms, distance) with just its terms — see note above.\n clean = clean.replace(/\\bNEAR\\s*\\(\\s*([^,)]*)(?:,[^)]*)?\\)/gi, '$1');\n\n // A NEAR( with no closing paren isn't the function-call form the rule\n // above rewrites, and the paren auto-close below would hand FTS5 a NEAR\n // call it never meant to write. Keep the grouping, drop the keyword.\n clean = clean.replace(/\\bNEAR\\s*\\(/gi, '(');\n\n // Reduce to the characters that carry meaning outside a phrase — see note\n // above. Everything else becomes a separator, which is what FTS5's own\n // tokenizer would do with it anyway.\n clean = clean.replace(/\"[^\"]*\"|[^\"]+/g, (span) =>\n span.startsWith('\"') ? span : span.replace(OUTSIDE_PHRASE_DISALLOWED, ' '),\n );\n\n // Strip bare NOT with no left operand — FTS5 requires \"term NOT term\", not \"NOT term\"\n clean = clean.replace(/(?:^|\\(\\s*)NOT\\s+/gi, (m) => m.replace(/NOT\\s+/i, ''));\n\n // Enforce max nesting depth; replace excess ( with a space and discard\n // unmatched ). Parens inside a phrase are literal text to FTS5, so the\n // depth count steps over quoted spans rather than reading them as syntax.\n let currentDepth = 0;\n let inPhrase = false;\n let result = '';\n for (const char of clean) {\n if (char === '\"') {\n inPhrase = !inPhrase;\n result += char;\n } else if (inPhrase) {\n result += char;\n } else if (char === '(') {\n if (currentDepth < maxDepth) {\n currentDepth++;\n result += char;\n } else result += ' ';\n } else if (char === ')') {\n if (currentDepth > 0) {\n currentDepth--;\n result += char;\n } else result += ' ';\n } else {\n result += char;\n }\n }\n\n // Close an odd trailing quote before the parens, so the added ) lands\n // outside the phrase rather than becoming a literal character in it.\n if (inPhrase) result += '\"';\n\n // Auto-close any unclosed parens\n if (currentDepth > 0) result += ')'.repeat(currentDepth);\n\n // Remove empty paren pairs left behind by NEAR/NOT stripping, and drop\n // operators left without an operand. Looped together because each can\n // create work for the other: removing an operator can empty a paren pair\n // (\"(a AND)\" -> \"(a)\" -> ...), and removing a pair can strand the\n // operator that pointed at it (\"x AND ()\" -> \"x AND\").\n let prev: string;\n do {\n prev = result;\n // An empty phrase matches nothing and is the one construct whose\n // quotes can re-pair differently once a rule inserts text beside it.\n result = result.replace(/\"\"/g, ' ');\n result = result.replace(/\\(\\s*\\)/g, ' ');\n result = dropDanglingOperators(result);\n result = restoreImplicitAnd(result);\n } while (result !== prev);\n\n return result.replace(/\\s+/g, ' ').replace(/\\(\\s+/g, '(').replace(/\\s+\\)/g, ')').trim();\n};\n\n/** An operator FTS5 requires a term on both sides of. */\nconst OPERATOR = String.raw`(?:AND|OR|NOT)`;\n\n/**\n * Stands in for a phrase while operators are rewritten. A private-use\n * character is not a word character, so `\\b` still sees the operator\n * beside it, while `^`, `$` and the paren anchors see an operand — which\n * is what a phrase is. Nothing reaches here holding one: `clean` is\n * rewritten from the caller's string, and a literal U+E000 in it would be\n * a term character FTS5 tokenizes away regardless.\n */\nconst PHRASE_HOLE = '\\u{E000}';\n\n/**\n * Drop operators left without an operand — the shape a person types on\n * their way to a longer query (\"cats AND\") or lands on once the rules\n * above have removed a term. FTS5 rejects each of them outright, so what\n * would otherwise be a syntax error becomes the search for the terms that\n * are actually there.\n *\n * Phrases are masked out first: `\"cats AND dogs\"` is literal text to FTS5,\n * and rewriting inside it would change what the user asked for.\n */\nconst dropDanglingOperators = (query: string): string =>\n withPhrasesMasked(query, (masked) => {\n let out = masked;\n let prev: string;\n do {\n prev = out;\n // A run of operators keeps only the first — the rest have no left operand.\n out = out.replace(\n new RegExp(String.raw`\\b(${OPERATOR})\\b(?:\\s+\\b${OPERATOR}\\b)+`, 'gi'),\n '$1',\n );\n // No left operand: at the start of the query or straight after \"(\".\n out = out.replace(new RegExp(String.raw`(^|\\()\\s*\\b${OPERATOR}\\b\\s*`, 'gi'), '$1');\n // No right operand: at the end of the query or straight before \")\".\n out = out.replace(new RegExp(String.raw`\\s*\\b${OPERATOR}\\b\\s*($|\\))`, 'gi'), '$1');\n } while (out !== prev);\n return out;\n });\n\n/**\n * Run `rewrite` over the query with each phrase swapped for PHRASE_HOLE,\n * then put the phrases back in order. Every rule that reasons about what\n * sits beside an operator or a paren needs this: inside a phrase the text\n * is literal, and from outside one the phrase is a single operand.\n */\nconst withPhrasesMasked = (query: string, rewrite: (masked: string) => string): string => {\n const phrases: string[] = [];\n const masked = query.replace(/\"[^\"]*\"/g, (phrase) => {\n phrases.push(phrase);\n return PHRASE_HOLE;\n });\n let i = 0;\n return rewrite(masked).replace(new RegExp(PHRASE_HOLE, 'g'), () => phrases[i++]);\n};\n\n/**\n * Put back the operator FTS5 requires around a group. Two terms side by\n * side are an implicit AND (`cats dogs`), but a group is not: `cats (dogs)`\n * is a syntax error where `cats AND (dogs)` is fine. Grouping survives a\n * rewrite that strips the operator which used to sit beside it, so this\n * names the AND that was always meant rather than dropping the parens.\n *\n * Both rules test a *whole* word against the operator list. Matching a\n * prefix instead reads the `D` of a preceding `AND` as a term and inserts\n * a second one beside it.\n *\n * Phrases are masked for the same reason as elsewhere — a phrase is an\n * operand, so `\"a b\" (c)` needs the operator too.\n */\nconst OPERAND_CHAR = String.raw`[\\p{L}\\p{N}_${PHRASE_HOLE}]`;\n\nconst isOperator = (word: string): boolean => /^(?:AND|OR|NOT)$/i.test(word);\n\nconst restoreImplicitAnd = (query: string): string =>\n withPhrasesMasked(query, (masked) =>\n masked\n // \")\" then an operand — \") cats\", \") (\", \") <phrase>\" — unless that\n // operand is an operator, which already joins the two sides.\n .replace(\n new RegExp(String.raw`\\)\\s*(?=${OPERAND_CHAR}|\\()`, 'gu'),\n (match, offset: number, whole: string) => {\n const next = /^\\s*([\\p{L}\\p{N}_]+)/u.exec(whole.slice(offset + match.length));\n return next && isOperator(next[1]) ? match : ') AND ';\n },\n )\n // An operand then \"(\", unless the operand is an operator.\n .replace(new RegExp(String.raw`(${OPERAND_CHAR}+)\\s*(?=\\()`, 'gu'), (match, word: string) =>\n isOperator(word) ? match : `${word} AND `,\n ),\n );\n\n// -------------------------------------------------------\n// Indexing strategy\n// -------------------------------------------------------\n\n/**\n * FTS5's external-content table needs its special `('delete', rowid,\n * content)` command — with the *old* content — to unindex a row; a plain\n * DELETE leaves stale, still-searchable entries. remove() therefore reads\n * the current rowid/content and must run before the caller's change.\n */\nexport const fts5Strategy = {\n /** Index a record that has no FTS entry yet (used right after an INSERT). */\n insert(exec: SqlExecutor, recordId: string, content: string): void {\n exec.run(`INSERT INTO records_fts(rowid, content) SELECT rowid, ? FROM records WHERE id = ?`, [\n content,\n recordId,\n ]);\n },\n /**\n * Remove a record's FTS entry. MUST be called before the records row's\n * content changes or the row is deleted — see the note above.\n */\n remove(exec: SqlExecutor, recordId: string): void {\n const row = exec.get<{ rowid: number; content: string }>(\n 'SELECT rowid, content FROM records WHERE id = ?',\n [recordId],\n );\n if (!row) return;\n exec.run(`INSERT INTO records_fts(records_fts, rowid, content) VALUES('delete', ?, ?)`, [\n row.rowid,\n row.content,\n ]);\n },\n};\n","/**\n * WHERE/ORDER clause building for StackQuery. Engine-independent SQL —\n * shared verbatim between SQLite-backed record adapters. The one caveat is\n * full-text search: `f.search` here assumes a `records_fts` virtual table\n * with a MATCH-able `content` column (see fts5.ts).\n */\n\nimport { StackQueryError, type StackQuery } from '@haverstack/core';\nimport { contentSortKey, parseContentFilterKey } from '@haverstack/core/adapter';\nimport {\n decodeCursor,\n getSortColumn,\n getSortField,\n type DecodedCursor,\n type SortField,\n} from './cursor.js';\nimport { sanitizeFts5Query } from './fts5.js';\n\nexport { getSortField, getSortColumn };\nexport type { SortField };\n\n/**\n * Reduce `sort.direction` to a keyword this module may interpolate into\n * SQL. Core's assertValidSort() already rejects anything else at the\n * invariant layer, but this builder writes the value straight into\n * `ORDER BY` and the cursor comparison, so it re-checks rather than trust\n * an upstream that a future refactor could bypass: an unvalidated direction\n * here is a SQL-injection sink. Anything but \"asc\" is treated as the\n * default \"desc\" only after passing the guard.\n */\nconst sqlDirection = (query: StackQuery): 'ASC' | 'DESC' => {\n const dir = query.sort?.direction ?? 'desc';\n if (dir !== 'asc' && dir !== 'desc') {\n throw new StackQueryError(`Invalid sort direction \"${dir}\": expected \"asc\" or \"desc\".`);\n }\n return dir === 'asc' ? 'ASC' : 'DESC';\n};\n\n/**\n * Normalize a json_each row's value so one array element and one bare\n * value are walked by the same clause: an array stands for its elements,\n * anything else for itself. See docs/spec/data-model.md § Nested content\n * paths.\n */\nconst spread = (alias: string): string =>\n `CASE WHEN ${alias}.type = 'array' THEN ${alias}.value ` +\n `WHEN ${alias}.type = 'object' THEN json_array(json(${alias}.value)) ` +\n `ELSE json_array(${alias}.value) END`;\n\n/**\n * The members of a json_each row that is an object, and none for a row\n * that is not — a path descending through a scalar reaches no value rather\n * than reaching an error.\n */\nconst members = (alias: string): string =>\n `CASE WHEN ${alias}.type = 'object' THEN ${alias}.value ELSE '{}' END`;\n\n/**\n * True when some value reached by `segments` satisfies `leaf`, applied to\n * the alias holding that value. Each segment is a bound parameter matched\n * against json_each's `key`, never interpolated into a path expression, so\n * no key can be read as syntax. See docs/spec/data-model.md § Filter.\n */\nconst contentPathExists = (\n segments: string[],\n leaf: (alias: string) => string,\n params: unknown[],\n nextAlias: () => string,\n): string => {\n // The walk is a flat join list, not a subquery per segment: both SQLite\n // builds cap expression-tree depth far below the segment cap, and\n // nesting spends that budget while a join list spends the join budget\n // the cap is actually sized against.\n let key = nextAlias();\n let value = nextAlias();\n const from = [`json_each(r.content) AS ${key}`, `json_each(${spread(key)}) AS ${value}`];\n const where = [`${key}.key = ?`];\n params.push(segments[0]);\n\n for (const segment of segments.slice(1)) {\n key = nextAlias();\n from.push(`json_each(${members(value)}) AS ${key}`);\n value = nextAlias();\n from.push(`json_each(${spread(key)}) AS ${value}`);\n where.push(`${key}.key = ?`);\n params.push(segment);\n }\n\n where.push(leaf(value));\n return `EXISTS (SELECT 1 FROM ${from.join(', ')} WHERE ${where.join(' AND ')})`;\n};\n\n/**\n * A scalar filter value never matches an object or array stored at the\n * path: json_each exposes those as their JSON text, which would compare\n * equal to a string spelling the same document while every non-SQL adapter\n * compares the value itself.\n */\nconst equalsLeaf = (alias: string): string =>\n `${alias}.type NOT IN ('object', 'array') AND ${alias}.value = ?`;\n\n/** The join alias the sort index is read through. */\nconst SORT_ALIAS = 'cs';\n\n/**\n * The sort index row for each record, or none — the source of every\n * `cs.*` term in the order and cursor clauses. A LEFT JOIN rather than an\n * inner one: a record holding no value at the sort field still belongs in\n * the result, at the end of it.\n */\nexport const buildFromClause = (query: StackQuery): { sql: string; params: unknown[] } => {\n const field = query.sort?.contentField;\n if (field === undefined) return { sql: 'records r', params: [] };\n return {\n sql: `records r LEFT JOIN content_sort ${SORT_ALIAS} ON ${SORT_ALIAS}.record_id = r.id AND ${SORT_ALIAS}.field = ?`,\n params: [field],\n };\n};\n\n/**\n * A cursor names a position in one ordering; carrying it into another\n * would silently resume somewhere arbitrary. The partition matters as\n * much as the field name — a cursor from a numeric page can't be read\n * against text values.\n */\nconst assertCursorMatchesSort = (cursor: DecodedCursor, query: StackQuery): void => {\n const contentField = query.sort?.contentField;\n if (cursor.kind === 'native') {\n const sortField = getSortField(query);\n if (contentField !== undefined || cursor.field !== sortField) {\n throw new StackQueryError(\n `Cursor sort field \"${cursor.field}\" does not match query sort field ` +\n `\"${contentField ?? sortField}\"`,\n );\n }\n return;\n }\n if (contentField === undefined || cursor.field !== contentField) {\n throw new StackQueryError(\n `Cursor sort field \"${cursor.field}\" does not match query sort field ` +\n `\"${contentField ?? getSortField(query)}\"`,\n );\n }\n};\n\n/**\n * Everything ordered after the cursor's record: a later partition, or the\n * same partition past the cursor's value and id. `rank` counts up in the\n * direction the query runs, so one comparison covers both directions and\n * keeps absent values at the end of each.\n */\nconst contentCursorCondition = (\n cursor: Exclude<DecodedCursor, { kind: 'native' }>,\n op: '>' | '<',\n params: unknown[],\n): string => {\n const ascending = op === '>';\n const numRank = ascending ? 0 : 1;\n const textRank = ascending ? 1 : 0;\n const rank = `CASE WHEN ${SORT_ALIAS}.record_id IS NULL THEN 2 WHEN ${SORT_ALIAS}.num_value IS NULL THEN ${textRank} ELSE ${numRank} END`;\n const cursorRank = cursor.kind === 'absent' ? 2 : cursor.kind === 'num' ? numRank : textRank;\n\n let tail: string;\n if (cursor.kind === 'num') {\n tail = `(${SORT_ALIAS}.num_value ${op} ? OR (${SORT_ALIAS}.num_value = ? AND r.id ${op} ?))`;\n params.push(cursor.value, cursor.value, cursor.id);\n } else if (cursor.kind === 'text') {\n const key = contentSortKey(cursor.value);\n tail =\n `(${SORT_ALIAS}.text_key ${op} ? OR (${SORT_ALIAS}.text_key = ? AND ` +\n `(${SORT_ALIAS}.text_value ${op} ? OR (${SORT_ALIAS}.text_value = ? AND r.id ${op} ?))))`;\n params.push(key, key, cursor.value, cursor.value, cursor.id);\n } else {\n tail = `r.id ${op} ?`;\n params.push(cursor.id);\n }\n\n return `((${rank}) > ${cursorRank} OR ((${rank}) = ${cursorRank} AND ${tail}))`;\n};\n\nexport const buildWhereClause = (query: StackQuery): { sql: string; params: unknown[] } => {\n const conditions: string[] = [\"r.id != '_config'\"];\n const params: unknown[] = [];\n const f = query.filter ?? {};\n\n if (!f.includeDeleted) {\n conditions.push('r.deleted_at IS NULL');\n }\n\n if (!f.includeUnlisted) {\n conditions.push('r.unlisted_at IS NULL');\n }\n\n if (f.typeId !== undefined) {\n const ids = Array.isArray(f.typeId) ? f.typeId : [f.typeId];\n conditions.push(`r.type_id IN (${ids.map(() => '?').join(',')})`);\n params.push(...ids);\n }\n\n if (f.parentId !== undefined) {\n if (f.parentId === null) {\n conditions.push('r.parent_id IS NULL');\n } else {\n conditions.push('r.parent_id = ?');\n params.push(f.parentId);\n }\n }\n\n if (f.appId !== undefined) {\n const ids = Array.isArray(f.appId) ? f.appId : [f.appId];\n conditions.push(`r.app_id IN (${ids.map(() => '?').join(',')})`);\n params.push(...ids);\n }\n\n if (f.entityId !== undefined) {\n const ids = Array.isArray(f.entityId) ? f.entityId : [f.entityId];\n conditions.push(`r.entity_id IN (${ids.map(() => '?').join(',')})`);\n params.push(...ids);\n }\n\n if (f.principalId !== undefined) {\n const ids = Array.isArray(f.principalId) ? f.principalId : [f.principalId];\n conditions.push(`r.principal_id IN (${ids.map(() => '?').join(',')})`);\n params.push(...ids);\n }\n\n if (f.createdAt?.after) {\n conditions.push('r.created_at > ?');\n params.push(f.createdAt.after.getTime());\n }\n if (f.createdAt?.before) {\n conditions.push('r.created_at < ?');\n params.push(f.createdAt.before.getTime());\n }\n if (f.updatedAt?.after) {\n conditions.push('r.updated_at > ?');\n params.push(f.updatedAt.after.getTime());\n }\n if (f.updatedAt?.before) {\n conditions.push('r.updated_at < ?');\n params.push(f.updatedAt.before.getTime());\n }\n\n // Every association filter below is a semi-join rather than a correlated\n // EXISTS, so the planner drives from the association side — reading the\n // matching rows through idx_assoc_kind_label / idx_assoc_kind_file_id /\n // idx_file_refs_file_id and looking up those records — instead of\n // scanning every record and probing for each. The work is proportional\n // to how many records match, not to how many the stack holds.\n\n // Tag filter — record must have ALL specified tags\n if (f.tags?.length) {\n for (const tag of f.tags) {\n conditions.push(\n `r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = 'tag' AND a.label = ?)`,\n );\n params.push(tag);\n }\n }\n\n // Attachment label filter\n if (f.hasAttachment) {\n conditions.push(\n `r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = 'attachment' AND a.label = ?)`,\n );\n params.push(f.hasAttachment);\n }\n\n // Attachment file ID filter — find records that reference a specific file,\n // either via an attachment association or a top-level file-ref content field\n if (f.attachmentFileId) {\n conditions.push(\n `(r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = 'attachment' AND a.file_id = ?)\n OR r.id IN (SELECT fr.record_id FROM file_refs fr WHERE fr.file_id = ?))`,\n );\n params.push(f.attachmentFileId, f.attachmentFileId);\n }\n\n // Relationship filter — each clause is an optional pattern, so a bare\n // label matches every target under it and an external target with no\n // `id` matches its whole namespace (docs/spec/data-model.md § Filter).\n // A target-bearing pattern reads through idx_assoc_related; a bare label\n // through idx_assoc_kind_label.\n if (f.relatedTo) {\n const clauses: string[] = [];\n const target = f.relatedTo.target;\n if (f.relatedTo.label !== undefined) {\n clauses.push('a.label = ?');\n params.push(f.relatedTo.label);\n }\n if (target) {\n clauses.push('a.related_scope = ?');\n params.push(target.scope);\n if (target.scope === 'record') {\n clauses.push('a.related_id = ?', 'a.related_stack = ?');\n params.push(target.recordId, target.stackUrl ?? '');\n } else if (target.scope === 'entity') {\n clauses.push('a.related_id = ?');\n params.push(target.entityId);\n } else {\n clauses.push('a.related_ns = ?');\n params.push(target.ns);\n if (target.id !== undefined) {\n clauses.push('a.related_id = ?');\n params.push(target.id);\n }\n }\n }\n conditions.push(\n `r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = 'relationship'` +\n clauses.map((c) => ` AND ${c}`).join('') +\n `)`,\n );\n }\n\n // Content field filters — a dot-separated path, matched element-wise\n // through arrays. A `null` value means \"no value at the path, or a value\n // that is null\" (docs/spec/data-model.md § Filter), which is the second\n // arm below: \"some value there is null\" OR \"nothing is there at all\".\n if (f.content) {\n let aliasSeq = 0;\n const nextAlias = () => `cp${aliasSeq++}`;\n for (const [key, value] of Object.entries(f.content)) {\n const segments = parseContentFilterKey(key);\n // undefined is the same absence null names — JSON drops it on the\n // way over the wire, so treating it as a value would make an\n // in-process query and its wire equivalent disagree.\n if (value === null || value === undefined) {\n const isNull = contentPathExists(segments, (a) => `${a}.value IS NULL`, params, nextAlias);\n const anyValue = contentPathExists(\n segments,\n (a) => `${a}.value IS NOT NULL`,\n params,\n nextAlias,\n );\n conditions.push(`(${isNull} OR NOT ${anyValue})`);\n } else {\n const match = contentPathExists(segments, equalsLeaf, params, nextAlias);\n params.push(value);\n conditions.push(match);\n }\n }\n }\n\n // Presence — the question an exact-match value cannot ask. Element-wise\n // like the content filter above: a path holds a value when at least one\n // non-null value is reachable at it. See docs/spec/data-model.md\n // § Filter.\n if (f.contentPresent?.length) {\n let aliasSeq = 0;\n const nextAlias = () => `pp${aliasSeq++}`;\n for (const key of f.contentPresent) {\n conditions.push(\n contentPathExists(\n parseContentFilterKey(key),\n (a) => `${a}.value IS NOT NULL`,\n params,\n nextAlias,\n ),\n );\n }\n }\n\n if (f.search) {\n const sanitized = sanitizeFts5Query(f.search);\n if (sanitized) {\n conditions.push(`r.rowid IN (SELECT rowid FROM records_fts WHERE records_fts MATCH ?)`);\n params.push(sanitized);\n } else {\n // A search that sanitizes to nothing (e.g. \"*\", punctuation-only) is\n // an honest zero-match result, not \"no filter\" — omitting the clause\n // would silently return the full table as the \"search result\".\n conditions.push('0');\n }\n }\n\n // Cursor (sort value + id for stable pagination)\n if (query.cursor) {\n const cursor = decodeCursor(query.cursor);\n assertCursorMatchesSort(cursor, query);\n const op = sqlDirection(query) === 'ASC' ? '>' : '<';\n if (cursor.kind === 'native') {\n const col = getSortColumn(cursor.field);\n conditions.push(`(r.${col} ${op} ? OR (r.${col} = ? AND r.id ${op} ?))`);\n params.push(cursor.value, cursor.value, cursor.id);\n } else {\n conditions.push(contentCursorCondition(cursor, op, params));\n }\n }\n\n return {\n sql: conditions.length ? `WHERE ${conditions.join(' AND ')}` : '',\n params,\n };\n};\n\nexport const buildOrderClause = (query: StackQuery): string => {\n const dir = sqlDirection(query);\n if (query.sort?.contentField === undefined) {\n return `ORDER BY r.${getSortColumn(getSortField(query))} ${dir}, r.id ${dir}`;\n }\n // Three leading terms before the value itself: absence last whichever\n // way the sort runs, then the numeric partition against the text one,\n // which does turn over with the direction because it is part of the\n // order between values. See docs/spec/data-model.md § Sorting by a\n // content field.\n return (\n `ORDER BY (${SORT_ALIAS}.record_id IS NULL) ASC, (${SORT_ALIAS}.num_value IS NULL) ${dir}, ` +\n `${SORT_ALIAS}.num_value ${dir}, ${SORT_ALIAS}.text_key ${dir}, ` +\n `${SORT_ALIAS}.text_value ${dir}, r.id ${dir}`\n );\n};\n","/**\n * Row <-> domain object mappers shared by every SQLite-backed record\n * adapter. Column names and JSON-encoding choices are the storage\n * contract; keeping one copy means the adapters can't drift on them.\n */\n\nimport type {\n StackRecord,\n StackType,\n RecordVersion,\n Association,\n RelationshipTarget,\n} from '@haverstack/core';\n\nexport const toMs = (d: Date): number => d.getTime();\nexport const fromMs = (ms: number): Date => new Date(ms);\n\nexport const rowToRecord = (\n row: Record<string, unknown>,\n associations: Association[],\n): StackRecord => {\n const record: StackRecord = {\n id: row.id as string,\n typeId: row.type_id as string,\n createdAt: fromMs(row.created_at as number),\n updatedAt: fromMs(row.updated_at as number),\n content: JSON.parse(row.content as string),\n version: row.version as number,\n };\n if (row.parent_id) record.parentId = row.parent_id as string;\n if (row.entity_id) record.entityId = row.entity_id as string;\n if (row.app_id) record.appId = row.app_id as string;\n if (row.principal_id) record.principalId = row.principal_id as string;\n if (row.updated_by) record.updatedBy = row.updated_by as string;\n if (row.updated_via) record.updatedVia = row.updated_via as string;\n if (row.deleted_at) record.deletedAt = fromMs(row.deleted_at as number);\n if (row.unlisted_at) record.unlistedAt = fromMs(row.unlisted_at as number);\n if (row.permissions) record.permissions = JSON.parse(row.permissions as string);\n if (associations.length) record.associations = associations;\n return record;\n};\n\nexport const rowToAssociation = (row: Record<string, unknown>): Association => {\n if (row.kind === 'tag') {\n return { kind: 'tag', label: row.label as string };\n }\n if (row.kind === 'attachment') {\n return {\n kind: 'attachment',\n label: row.label as string,\n fileId: row.file_id as string,\n };\n }\n // relationship\n return {\n kind: 'relationship',\n label: row.label as string,\n target: rowToTarget(row),\n };\n};\n\n/**\n * The five columns that identify an association, in the order every\n * INSERT and DELETE below binds them. One helper because the two must\n * agree exactly — a dissociate that bound them differently would delete\n * nothing and report success.\n */\nexport const associationKeyColumns = (a: Association): [string, string, string, string, string] => {\n if (a.kind === 'attachment') return [a.fileId, '', '', '', ''];\n if (a.kind !== 'relationship') return ['', '', '', '', ''];\n const t = a.target;\n if (t.scope === 'entity') return ['', 'entity', t.entityId, '', ''];\n if (t.scope === 'external') return ['', 'external', t.id, t.ns, ''];\n return ['', 'record', t.recordId, '', t.stackUrl ?? ''];\n};\n\nconst rowToTarget = (row: Record<string, unknown>): RelationshipTarget => {\n const id = row.related_id as string;\n if (row.related_scope === 'entity') return { scope: 'entity', entityId: id };\n if (row.related_scope === 'external') {\n return { scope: 'external', ns: row.related_ns as string, id };\n }\n const stackUrl = row.related_stack as string;\n return { scope: 'record', recordId: id, ...(stackUrl && { stackUrl }) };\n};\n\nexport const rowToType = (row: Record<string, unknown>): StackType => {\n const t: StackType = {\n id: row.id as string,\n baseId: row.base_id as string,\n version: row.version as number,\n name: row.name as string,\n schema: JSON.parse(row.schema as string),\n schemaHash: row.schema_hash as string,\n createdAt: fromMs(row.created_at as number),\n };\n if (row.migrates_from) t.migratesFrom = row.migrates_from as string;\n return t;\n};\n\nexport const rowToVersion = (row: Record<string, unknown>): RecordVersion => {\n const v: RecordVersion = {\n version: row.version as number,\n typeId: row.type_id as string,\n content: JSON.parse(row.content as string),\n updatedAt: fromMs(row.updated_at as number),\n };\n if (row.entity_id) v.entityId = row.entity_id as string;\n if (row.updated_by) v.updatedBy = row.updated_by as string;\n if (row.updated_via) v.updatedVia = row.updated_via as string;\n if (row.associations) v.associations = JSON.parse(row.associations as string);\n if (row.permissions) v.permissions = JSON.parse(row.permissions as string);\n return v;\n};\n","/**\n * Normalizes a SQLite binding's call convention (array-bind-then-step,\n * spread-args run/get/all, cursor-returning exec) behind one interface,\n * so the actual CRUD/query/version/type/association/token logic can live\n * once in this package instead of being re-typed per engine. Each adapter\n * package provides a thin SqlExecutor implementation over its own\n * database handle.\n *\n * Deliberately synchronous: every binding this targets executes queries\n * in-process without yielding, and an async interface would force the\n * shared logic's transaction sequences to interleave.\n */\nexport interface SqlExecutor {\n /** Run DDL, pragmas, or any statement(s) with no bound params and no results needed. */\n exec(sql: string): void;\n /** Run a single parameterized statement. Returns the number of rows affected (INSERT/UPDATE/DELETE). */\n run(sql: string, params?: readonly unknown[]): number;\n /** Run a parameterized statement and return the first result row, or undefined. */\n get<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): T | undefined;\n /** Run a parameterized statement and return all result rows. */\n all<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): T[];\n /**\n * Run `fn` atomically: every statement it issues commits together, or\n * none do if it throws. `fn` must be synchronous and call back into\n * this executor only — not every engine's transaction primitive can\n * straddle a suspended call. A binding with a real multi-statement\n * transaction (BEGIN/COMMIT/ROLLBACK) implements this with one; a\n * binding whose transaction primitive is itself a callback wrapper\n * (e.g. a Durable Object's storage.transactionSync) can pass `fn`\n * straight through to it.\n */\n transaction<T>(fn: () => T): T;\n}\n\n/** SQLite reports FK violations with this exact message; verify it holds when adding an engine. */\nexport const isForeignKeyViolation = (err: unknown): boolean =>\n err instanceof Error && err.message.includes('FOREIGN KEY constraint failed');\n\n/** SQLite reports a PRIMARY KEY/UNIQUE collision with this exact message. */\nexport const isUniqueConstraintViolation = (err: unknown): boolean =>\n err instanceof Error && err.message.includes('UNIQUE constraint failed');\n","import type { SqlExecutor } from './executor.js';\n\n/** timezone is optional passthrough app metadata — no default. */\nexport type StackConfig = { entityId: string; timezone: string | undefined };\n\n/** Inserts the singleton _config@1 record. Only valid on a freshly-created database. */\nexport const insertConfigRecord = (\n exec: SqlExecutor,\n entityId: string,\n timezone: string | undefined,\n): void => {\n const now = Date.now();\n exec.run(\n `INSERT INTO records (id, type_id, created_at, updated_at, content, version)\n VALUES ('_config', '_config@1', ?, ?, ?, 1)`,\n [now, now, JSON.stringify({ entityId, timezone })],\n );\n};\n\n/** Reads the singleton _config@1 record. Throws if the database is missing it. */\nexport const readStackConfig = (exec: SqlExecutor): StackConfig => {\n const row = exec.get<{ content: string }>(`SELECT content FROM records WHERE id = '_config'`);\n if (!row) throw new Error('Stack database is missing its config record.');\n const content = JSON.parse(row.content) as { entityId: string; timezone?: string };\n return { entityId: content.entityId, timezone: content.timezone };\n};\n","/**\n * The actual StackRecordAdapter logic (minus lifecycle: initialize/open/\n * close/flush stay per-engine, since schema setup, pragmas, locking, and\n * durability differ there). Every CRUD/query/version/type/association\n * operation lives here exactly once, parametrized over a SqlExecutor\n * (which normalizes each engine's call conventions). Concrete adapters\n * construct one of these and delegate their StackRecordAdapter methods\n * to it — see record-adapter-sqlite.\n */\n\nimport {\n StackConflictError,\n StackVersionConflictError,\n StackNotFoundError,\n StackQueryError,\n applyMergePatch,\n} from '@haverstack/core';\nimport type {\n StackRecord,\n StackType,\n TypeId,\n FileId,\n RecordVersion,\n StackQuery,\n QueryResult,\n Association,\n Permission,\n ActorOptions,\n ExpectedVersionOptions,\n} from '@haverstack/core';\nimport type { SqlExecutor } from './executor.js';\nimport { isForeignKeyViolation, isUniqueConstraintViolation } from './executor.js';\nimport { buildFromClause, buildWhereClause, buildOrderClause } from './query.js';\nimport { fts5Strategy } from './fts5.js';\nimport {\n rowToRecord,\n rowToAssociation,\n rowToType,\n rowToVersion,\n toMs,\n associationKeyColumns,\n} from './mappers.js';\nimport { makeCursor } from './cursor.js';\nimport { contentSortEntry } from '@haverstack/core/adapter';\nimport type { ScalarFieldKind } from '@haverstack/core';\n\n/**\n * Run a query statement, restating an engine parse error as the wire\n * taxonomy's `bad_request` rather than letting it escape as a raw engine\n * error (which a server has no code to map, so it becomes a 500).\n *\n * Scoped to the one filter that carries a query language: `filter.search`\n * is FTS5 source text, and sanitizeFts5Query() repairs the common shapes\n * but claims no completeness against the grammar. Every other clause here\n * is built from parameters, so a parse error in one is this module's bug —\n * rethrown untouched, where it belongs.\n */\nconst asStackQueryError = <T>(query: StackQuery, run: () => T): T => {\n try {\n return run();\n } catch (err) {\n if (!query.filter?.search) throw err;\n throw new StackQueryError(\n `Search text could not be parsed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n};\n\nexport type SharedSqlRecordLogicDeps = {\n exec: SqlExecutor;\n};\n\n/** Top-level field names in `schema` whose kind is 'file-ref'. */\nconst fileRefFieldNames = (schema: Record<string, { kind?: string }>): string[] =>\n Object.keys(schema).filter((field) => schema[field].kind === 'file-ref');\n\n/**\n * Top-level scalar fields, by name and declared kind — what the sort\n * index covers. Arrays and objects are left out: a value inside one has\n * no single position to order its record by.\n */\nconst sortableFieldKinds = (\n schema: Record<string, { kind?: string }>,\n): Map<string, ScalarFieldKind> => {\n const kinds = new Map<string, ScalarFieldKind>();\n for (const [field, def] of Object.entries(schema)) {\n if (!def.kind || def.kind === 'array' || def.kind === 'object') continue;\n kinds.set(field, def.kind as ScalarFieldKind);\n }\n return kinds;\n};\n\nexport class SharedSqlRecordLogic {\n /**\n * Per-typeId cache of file-ref field names, so syncFileRefs() doesn't\n * hit the `types` table and re-parse schema JSON on every write.\n * Populated eagerly in saveType() and lazily via getFileRefFields().\n */\n private readonly fileRefFieldsByType = new Map<string, string[]>();\n\n /** Per-typeId cache of sortable field kinds, as fileRefFieldsByType is. */\n private readonly sortFieldsByType = new Map<string, Map<string, ScalarFieldKind>>();\n\n constructor(private readonly deps: SharedSqlRecordLogicDeps) {}\n\n private get exec(): SqlExecutor {\n return this.deps.exec;\n }\n\n /**\n * SQL fragment (plus its bind params) that gates a records-table\n * UPDATE/DELETE on the opt-in `expectedVersion` precondition. Empty when\n * expectedVersion is omitted, keeping today's unconditional behavior.\n */\n private versionGuard(expectedVersion: number | undefined): { clause: string; params: unknown[] } {\n return expectedVersion === undefined\n ? { clause: '', params: [] }\n : { clause: ' AND version = ?', params: [expectedVersion] };\n }\n\n /**\n * Precondition check for mutations that can't fold expectedVersion into\n * their primary UPDATE's WHERE clause: patchContent/restoreVersion need\n * fts.remove() to run *before* the records-table content changes, so\n * the check happens first, standalone.\n */\n private checkExpectedVersion(record: StackRecord, expectedVersion: number | undefined): void {\n if (expectedVersion === undefined || record.version === expectedVersion) return;\n throw new StackVersionConflictError(\n `Record \"${record.id}\" is at version ${record.version}, expected ${expectedVersion}`,\n record.id,\n expectedVersion,\n record.version,\n );\n }\n\n /**\n * Called after a versionGuard()-gated statement affected zero rows.\n * Distinguishes \"record doesn't exist\" (StackNotFoundError) from \"it\n * exists but isn't at expectedVersion\" (StackVersionConflictError, with\n * the actual current version for the caller to act on).\n */\n private throwVersionConflict(id: string, expectedVersion: number | undefined): never {\n const row = this.exec.get<{ version: number }>('SELECT version FROM records WHERE id = ?', [\n id,\n ]);\n if (!row) throw new StackNotFoundError(`Record not found: \"${id}\"`);\n throw new StackVersionConflictError(\n `Record \"${id}\" is at version ${row.version}, expected ${expectedVersion}`,\n id,\n expectedVersion as number,\n row.version,\n );\n }\n\n // -------------------------------------------------------\n // Records\n // -------------------------------------------------------\n\n /**\n * A duplicate id hits the `records` PK — mapped to StackConflictError\n * instead of surfacing the raw engine exception, mirroring saveVersion's\n * collision mapping below.\n */\n async createRecord(record: StackRecord): Promise<StackRecord> {\n try {\n this.exec.run(\n `INSERT INTO records\n (id, type_id, created_at, updated_at, content, version,\n parent_id, entity_id, app_id, principal_id, updated_by, updated_via,\n deleted_at, unlisted_at, permissions)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n record.id,\n record.typeId,\n toMs(record.createdAt),\n toMs(record.updatedAt),\n JSON.stringify(record.content),\n record.version,\n record.parentId ?? null,\n record.entityId ?? null,\n record.appId ?? null,\n record.principalId ?? null,\n record.updatedBy ?? null,\n record.updatedVia ?? null,\n record.deletedAt ? toMs(record.deletedAt) : null,\n record.unlistedAt ? toMs(record.unlistedAt) : null,\n record.permissions ? JSON.stringify(record.permissions) : null,\n ],\n );\n } catch (err) {\n if (isUniqueConstraintViolation(err)) {\n throw new StackConflictError(`Record already exists: \"${record.id}\"`);\n }\n throw err;\n }\n\n if (record.associations?.length) {\n this.insertAssociations(record.id, record.associations);\n }\n\n fts5Strategy.insert(this.exec, record.id, JSON.stringify(record.content));\n this.syncFileRefs(record.id, record.typeId, record.content);\n this.syncContentSort(record.id, record.typeId, record.content);\n return record;\n }\n\n async getRecord(id: string): Promise<StackRecord | null> {\n return this.readRecord(id);\n }\n\n /**\n * The synchronous read behind getRecord(). Callers inside exec.transaction()\n * use this one: an `await` inside that callback yields the microtask queue\n * mid-transaction, and the next operation to run would try to open one of\n * its own — transaction() requires a synchronous callback for exactly this\n * reason (see SqlExecutor.transaction).\n *\n * The same synchrony is what lets the post-commit reads below report the\n * version their own mutation produced: `getRecord()` runs its body before\n * the `await` yields, so nothing interleaves between the commit and the\n * read. A backend that made these reads genuinely asynchronous would open\n * that window, and a concurrent mutation could be reported under this\n * one's verb.\n */\n private readRecord(id: string): StackRecord | null {\n const row = this.exec.get<Record<string, unknown>>('SELECT * FROM records WHERE id = ?', [id]);\n if (!row) return null;\n const associations = this.getAssociationsForRecord(id);\n return rowToRecord(row, associations);\n }\n\n async patchContent(\n id: string,\n patch: Record<string, unknown | null>,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n const existing = await this.getRecord(id);\n if (!existing) throw new Error(`Record not found: \"${id}\"`);\n this.checkExpectedVersion(existing, opts.expectedVersion);\n\n const merged = applyMergePatch(existing.content, patch);\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n fts5Strategy.remove(this.exec, id);\n this.exec.run(\n `UPDATE records SET content = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?`,\n [\n JSON.stringify(merged),\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ],\n );\n fts5Strategy.insert(this.exec, id, JSON.stringify(merged));\n this.syncFileRefs(id, existing.typeId, merged);\n this.syncContentSort(id, existing.typeId, merged);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after patchContent: \"${id}\"`);\n return updated;\n }\n\n async deleteRecord(\n id: string,\n opts: {\n hard?: boolean;\n expectedVersion?: number;\n snapshot?: RecordVersion;\n } & ActorOptions = {},\n ): Promise<StackRecord | null> {\n if (opts.hard) {\n return this.exec.transaction(() => this.hardDeleteRecord(id, opts.expectedVersion));\n } else {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const changed = this.exec.run(\n `UPDATE records SET deleted_at = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [\n toMs(new Date()),\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ...verParams,\n ],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n });\n }\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after deleteRecord: \"${id}\"`);\n return updated;\n }\n\n /**\n * Deletes a record's FTS entry, associations, versions, file-refs, sort\n * index rows, and row, returning the record as it stood — the last copy that will ever\n * exist, read here so that nothing can overtake it between the read and\n * the deletes. Null when there was no such record. No write() call —\n * callers batch it. expectedVersion is checked first so a lost CAS race\n * leaves nothing touched (children reference the row, so the check can't\n * fold into the final DELETE).\n */\n private hardDeleteRecord(id: string, expectedVersion?: number): StackRecord | null {\n const purged = this.readRecord(id);\n if (expectedVersion !== undefined) {\n if (!purged) throw new StackNotFoundError(`Record not found: \"${id}\"`);\n if (purged.version !== expectedVersion) {\n throw new StackVersionConflictError(\n `Record \"${id}\" is at version ${purged.version}, expected ${expectedVersion}`,\n id,\n expectedVersion,\n purged.version,\n );\n }\n }\n if (!purged) return null;\n fts5Strategy.remove(this.exec, id);\n this.exec.run('DELETE FROM associations WHERE record_id = ?', [id]);\n this.exec.run('DELETE FROM versions WHERE record_id = ?', [id]);\n this.exec.run('DELETE FROM file_refs WHERE record_id = ?', [id]);\n this.exec.run('DELETE FROM content_sort WHERE record_id = ?', [id]);\n this.exec.run('DELETE FROM records WHERE id = ?', [id]);\n return purged;\n }\n\n async undeleteRecord(\n id: string,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const changed = this.exec.run(\n `UPDATE records SET deleted_at = NULL, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [toMs(new Date()), opts.updatedBy ?? null, opts.updatedVia ?? null, id, ...verParams],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after undelete: \"${id}\"`);\n return updated;\n }\n\n async setPermissions(\n id: string,\n permissions: Permission[],\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const changed = this.exec.run(\n `UPDATE records SET permissions = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [\n permissions.length ? JSON.stringify(permissions) : null,\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ...verParams,\n ],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after setPermissions: \"${id}\"`);\n return updated;\n }\n\n async setUnlisted(\n id: string,\n unlisted: boolean,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const now = toMs(new Date());\n const changed = this.exec.run(\n `UPDATE records SET unlisted_at = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [\n unlisted ? now : null,\n now,\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ...verParams,\n ],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after setUnlisted: \"${id}\"`);\n return updated;\n }\n\n async restoreVersion(\n id: string,\n version: number,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n const existing = await this.getRecord(id);\n if (!existing) throw new Error(`Record not found: \"${id}\"`);\n this.checkExpectedVersion(existing, opts.expectedVersion);\n\n const target = await this.getVersion(id, version);\n if (!target) throw new Error(`Version not found: ${id}@${version}`);\n\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n fts5Strategy.remove(this.exec, id);\n this.exec.run(\n `UPDATE records SET type_id = ?, content = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?`,\n [\n target.typeId,\n JSON.stringify(target.content),\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ],\n );\n if (target.associations !== undefined) {\n this.exec.run('DELETE FROM associations WHERE record_id = ?', [id]);\n if (target.associations.length) this.insertAssociations(id, target.associations);\n }\n fts5Strategy.insert(this.exec, id, JSON.stringify(target.content));\n this.syncFileRefs(id, target.typeId, target.content);\n this.syncContentSort(id, target.typeId, target.content);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after restoreVersion: \"${id}\"`);\n return updated;\n }\n\n async commitMigration(\n id: string,\n toTypeId: TypeId,\n content: Record<string, unknown>,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n // Checked here rather than folded into the UPDATE's WHERE clause:\n // fts5Strategy.remove() has to run before the content changes, so the\n // precondition has to settle first — same shape as patchContent() and\n // restoreVersion().\n const existing = await this.getRecord(id);\n if (!existing) throw new Error(`Record not found: \"${id}\"`);\n this.checkExpectedVersion(existing, opts.expectedVersion);\n\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(id, opts.snapshot);\n fts5Strategy.remove(this.exec, id);\n this.exec.run(\n `UPDATE records SET type_id = ?, content = ?, version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?`,\n [\n toTypeId,\n JSON.stringify(content),\n toMs(new Date()),\n opts.updatedBy ?? null,\n opts.updatedVia ?? null,\n id,\n ],\n );\n fts5Strategy.insert(this.exec, id, JSON.stringify(content));\n this.syncFileRefs(id, toTypeId, content);\n this.syncContentSort(id, toTypeId, content);\n });\n\n const updated = await this.getRecord(id);\n if (!updated) throw new Error(`Record not found after commitMigration: \"${id}\"`);\n return updated;\n }\n\n async queryRecords(query: StackQuery): Promise<QueryResult> {\n // The sort index joins into both statements below, not just the\n // ordered one: a content-sort cursor is a condition on the joined row,\n // so a count taken without the join would be counting a different\n // query.\n const { sql: from, params: fromParams } = buildFromClause(query);\n const { sql: where, params: whereParams } = buildWhereClause(query);\n const params = [...fromParams, ...whereParams];\n const order = buildOrderClause(query);\n const limit = query.limit ?? 50;\n\n // Fetch one extra to determine if there's a next page\n const rows = asStackQueryError(query, () =>\n this.exec.all<Record<string, unknown>>(`SELECT r.* FROM ${from} ${where} ${order} LIMIT ?`, [\n ...params,\n limit + 1,\n ]),\n );\n\n const hasMore = rows.length > limit;\n const page = hasMore ? rows.slice(0, limit) : rows;\n\n const records = page.map((row) => {\n const associations = this.getAssociationsForRecord(row.id as string);\n return rowToRecord(row, associations);\n });\n\n const countRows = asStackQueryError(query, () =>\n this.exec.all<{ total: number }>(`SELECT COUNT(*) as total FROM ${from} ${where}`, params),\n );\n const total = countRows[0]?.total ?? 0;\n\n const lastRecord = records[records.length - 1];\n const cursor =\n hasMore && lastRecord\n ? makeCursor(lastRecord, query.sort, (record, field) => this.sortFieldKind(record, field))\n : null;\n\n return { records, cursor, total };\n }\n\n /**\n * Atomically verify fileId is unreferenced, then hard-delete its\n * metadata records across every typeId in `metadataTypeIds` (see\n * deleteUnreferencedAttachmentRecords on the adapter contract). A real\n * SQL transaction of synchronous calls — no `await` between the check\n * and the deletes, so nothing interleaves.\n */\n async deleteUnreferencedAttachmentRecords(\n fileId: FileId,\n metadataTypeIds: TypeId[],\n ): Promise<StackRecord[]> {\n return this.exec.transaction(() => {\n const referenced = this.exec.all<{ found: number }>(\n `SELECT 1 as found FROM associations WHERE kind = 'attachment' AND file_id = ?\n UNION ALL\n SELECT 1 FROM file_refs WHERE file_id = ?\n LIMIT 1`,\n [fileId, fileId],\n );\n if (referenced.length) {\n throw new StackConflictError('Attachment is still referenced by one or more records');\n }\n\n // An empty family still owes the caller the reference check above —\n // the conflict is a fact about the file, not about which metadata\n // types happen to be defined.\n if (metadataTypeIds.length === 0) return [];\n\n const placeholders = metadataTypeIds.map(() => '?').join(', ');\n const metaRows = this.exec.all<{ id: string }>(\n `SELECT id FROM records\n WHERE type_id IN (${placeholders}) AND json_extract(content, '$.fileId') = ?`,\n [...metadataTypeIds, fileId],\n );\n const deleted: StackRecord[] = [];\n for (const row of metaRows) {\n const purged = this.hardDeleteRecord(row.id);\n if (purged) deleted.push(purged);\n }\n\n return deleted;\n });\n }\n\n // -------------------------------------------------------\n // Versions\n // -------------------------------------------------------\n\n async getVersions(id: string): Promise<RecordVersion[]> {\n const rows = this.exec.all<Record<string, unknown>>(\n 'SELECT * FROM versions WHERE record_id = ? ORDER BY version DESC',\n [id],\n );\n return rows.map(rowToVersion);\n }\n\n async getVersion(id: string, version: number): Promise<RecordVersion | null> {\n const rows = this.exec.all<Record<string, unknown>>(\n 'SELECT * FROM versions WHERE record_id = ? AND version = ?',\n [id, version],\n );\n return rows.length ? rowToVersion(rows[0]) : null;\n }\n\n /**\n * A (record_id, version) collision is rejected loudly via the UNIQUE\n * constraint, mapped to StackConflictError — never a silently discarded\n * snapshot leaving a hole in rollback history. See\n * docs/spec/versioning.md § Optimistic concurrency (`ifVersion`).\n */\n private insertVersionRow(id: string, version: RecordVersion): void {\n try {\n this.exec.run(\n `INSERT INTO versions\n (record_id, version, type_id, content, updated_at, entity_id,\n updated_by, updated_via, associations, permissions)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n id,\n version.version,\n version.typeId,\n JSON.stringify(version.content),\n toMs(version.updatedAt),\n version.entityId ?? null,\n version.updatedBy ?? null,\n version.updatedVia ?? null,\n version.associations ? JSON.stringify(version.associations) : null,\n version.permissions ? JSON.stringify(version.permissions) : null,\n ],\n );\n } catch (err) {\n if (isUniqueConstraintViolation(err)) {\n throw new StackConflictError(\n `Version ${version.version} already exists for record \"${id}\" — a concurrent ` +\n `writer raced past this version. Use ifVersion to detect this before it happens.`,\n );\n }\n throw err;\n }\n }\n\n private overwriteVersionRow(id: string, version: RecordVersion): void {\n this.exec.run(\n `UPDATE versions\n SET type_id = ?, content = ?, updated_at = ?, entity_id = ?, associations = ?, permissions = ?\n WHERE record_id = ? AND version = ?`,\n [\n version.typeId,\n JSON.stringify(version.content),\n toMs(version.updatedAt),\n version.entityId ?? null,\n version.associations ? JSON.stringify(version.associations) : null,\n version.permissions ? JSON.stringify(version.permissions) : null,\n id,\n version.version,\n ],\n );\n }\n\n /**\n * Standalone snapshot write for tooling and tests — loud on any\n * collision, since nothing here is about to bump the version. Mutating\n * methods take a `snapshot` option instead; see snapshotBeforeMutation.\n */\n async saveVersion(id: string, version: RecordVersion): Promise<void> {\n this.insertVersionRow(id, version);\n }\n\n /**\n * The snapshot half of a mutating method's atomic snapshot-then-mutate\n * transaction. A collision is a racing-writer conflict, except an orphan\n * row at the record's *current* version — recognized by version number\n * alone, never content — which is overwritten so the interrupted write\n * can complete. See docs/spec/versioning.md § Snapshot atomicity.\n */\n private snapshotBeforeMutation(id: string, version: RecordVersion): void {\n try {\n this.insertVersionRow(id, version);\n } catch (err) {\n if (!(err instanceof StackConflictError)) throw err;\n\n const row = this.exec.get<{ version: number }>('SELECT version FROM records WHERE id = ?', [\n id,\n ]);\n if (!row || row.version !== version.version) throw err;\n\n this.overwriteVersionRow(id, version);\n }\n }\n\n // -------------------------------------------------------\n // Types\n // -------------------------------------------------------\n\n async saveType(type: StackType): Promise<void> {\n this.exec.run(\n `INSERT OR REPLACE INTO types\n (id, base_id, version, name, schema, schema_hash, migrates_from, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n type.id,\n type.baseId,\n type.version,\n type.name,\n JSON.stringify(type.schema),\n type.schemaHash,\n type.migratesFrom ?? null,\n toMs(type.createdAt),\n ],\n );\n const schema = type.schema as Record<string, { kind?: string }>;\n this.fileRefFieldsByType.set(type.id, fileRefFieldNames(schema));\n this.sortFieldsByType.set(type.id, sortableFieldKinds(schema));\n }\n\n async getType(id: TypeId): Promise<StackType | null> {\n const rows = this.exec.all<Record<string, unknown>>('SELECT * FROM types WHERE id = ?', [id]);\n return rows.length ? rowToType(rows[0]) : null;\n }\n\n async listTypes(): Promise<StackType[]> {\n const rows = this.exec.all<Record<string, unknown>>(\n 'SELECT * FROM types ORDER BY base_id, version',\n );\n return rows.map(rowToType);\n }\n\n // -------------------------------------------------------\n // Associations\n // -------------------------------------------------------\n\n async associate(\n recordId: string,\n association: Association,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(recordId, opts.snapshot);\n // Bump (and CAS-check) first, before the associations-table write, so\n // a lost race never partially applies.\n this.bumpVersion(recordId, opts);\n this.insertAssociations(recordId, [association]);\n });\n\n const updated = await this.getRecord(recordId);\n if (!updated) throw new Error(`Record not found after associate: \"${recordId}\"`);\n return updated;\n }\n\n async dissociate(\n recordId: string,\n association: Association,\n opts: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions = {},\n ): Promise<StackRecord> {\n this.exec.transaction(() => {\n if (opts.snapshot) this.snapshotBeforeMutation(recordId, opts.snapshot);\n this.bumpVersion(recordId, opts);\n this.exec.run(\n `DELETE FROM associations\n WHERE record_id = ?\n AND kind = ?\n AND label = ?\n AND file_id = ?\n AND related_scope = ?\n AND related_id = ?\n AND related_ns = ?\n AND related_stack = ?`,\n [recordId, association.kind, association.label, ...associationKeyColumns(association)],\n );\n });\n\n const updated = await this.getRecord(recordId);\n if (!updated) throw new Error(`Record not found after dissociate: \"${recordId}\"`);\n return updated;\n }\n\n private bumpVersion(id: string, opts: ExpectedVersionOptions & ActorOptions = {}): void {\n const { clause, params: verParams } = this.versionGuard(opts.expectedVersion);\n const changed = this.exec.run(\n `UPDATE records SET version = version + 1, updated_at = ?, updated_by = ?, updated_via = ? WHERE id = ?${clause}`,\n [toMs(new Date()), opts.updatedBy ?? null, opts.updatedVia ?? null, id, ...verParams],\n );\n if (changed === 0) this.throwVersionConflict(id, opts.expectedVersion);\n }\n\n /**\n * FK enforcement (PRAGMA_FOREIGN_KEYS_ON) means inserting an\n * association against a record that doesn't exist throws — mapped\n * here to StackNotFoundError so associate() on a nonexistent record\n * fails loudly instead of silently creating an orphan row.\n */\n private insertAssociations(recordId: string, associations: Association[]): void {\n for (const assoc of associations) {\n try {\n this.exec.run(\n `INSERT OR IGNORE INTO associations\n (record_id, kind, label, file_id, related_scope, related_id, related_ns, related_stack)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [recordId, assoc.kind, assoc.label, ...associationKeyColumns(assoc)],\n );\n } catch (err) {\n if (isForeignKeyViolation(err)) {\n throw new StackNotFoundError(`Record not found: \"${recordId}\"`);\n }\n throw err;\n }\n }\n }\n\n private getAssociationsForRecord(recordId: string): Association[] {\n const rows = this.exec.all<Record<string, unknown>>(\n 'SELECT * FROM associations WHERE record_id = ?',\n [recordId],\n );\n return rows.map(rowToAssociation);\n }\n\n /**\n * File-ref field names for typeId, from the in-memory cache — falling\n * back to a `types` lookup (and populating the cache) only for a typeId\n * this process hasn't seen via saveType() yet, e.g. right after open().\n */\n private getFileRefFields(typeId: string): string[] {\n const cached = this.fileRefFieldsByType.get(typeId);\n if (cached) return cached;\n\n const typeRow = this.exec.get<{ schema: string }>('SELECT schema FROM types WHERE id = ?', [\n typeId,\n ]);\n const fields = typeRow\n ? fileRefFieldNames(JSON.parse(typeRow.schema) as Record<string, { kind?: string }>)\n : [];\n this.fileRefFieldsByType.set(typeId, fields);\n return fields;\n }\n\n /**\n * Replace a record's file_refs rows with whatever its content currently\n * holds in top-level file-ref fields, on every write that can change\n * content or typeId, so the index never drifts. Only top-level scalars\n * are indexed; the schema lookup is cached (fileRefFieldsByType).\n */\n private getSortFields(typeId: string): Map<string, ScalarFieldKind> {\n const cached = this.sortFieldsByType.get(typeId);\n if (cached) return cached;\n\n const typeRow = this.exec.get<{ schema: string }>('SELECT schema FROM types WHERE id = ?', [\n typeId,\n ]);\n const fields = typeRow\n ? sortableFieldKinds(JSON.parse(typeRow.schema) as Record<string, { kind?: string }>)\n : new Map<string, ScalarFieldKind>();\n this.sortFieldsByType.set(typeId, fields);\n return fields;\n }\n\n /**\n * Replace a record's content_sort rows with what its content currently\n * holds, on every write that can change content or typeId. A field\n * holding no orderable value gets no row at all, which is what puts the\n * record at the end of a sort on that field.\n */\n private syncContentSort(\n recordId: string,\n typeId: string,\n content: Record<string, unknown>,\n ): void {\n this.exec.run('DELETE FROM content_sort WHERE record_id = ?', [recordId]);\n\n for (const [field, kind] of this.getSortFields(typeId)) {\n const entry = contentSortEntry(kind, content[field]);\n if (!entry) continue;\n this.exec.run(\n `INSERT OR IGNORE INTO content_sort (record_id, field, num_value, text_key, text_value)\n VALUES (?, ?, ?, ?, ?)`,\n entry.kind === 'num'\n ? [recordId, field, entry.num, null, null]\n : [recordId, field, null, entry.key, entry.text],\n );\n }\n }\n\n /** The declared kind of a record's top-level `field`, if it has one. */\n private sortFieldKind(record: StackRecord, field: string): ScalarFieldKind | undefined {\n return this.getSortFields(record.typeId).get(field);\n }\n\n private syncFileRefs(recordId: string, typeId: string, content: Record<string, unknown>): void {\n this.exec.run('DELETE FROM file_refs WHERE record_id = ?', [recordId]);\n\n const fields = this.getFileRefFields(typeId);\n for (const field of fields) {\n const value = content[field];\n if (typeof value === 'string') {\n this.exec.run(\n 'INSERT OR IGNORE INTO file_refs (record_id, field, file_id) VALUES (?, ?, ?)',\n [recordId, field, value],\n );\n }\n }\n }\n}\n","import type { SqlExecutor } from '@haverstack/sqlite-shared/record';\n\n/**\n * Normalizes a Durable Object's ctx.storage.sql to SqlExecutor.\n *\n * Two things don't map onto node:sqlite's shape the way the rest of this\n * interface does:\n *\n * - There is no get/all/run split — sql.exec() always returns a cursor.\n * get()/all() read it via .toArray(); run() reads .rowsWritten for the\n * affected-row count run()'s contract promises.\n * - Raw BEGIN/COMMIT/ROLLBACK are rejected outright by DO SQLite (verified\n * against the real Workers runtime, not assumed) — its storage has no\n * notion of a transaction left open across separate exec() calls, and\n * each statement auto-commits the instant it runs, so no-op'ing those\n * three strings would silently break atomicity, not preserve it. The\n * platform's real primitive is ctx.storage.transactionSync(fn), which\n * SqlExecutor.transaction() reaches directly — see docs/spec/adapters.md\n * § Concurrency & storage ownership.\n */\nexport class DurableObjectSqliteExecutor implements SqlExecutor {\n constructor(private readonly storage: DurableObjectStorage) {}\n\n private get sql(): SqlStorage {\n return this.storage.sql;\n }\n\n exec(sql: string): void {\n this.sql.exec(sql);\n }\n\n run(sql: string, params: readonly unknown[] = []): number {\n const cursor = this.sql.exec(sql, ...(params as SqlStorageValue[]));\n return cursor.rowsWritten;\n }\n\n get<T = Record<string, unknown>>(sql: string, params: readonly unknown[] = []): T | undefined {\n const rows = this.sql.exec(sql, ...(params as SqlStorageValue[])).toArray();\n return rows[0] as T | undefined;\n }\n\n all<T = Record<string, unknown>>(sql: string, params: readonly unknown[] = []): T[] {\n return this.sql.exec(sql, ...(params as SqlStorageValue[])).toArray() as T[];\n }\n\n transaction<T>(fn: () => T): T {\n return this.storage.transactionSync(fn);\n }\n}\n","/**\n * Haverstack — Durable Object SQLite Record Adapter\n * -------------------------------------------------------\n * Implements StackRecordAdapter over a Cloudflare Durable Object's SQLite\n * storage (ctx.storage.sql). Full-text search uses FTS5, same as\n * record-adapter-sqlite — DO's SQLite build ships it.\n *\n * Ownership and durability come from the platform, not from anything this\n * class does: a Durable Object id maps to exactly one running instance,\n * so there is no separate lock file the way record-adapter-sqlite needs\n * one for real files (see docs/spec/adapters.md § Concurrency & storage\n * ownership) — the DO *is* the lock. There is likewise no persist/flush\n * step: every write through ctx.storage.sql is durable by the time the\n * call returns, so flush()/close() are no-ops kept only to satisfy the\n * optional StackRecordAdapter methods.\n *\n * This class itself is a thin binding: schema setup and the one piece of\n * genuinely engine-specific wiring — SqlExecutor.transaction() reaching\n * ctx.storage.transactionSync() instead of raw SQL BEGIN/COMMIT/ROLLBACK,\n * which DO SQLite rejects outright — live here (see executor.ts). The\n * actual StackRecordAdapter logic lives in @haverstack/sqlite-shared's\n * SharedSqlRecordLogic, exactly as it does for record-adapter-sqlite.\n */\n\nimport type { StackType, TypeId, FileId, RecordVersion, ActorOptions } from '@haverstack/core';\nimport type {\n StackRecord,\n StackQuery,\n QueryResult,\n Association,\n Permission,\n} from '@haverstack/core';\nimport type { StackRecordAdapter, AdapterCapabilities } from '@haverstack/core/adapter';\nimport {\n RECORD_SCHEMA_SQL,\n FTS5_SCHEMA_SQL,\n PRAGMA_FOREIGN_KEYS_ON,\n insertConfigRecord,\n readStackConfig,\n SharedSqlRecordLogic,\n} from '@haverstack/sqlite-shared/record';\nimport { DurableObjectSqliteExecutor } from './executor.js';\n\n// -------------------------------------------------------\n// Types\n// -------------------------------------------------------\n\nexport type DoRecordCreateOptions = {\n /** Entity ID of the stack owner. Ignored if the DO's storage already has a config record. */\n entityId: string;\n /** IANA timezone string e.g. \"America/New_York\". Optional passthrough app metadata — no default. */\n timezone?: string;\n};\n\n// -------------------------------------------------------\n// DoSQLiteRecordAdapter\n// -------------------------------------------------------\n\nexport class DoSQLiteRecordAdapter implements StackRecordAdapter {\n readonly capabilities: AdapterCapabilities = {\n fullTextSearch: true,\n contentFieldQuery: true,\n nestedContentQuery: true,\n contentPresenceQuery: true,\n contentFieldSort: true,\n sortableFields: ['createdAt', 'updatedAt', 'version'],\n maxAttachmentBytes: null,\n maxContentBytes: null,\n };\n\n ownerEntityId!: string;\n timezone: string | undefined;\n\n private readonly record: SharedSqlRecordLogic;\n\n private constructor(private readonly exec: DurableObjectSqliteExecutor) {\n this.record = new SharedSqlRecordLogic({ exec });\n }\n\n /**\n * Create (or reattach to) the adapter for a DO instance. There is no\n * initialize()/open() split the way file-based adapters need one: a DO\n * id either already has a config record (a previous call created it —\n * reattach, opts.entityId/timezone ignored in favor of what's stored)\n * or it doesn't (first call — opts.entityId/timezone become the config).\n * Schema DDL is `CREATE TABLE IF NOT EXISTS`, so running it every call\n * is idempotent and cheap.\n */\n static async create(\n storage: DurableObjectStorage,\n opts: DoRecordCreateOptions,\n ): Promise<DoSQLiteRecordAdapter> {\n const exec = new DurableObjectSqliteExecutor(storage);\n exec.exec(RECORD_SCHEMA_SQL);\n exec.exec(FTS5_SCHEMA_SQL);\n exec.exec(PRAGMA_FOREIGN_KEYS_ON);\n // DO SQLite manages its own durability and rejects PRAGMA journal_mode\n // outright (\"not authorized\") — verified against the real runtime, not\n // assumed — so unlike record-adapter-sqlite, no WAL pragma runs here.\n\n const adapter = new DoSQLiteRecordAdapter(exec);\n const existing = exec.get<{ content: string }>(\n `SELECT content FROM records WHERE id = '_config'`,\n );\n if (existing) {\n const config = readStackConfig(exec);\n adapter.ownerEntityId = config.entityId;\n adapter.timezone = config.timezone;\n } else {\n insertConfigRecord(exec, opts.entityId, opts.timezone);\n adapter.ownerEntityId = opts.entityId;\n adapter.timezone = opts.timezone;\n }\n return adapter;\n }\n\n // -------------------------------------------------------\n // Records\n // -------------------------------------------------------\n\n createRecord(record: StackRecord): Promise<StackRecord> {\n return this.record.createRecord(record);\n }\n\n getRecord(id: string): Promise<StackRecord | null> {\n return this.record.getRecord(id);\n }\n\n patchContent(\n id: string,\n patch: Record<string, unknown | null>,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.patchContent(id, patch, opts);\n }\n\n deleteRecord(\n id: string,\n opts?: { hard?: boolean; expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord | null> {\n return this.record.deleteRecord(id, opts);\n }\n\n undeleteRecord(\n id: string,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.undeleteRecord(id, opts);\n }\n\n setPermissions(\n id: string,\n permissions: Permission[],\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.setPermissions(id, permissions, opts);\n }\n\n setUnlisted(\n id: string,\n unlisted: boolean,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.setUnlisted(id, unlisted, opts);\n }\n\n restoreVersion(\n id: string,\n version: number,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.restoreVersion(id, version, opts);\n }\n\n commitMigration(\n id: string,\n toTypeId: TypeId,\n content: Record<string, unknown>,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.commitMigration(id, toTypeId, content, opts);\n }\n\n queryRecords(query: StackQuery): Promise<QueryResult> {\n return this.record.queryRecords(query);\n }\n\n deleteUnreferencedAttachmentRecords(\n fileId: FileId,\n metadataTypeIds: TypeId[],\n ): Promise<StackRecord[]> {\n return this.record.deleteUnreferencedAttachmentRecords(fileId, metadataTypeIds);\n }\n\n // -------------------------------------------------------\n // Versions\n // -------------------------------------------------------\n\n getVersions(id: string): Promise<RecordVersion[]> {\n return this.record.getVersions(id);\n }\n\n getVersion(id: string, version: number): Promise<RecordVersion | null> {\n return this.record.getVersion(id, version);\n }\n\n saveVersion(id: string, version: RecordVersion): Promise<void> {\n return this.record.saveVersion(id, version);\n }\n\n // -------------------------------------------------------\n // Types\n // -------------------------------------------------------\n\n saveType(type: StackType): Promise<void> {\n return this.record.saveType(type);\n }\n\n getType(id: TypeId): Promise<StackType | null> {\n return this.record.getType(id);\n }\n\n listTypes(): Promise<StackType[]> {\n return this.record.listTypes();\n }\n\n // -------------------------------------------------------\n // Associations\n // -------------------------------------------------------\n\n associate(\n recordId: string,\n association: Association,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.associate(recordId, association, opts);\n }\n\n dissociate(\n recordId: string,\n association: Association,\n opts?: { expectedVersion?: number; snapshot?: RecordVersion } & ActorOptions,\n ): Promise<StackRecord> {\n return this.record.dissociate(recordId, association, opts);\n }\n\n // -------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------\n\n /** No-op: every write through ctx.storage.sql is already durable. */\n async flush(): Promise<void> {}\n\n /** No-op: no lock file, no connection to release — the DO's own lifecycle governs storage. */\n async close(): Promise<void> {}\n}\n\nexport { DurableObjectSqliteExecutor } from './executor.js';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haverstack/record-adapter-do-sqlite",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Cloudflare Durable Objects (SQLite storage) StackRecordAdapter for Haverstack — Workers, FTS5, single-writer by construction",
5
5
  "type": "module",
6
6
  "exports": {
@@ -32,7 +32,7 @@
32
32
  "storage"
33
33
  ],
34
34
  "dependencies": {
35
- "@haverstack/core": "^0.23.0"
35
+ "@haverstack/core": "^0.25.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@cloudflare/vitest-pool-workers": "^0.22.0",