@haverstack/record-adapter-do-sqlite 0.22.0 → 0.23.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
@@ -1,5 +1,5 @@
1
- import { StackRecord, JournalOptions, RecordChanges, RecordVersion, ActorOptions, TypeId, StackQuery, QueryResult, FileId, JournalQuery, RecordJournalEntry, StackType, Association } from '@haverstack/core';
2
- import { StackRecordAdapter, AdapterCapabilities } from '@haverstack/core/adapter';
1
+ import { StackCapabilities, StackRecord, RecordChangeSet, RecordVersion, ActorOptions, TypeId, StackQuery, QueryResult, FileId, JournalQuery, RecordJournalEntry, StackType, Association } from '@haverstack/core';
2
+ import { StackRecordAdapter, JournalOptions } from '@haverstack/core/adapter';
3
3
 
4
4
  /**
5
5
  * Normalizes a SQLite binding's call convention (array-bind-then-step,
@@ -59,33 +59,33 @@ type StackConfig = {
59
59
  */
60
60
 
61
61
  declare abstract class SharedSqlRecordAdapter implements StackRecordAdapter {
62
- readonly capabilities: AdapterCapabilities;
62
+ readonly capabilities: StackCapabilities;
63
63
  readonly ownerEntityId: string;
64
64
  readonly timezone: string | undefined;
65
65
  private readonly record;
66
66
  protected constructor(exec: SqlExecutor, config: StackConfig);
67
67
  createRecord(record: StackRecord, opts?: JournalOptions): Promise<StackRecord>;
68
68
  getRecord(id: string): Promise<StackRecord | null>;
69
- mutateRecord(id: string, changes: RecordChanges, opts?: {
70
- expectedVersion?: number;
69
+ mutateRecord(id: string, changes: RecordChangeSet, opts?: {
70
+ ifVersion?: number;
71
71
  snapshot?: RecordVersion;
72
72
  bumpsVersion?: boolean;
73
73
  } & ActorOptions & JournalOptions): Promise<StackRecord>;
74
74
  deleteRecord(id: string, opts?: {
75
- hard?: boolean;
76
- expectedVersion?: number;
75
+ purge?: boolean;
76
+ ifVersion?: number;
77
77
  snapshot?: RecordVersion;
78
78
  } & ActorOptions & JournalOptions): Promise<StackRecord | null>;
79
79
  undeleteRecord(id: string, opts?: {
80
- expectedVersion?: number;
80
+ ifVersion?: number;
81
81
  snapshot?: RecordVersion;
82
82
  } & ActorOptions & JournalOptions): Promise<StackRecord>;
83
83
  restoreVersion(id: string, version: number, opts?: {
84
- expectedVersion?: number;
84
+ ifVersion?: number;
85
85
  snapshot?: RecordVersion;
86
86
  } & ActorOptions & JournalOptions): Promise<StackRecord>;
87
87
  commitMigration(id: string, toTypeId: TypeId, content: Record<string, unknown>, opts?: {
88
- expectedVersion?: number;
88
+ ifVersion?: number;
89
89
  snapshot?: RecordVersion;
90
90
  } & ActorOptions & JournalOptions): Promise<StackRecord>;
91
91
  queryRecords(query: StackQuery): Promise<QueryResult>;
@@ -156,20 +156,21 @@ declare class DurableObjectSqliteExecutor implements SqlExecutor {
156
156
  * SharedSqlRecordAdapter, exactly as it does for record-adapter-sqlite.
157
157
  */
158
158
 
159
- type DoRecordCreateOptions = {
159
+ type DoSQLiteRecordAdapterOpenOrInitializeOptions = {
160
160
  /** Entity ID of the stack owner. Ignored if the DO's storage already has a config record. */
161
- entityId: string;
161
+ ownerEntityId: string;
162
162
  /** IANA timezone string e.g. "America/New_York". Optional passthrough app metadata — no default. */
163
163
  timezone?: string;
164
164
  };
165
165
  declare class DoSQLiteRecordAdapter extends SharedSqlRecordAdapter {
166
166
  private constructor();
167
167
  /**
168
- * Create (or reattach to) the adapter for a DO instance. There is no
169
- * initialize()/open() split the way file-based adapters need one: a DO
170
- * id either already has a config record (a previous call created it —
171
- * reattach, opts.entityId/timezone ignored in favor of what's stored)
172
- * or it doesn't (first call — opts.entityId/timezone become the config).
168
+ * Open the adapter for a DO instance, initializing its storage on first
169
+ * use — LocalAdapter.openOrInitialize()'s counterpart. There is no
170
+ * separate initialize()/open() the way file-based adapters need one: a
171
+ * DO id either already has a config record (reattach,
172
+ * opts.ownerEntityId/timezone ignored in favor of what's stored) or it
173
+ * doesn't (first call — opts.ownerEntityId/timezone become the config).
173
174
  * Schema DDL is `CREATE TABLE IF NOT EXISTS`, so running it every call
174
175
  * is idempotent and cheap.
175
176
  *
@@ -177,11 +178,11 @@ declare class DoSQLiteRecordAdapter extends SharedSqlRecordAdapter {
177
178
  * outright ("not authorized") — verified against the real runtime, not
178
179
  * assumed — hence `wal: false`, unlike record-adapter-sqlite.
179
180
  */
180
- static create(storage: DurableObjectStorage, opts: DoRecordCreateOptions): Promise<DoSQLiteRecordAdapter>;
181
+ static openOrInitialize(storage: DurableObjectStorage, opts: DoSQLiteRecordAdapterOpenOrInitializeOptions): Promise<DoSQLiteRecordAdapter>;
181
182
  /** No-op: every write through ctx.storage.sql is already durable. */
182
183
  flush(): Promise<void>;
183
184
  /** No-op: no lock file, no connection to release — the DO's own lifecycle governs storage. */
184
185
  close(): Promise<void>;
185
186
  }
186
187
 
187
- export { type DoRecordCreateOptions, DoSQLiteRecordAdapter, DurableObjectSqliteExecutor };
188
+ export { DoSQLiteRecordAdapter, type DoSQLiteRecordAdapterOpenOrInitializeOptions, DurableObjectSqliteExecutor };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { StackNotFoundError, StackConflictError, applyMergePatch, StackVersionConflictError, StackQueryError, NATIVE_SORT_FIELDS } from '@haverstack/core';
1
+ import { StackNotFoundError, StackConflictError, applyMergePatch, StackVersionConflictError, StackBadRequestError, NATIVE_SORT_FIELDS } from '@haverstack/core';
2
2
  import { contentSortEntry, parseContentFilterKey, contentSortKey } from '@haverstack/core/adapter';
3
3
 
4
4
  // ../sqlite-shared/dist/schema.js
@@ -68,6 +68,7 @@ var RECORD_SCHEMA_SQL = `
68
68
  content TEXT NOT NULL CHECK (json_valid(content)),
69
69
  updated_at INTEGER NOT NULL,
70
70
  entity_id TEXT,
71
+ principal_id TEXT,
71
72
  updated_by TEXT,
72
73
  updated_via TEXT,
73
74
  PRIMARY KEY (record_id, version)
@@ -84,7 +85,7 @@ var RECORD_SCHEMA_SQL = `
84
85
  at INTEGER NOT NULL,
85
86
  -- No 'purged': a purge destroys the log rather than appending to it,
86
87
  -- so the constraint names exactly the kinds an entry can carry.
87
- -- See docs/spec/journal.md \xA7 A hard delete destroys the journal.
88
+ -- See docs/spec/journal.md \xA7 A purge destroys the journal.
88
89
  kind TEXT NOT NULL CHECK (kind IN ('created', 'changed', 'deleted')),
89
90
  ops TEXT NOT NULL CHECK (json_valid(ops)),
90
91
  version INTEGER NOT NULL,
@@ -197,7 +198,7 @@ var fromBase64 = (cursor) => {
197
198
  };
198
199
  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 }));
199
200
  var decodeCursor = (cursor) => {
200
- const malformed = () => new StackQueryError(`Invalid cursor: malformed "${cursor}"`);
201
+ const malformed = () => new StackBadRequestError(`Invalid cursor: malformed "${cursor}"`);
201
202
  let parsed;
202
203
  try {
203
204
  parsed = JSON.parse(fromBase64(cursor));
@@ -218,11 +219,11 @@ var decodeCursor = (cursor) => {
218
219
  }
219
220
  if (k === "num" || k === "native") {
220
221
  if (typeof v !== "number" || !isFinite(v)) {
221
- throw new StackQueryError(`Invalid cursor: non-numeric sort value`);
222
+ throw new StackBadRequestError(`Invalid cursor: non-numeric sort value`);
222
223
  }
223
224
  if (k === "native") {
224
225
  if (!SORT_FIELDS.includes(f)) {
225
- throw new StackQueryError(`Invalid cursor: unknown sort field "${f}"`);
226
+ throw new StackBadRequestError(`Invalid cursor: unknown sort field "${f}"`);
226
227
  }
227
228
  return { kind: "native", field: f, value: v, id: i };
228
229
  }
@@ -356,7 +357,7 @@ var atBudget = (page, rows) => ({
356
357
  var sqlDirection = (query) => {
357
358
  const dir = query.sort?.direction ?? "desc";
358
359
  if (dir !== "asc" && dir !== "desc") {
359
- throw new StackQueryError(`Invalid sort direction "${dir}": expected "asc" or "desc".`);
360
+ throw new StackBadRequestError(`Invalid sort direction "${dir}": expected "asc" or "desc".`);
360
361
  }
361
362
  return dir === "asc" ? "ASC" : "DESC";
362
363
  };
@@ -381,7 +382,7 @@ var contentPathExists = (segments, leaf, params, nextAlias) => {
381
382
  };
382
383
  var equalsLeaf = (alias) => `${alias}.type NOT IN ('object', 'array') AND ${alias}.value = ?`;
383
384
  var SORT_ALIAS = "cs";
384
- var cursorMismatch = (cursor, field) => new StackQueryError(`Cursor sort field "${cursor.field}" does not match query sort field "${field}"`);
385
+ var cursorMismatch = (cursor, field) => new StackBadRequestError(`Cursor sort field "${cursor.field}" does not match query sort field "${field}"`);
385
386
  var nativeCursor = (cursor, field) => {
386
387
  if (cursor.kind !== "native" || cursor.field !== field)
387
388
  throw cursorMismatch(cursor, field);
@@ -429,8 +430,8 @@ var recordConditions = (query) => {
429
430
  }
430
431
  }
431
432
  inFilter("app_id", f.appId);
432
- inFilter("entity_id", f.entityId);
433
- inFilter("principal_id", f.principalId);
433
+ inFilter("entity_id", f.createdBy?.subjectId);
434
+ inFilter("principal_id", f.createdBy?.principalId);
434
435
  rangeFilter("created_at", f.createdAt);
435
436
  rangeFilter("updated_at", f.updatedAt);
436
437
  const hasAssociation = (kind, clauses) => `r.id IN (SELECT a.record_id FROM associations a WHERE a.kind = '${kind}'` + clauses.map((c) => ` AND ${c}`).join("") + `)`;
@@ -440,14 +441,22 @@ var recordConditions = (query) => {
440
441
  params.push(tag);
441
442
  }
442
443
  }
443
- if (f.hasAttachment) {
444
- conditions.push(hasAssociation("attachment", ["a.label = ?"]));
445
- params.push(f.hasAttachment);
444
+ if (f.attachment) {
445
+ const clauses = [];
446
+ if (f.attachment.label !== void 0) {
447
+ clauses.push("a.label = ?");
448
+ params.push(f.attachment.label);
449
+ }
450
+ if (f.attachment.fileId !== void 0) {
451
+ clauses.push("a.file_id = ?");
452
+ params.push(f.attachment.fileId);
453
+ }
454
+ conditions.push(hasAssociation("attachment", clauses));
446
455
  }
447
- if (f.attachmentFileId) {
456
+ if (f.referencesFileId) {
448
457
  conditions.push(`(${hasAssociation("attachment", ["a.file_id = ?"])}
449
458
  OR r.id IN (SELECT ci.record_id FROM content_index ci WHERE ci.file_id = ?))`);
450
- params.push(f.attachmentFileId, f.attachmentFileId);
459
+ params.push(f.referencesFileId, f.referencesFileId);
451
460
  }
452
461
  if (f.relatedTo) {
453
462
  const clauses = [];
@@ -458,11 +467,11 @@ var recordConditions = (query) => {
458
467
  }
459
468
  if (target) {
460
469
  clauses.push("a.related_scope = ?");
461
- params.push(target.scope);
462
- if (target.scope === "record") {
470
+ params.push(target.kind);
471
+ if (target.kind === "record") {
463
472
  clauses.push("a.related_ns = ?", "a.related_id = ?", "a.related_stack = ?");
464
473
  params.push("", target.recordId, target.stackUrl ?? "");
465
- } else if (target.scope === "entity") {
474
+ } else if (target.kind === "entity") {
466
475
  clauses.push("a.related_ns = ?", "a.related_id = ?");
467
476
  params.push("", target.entityId);
468
477
  } else {
@@ -567,6 +576,10 @@ var buildQueryPlan = (query) => {
567
576
 
568
577
  // ../sqlite-shared/dist/mappers.js
569
578
  var toMs = (d) => d.getTime();
579
+ var actorFromColumns = (subject, principal) => subject == null ? void 0 : {
580
+ subjectId: subject,
581
+ ...principal != null && { principalId: principal }
582
+ };
570
583
  var fromMs = (ms) => new Date(ms);
571
584
  var partitionAssociations = (associations) => ({
572
585
  associations: associations.filter((a) => a.kind !== "permission" && a.kind !== "anyone"),
@@ -583,16 +596,14 @@ var rowToRecord = (row, associations) => {
583
596
  };
584
597
  if (row.parent_id != null)
585
598
  record.parentId = row.parent_id;
586
- if (row.entity_id != null)
587
- record.entityId = row.entity_id;
588
599
  if (row.app_id != null)
589
600
  record.appId = row.app_id;
590
- if (row.principal_id != null)
591
- record.principalId = row.principal_id;
592
- if (row.updated_by != null)
593
- record.updatedBy = row.updated_by;
594
- if (row.updated_via != null)
595
- record.updatedVia = row.updated_via;
601
+ const createdBy = actorFromColumns(row.entity_id, row.principal_id);
602
+ if (createdBy)
603
+ record.createdBy = createdBy;
604
+ const updatedBy = actorFromColumns(row.updated_by, row.updated_via);
605
+ if (updatedBy)
606
+ record.updatedBy = updatedBy;
596
607
  if (row.deleted_at != null)
597
608
  record.deletedAt = fromMs(row.deleted_at);
598
609
  if (row.unlisted_at != null)
@@ -641,31 +652,31 @@ var associationKeyColumns = (a) => {
641
652
  return [a.fileId, "", "", "", "", ""];
642
653
  if (a.kind === "permission") {
643
654
  const g = a.grantee;
644
- return g.scope === "entity" ? ["", "entity", g.entityId, "", "", ""] : ["", "group", g.groupId, "", "", g.role];
655
+ return g.kind === "entity" ? ["", "entity", g.entityId, "", "", ""] : ["", "group", g.groupId, "", "", g.role];
645
656
  }
646
657
  if (a.kind !== "relationship")
647
658
  return ["", "", "", "", "", ""];
648
659
  const t = a.target;
649
- if (t.scope === "entity")
660
+ if (t.kind === "entity")
650
661
  return ["", "entity", t.entityId, "", "", ""];
651
- if (t.scope === "external")
662
+ if (t.kind === "external")
652
663
  return ["", "external", t.id, t.ns, "", ""];
653
664
  return ["", "record", t.recordId, "", t.stackUrl ?? "", ""];
654
665
  };
655
666
  var rowToGrantee = (row) => row.related_scope === "group" ? {
656
- scope: "group",
667
+ kind: "group",
657
668
  groupId: row.related_id,
658
669
  role: row.related_role
659
- } : { scope: "entity", entityId: row.related_id };
670
+ } : { kind: "entity", entityId: row.related_id };
660
671
  var rowToTarget = (row) => {
661
672
  const id = row.related_id;
662
673
  if (row.related_scope === "entity")
663
- return { scope: "entity", entityId: id };
674
+ return { kind: "entity", entityId: id };
664
675
  if (row.related_scope === "external") {
665
- return { scope: "external", ns: row.related_ns, id };
676
+ return { kind: "external", ns: row.related_ns, id };
666
677
  }
667
678
  const stackUrl = row.related_stack;
668
- return { scope: "record", recordId: id, ...stackUrl && { stackUrl } };
679
+ return { kind: "record", recordId: id, ...stackUrl && { stackUrl } };
669
680
  };
670
681
  var rowToType = (row) => {
671
682
  const t = {
@@ -688,12 +699,12 @@ var rowToVersion = (row) => {
688
699
  content: JSON.parse(row.content),
689
700
  updatedAt: fromMs(row.updated_at)
690
701
  };
691
- if (row.entity_id != null)
692
- v.entityId = row.entity_id;
693
- if (row.updated_by != null)
694
- v.updatedBy = row.updated_by;
695
- if (row.updated_via != null)
696
- v.updatedVia = row.updated_via;
702
+ const createdBy = actorFromColumns(row.entity_id, row.principal_id);
703
+ if (createdBy)
704
+ v.createdBy = createdBy;
705
+ const updatedBy = actorFromColumns(row.updated_by, row.updated_via);
706
+ if (updatedBy)
707
+ v.updatedBy = updatedBy;
697
708
  return v;
698
709
  };
699
710
  var rowToJournalEntry = (row) => {
@@ -736,13 +747,13 @@ var tryReadStackConfig = (exec) => {
736
747
  const content = JSON.parse(row.content);
737
748
  return { entityId: content.entityId, timezone: content.timezone };
738
749
  };
739
- var asStackQueryError = (query, run) => {
750
+ var asStackBadRequestError = (query, run) => {
740
751
  try {
741
752
  return run();
742
753
  } catch (err) {
743
754
  if (!query.filter?.search)
744
755
  throw err;
745
- throw new StackQueryError(`Search text could not be parsed: ${err instanceof Error ? err.message : String(err)}`);
756
+ throw new StackBadRequestError(`Search text could not be parsed: ${err instanceof Error ? err.message : String(err)}`);
746
757
  }
747
758
  };
748
759
  var versionConflict = (id, expected, actual) => new StackVersionConflictError(`Record "${id}" is at version ${actual}, expected ${expected}`, id, expected, actual);
@@ -751,6 +762,7 @@ var VERSION_COLUMNS = [
751
762
  "content",
752
763
  "updated_at",
753
764
  "entity_id",
765
+ "principal_id",
754
766
  "updated_by",
755
767
  "updated_via"
756
768
  ];
@@ -758,9 +770,10 @@ var versionRowValues = (version) => [
758
770
  version.typeId,
759
771
  JSON.stringify(version.content),
760
772
  toMs(version.updatedAt),
761
- version.entityId ?? null,
762
- version.updatedBy ?? null,
763
- version.updatedVia ?? null
773
+ version.createdBy?.subjectId ?? null,
774
+ version.createdBy?.principalId ?? null,
775
+ version.updatedBy?.subjectId ?? null,
776
+ version.updatedBy?.principalId ?? null
764
777
  ];
765
778
  var indexedFieldKinds = (schema) => {
766
779
  const kinds = /* @__PURE__ */ new Map();
@@ -789,7 +802,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
789
802
  /**
790
803
  * Precondition check for a mutation that has already read the record —
791
804
  * either because it can't fold the check into its own statement
792
- * (hardDeleteRecord: children reference the row, so the DELETE can't
805
+ * (purgeRecord: children reference the row, so the DELETE can't
793
806
  * carry it), or because it has to settle the precondition *before*
794
807
  * fts5Strategy.remove() runs, which needs the records row still holding
795
808
  * the old content. In the latter case versionedUpdate() re-checks it
@@ -818,7 +831,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
818
831
  /**
819
832
  * The one shape every mutating UPDATE in this class has: whatever columns
820
833
  * the caller is changing, then the version bump and the actor/timestamp
821
- * stamp that every mutation owes, gated on the opt-in `expectedVersion`
834
+ * stamp that every mutation owes, gated on the opt-in `ifVersion`
822
835
  * precondition and failing loudly when nothing matched.
823
836
  *
824
837
  * The guard rides in the WHERE clause even where the caller has already
@@ -827,17 +840,17 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
827
840
  * rather than silently overwritten.
828
841
  */
829
842
  versionedUpdate(id, sets, values, opts, now = toMs(/* @__PURE__ */ new Date())) {
830
- const guarded = opts.expectedVersion !== void 0;
843
+ const guarded = opts.ifVersion !== void 0;
831
844
  const changed = this.exec.run(`UPDATE records SET ${[...sets, "version = version + 1", "updated_at = ?", "updated_by = ?", "updated_via = ?"].join(", ")} WHERE id = ?${guarded ? " AND version = ?" : ""}`, [
832
845
  ...values,
833
846
  now,
834
- opts.updatedBy ?? null,
835
- opts.updatedVia ?? null,
847
+ opts.actor?.subjectId ?? null,
848
+ opts.actor?.principalId ?? null,
836
849
  id,
837
- ...guarded ? [opts.expectedVersion] : []
850
+ ...guarded ? [opts.ifVersion] : []
838
851
  ]);
839
852
  if (changed === 0)
840
- this.throwVersionConflict(id, opts.expectedVersion);
853
+ this.throwVersionConflict(id, opts.ifVersion);
841
854
  }
842
855
  /**
843
856
  * Read a record back after its own mutation committed, for the caller to
@@ -886,11 +899,11 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
886
899
  JSON.stringify(record.content),
887
900
  record.version,
888
901
  record.parentId ?? null,
889
- record.entityId ?? null,
902
+ record.createdBy?.subjectId ?? null,
890
903
  record.appId ?? null,
891
- record.principalId ?? null,
892
- record.updatedBy ?? null,
893
- record.updatedVia ?? null,
904
+ record.createdBy?.principalId ?? null,
905
+ record.updatedBy?.subjectId ?? null,
906
+ record.updatedBy?.principalId ?? null,
894
907
  record.deletedAt ? toMs(record.deletedAt) : null,
895
908
  record.unlistedAt ? toMs(record.unlistedAt) : null
896
909
  ]);
@@ -943,13 +956,13 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
943
956
  * `associations`, `parentId` and/or `unlisted` — Stack computes this from
944
957
  * which ops the set actually moves. Such a write still lands its columns,
945
958
  * but leaves `version`, `updatedAt` and the actor stamps exactly as they
946
- * stood and takes no snapshot. `expectedVersion`, if given, is re-checked
959
+ * stood and takes no snapshot. `ifVersion`, if given, is re-checked
947
960
  * synchronously inside the transaction — the CAS guard the bumping path
948
961
  * gets from its UPDATE's WHERE clause, restated as a read because the
949
962
  * UPDATE here carries no version predicate. See
950
963
  * docs/spec/versioning.md § Version history.
951
964
  *
952
- * The expectedVersion check for a bumping write is standalone rather than
965
+ * The ifVersion check for a bumping write is standalone rather than
953
966
  * folded into the UPDATE alone, because a content change needs
954
967
  * fts.remove() to run *before* the records-table content changes. The
955
968
  * guard is still in the UPDATE's WHERE, so a writer that slipped in
@@ -959,7 +972,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
959
972
  const existing = await this.getRecord(id);
960
973
  if (!existing)
961
974
  throw new StackNotFoundError(`Record not found: "${id}"`);
962
- this.checkExpectedVersion(existing, opts.expectedVersion);
975
+ this.checkExpectedVersion(existing, opts.ifVersion);
963
976
  const now = toMs(/* @__PURE__ */ new Date());
964
977
  const sets = [];
965
978
  const values = [];
@@ -976,7 +989,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
976
989
  const current = this.readRecord(id);
977
990
  if (!current)
978
991
  throw new StackNotFoundError(`Record not found: "${id}"`);
979
- this.checkExpectedVersion(current, opts.expectedVersion);
992
+ this.checkExpectedVersion(current, opts.ifVersion);
980
993
  if (sets.length > 0) {
981
994
  this.exec.run(`UPDATE records SET ${sets.join(", ")} WHERE id = ?`, [...values, id]);
982
995
  }
@@ -1006,8 +1019,8 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
1006
1019
  return this.reread(id, "mutateRecord");
1007
1020
  }
1008
1021
  async deleteRecord(id, opts = {}) {
1009
- if (opts.hard) {
1010
- return this.exec.transaction(() => this.hardDeleteRecord(id, opts.expectedVersion));
1022
+ if (opts.purge) {
1023
+ return this.exec.transaction(() => this.purgeRecord(id, opts.ifVersion));
1011
1024
  }
1012
1025
  const now = toMs(/* @__PURE__ */ new Date());
1013
1026
  this.exec.transaction(() => {
@@ -1023,18 +1036,18 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
1023
1036
  * index rows, and row, returning the record as it stood — the last copy that will ever
1024
1037
  * exist, read here so that nothing can overtake it between the read and
1025
1038
  * the deletes. Null when there was no such record. No write() call —
1026
- * callers batch it. expectedVersion is checked first so a lost CAS race
1039
+ * callers batch it. ifVersion is checked first so a lost CAS race
1027
1040
  * leaves nothing touched (children reference the row, so the check can't
1028
1041
  * fold into the final DELETE).
1029
1042
  */
1030
- hardDeleteRecord(id, expectedVersion) {
1043
+ purgeRecord(id, ifVersion) {
1031
1044
  const purged = this.readRecord(id);
1032
1045
  if (!purged) {
1033
- if (expectedVersion !== void 0)
1046
+ if (ifVersion !== void 0)
1034
1047
  throw new StackNotFoundError(`Record not found: "${id}"`);
1035
1048
  return null;
1036
1049
  }
1037
- this.checkExpectedVersion(purged, expectedVersion);
1050
+ this.checkExpectedVersion(purged, ifVersion);
1038
1051
  fts5Strategy.remove(this.exec, id);
1039
1052
  this.exec.run("DELETE FROM associations WHERE record_id = ?", [id]);
1040
1053
  this.exec.run("DELETE FROM versions WHERE record_id = ?", [id]);
@@ -1056,7 +1069,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
1056
1069
  const existing = await this.getRecord(id);
1057
1070
  if (!existing)
1058
1071
  throw new StackNotFoundError(`Record not found: "${id}"`);
1059
- this.checkExpectedVersion(existing, opts.expectedVersion);
1072
+ this.checkExpectedVersion(existing, opts.ifVersion);
1060
1073
  const target = await this.getVersion(id, version);
1061
1074
  if (!target)
1062
1075
  throw new StackNotFoundError(`Version not found: "${id}"@${version}`);
@@ -1072,7 +1085,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
1072
1085
  const existing = await this.getRecord(id);
1073
1086
  if (!existing)
1074
1087
  throw new StackNotFoundError(`Record not found: "${id}"`);
1075
- this.checkExpectedVersion(existing, opts.expectedVersion);
1088
+ this.checkExpectedVersion(existing, opts.ifVersion);
1076
1089
  this.exec.transaction(() => {
1077
1090
  if (opts.snapshot)
1078
1091
  this.snapshotBeforeMutation(id, opts.snapshot);
@@ -1107,7 +1120,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
1107
1120
  if (budget <= 0)
1108
1121
  break;
1109
1122
  const statement2 = atBudget(page2, budget);
1110
- rows.push(...asStackQueryError(query, () => this.exec.all(statement2.sql, statement2.params)));
1123
+ rows.push(...asStackBadRequestError(query, () => this.exec.all(statement2.sql, statement2.params)));
1111
1124
  }
1112
1125
  const hasMore = rows.length > limit;
1113
1126
  const page = hasMore ? rows.slice(0, limit) : rows;
@@ -1120,7 +1133,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
1120
1133
  return { records, cursor };
1121
1134
  }
1122
1135
  /**
1123
- * Atomically verify fileId is unreferenced, then hard-delete its
1136
+ * Atomically verify fileId is unreferenced, then purge its
1124
1137
  * metadata records across every typeId in `metadataTypeIds` (see
1125
1138
  * deleteUnreferencedAttachmentRecords on the adapter contract). A real
1126
1139
  * SQL transaction of synchronous calls — no `await` between the check
@@ -1142,7 +1155,7 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
1142
1155
  WHERE type_id IN (${placeholders}) AND json_extract(content, '$.fileId') = ?`, [...metadataTypeIds, fileId]);
1143
1156
  const deleted = [];
1144
1157
  for (const row of metaRows) {
1145
- const purged = this.hardDeleteRecord(row.id);
1158
+ const purged = this.purgeRecord(row.id);
1146
1159
  if (purged)
1147
1160
  deleted.push(purged);
1148
1161
  }
@@ -1264,9 +1277,9 @@ var SharedSqlRecordLogic = class _SharedSqlRecordLogic {
1264
1277
  }
1265
1278
  const conditions = ["record_id = ?"];
1266
1279
  const values = [id];
1267
- if (query.sinceSeq !== void 0) {
1280
+ if (query.afterSeq !== void 0) {
1268
1281
  conditions.push("seq > ?");
1269
- values.push(query.sinceSeq);
1282
+ values.push(query.afterSeq);
1270
1283
  }
1271
1284
  const rows = this.exec.all(`SELECT * FROM journal WHERE ${conditions.join(" AND ")} ORDER BY seq ASC${query.limit !== void 0 ? " LIMIT ?" : ""}`, query.limit !== void 0 ? [...values, query.limit] : values);
1272
1285
  return rows.map(rowToJournalEntry);
@@ -1576,11 +1589,12 @@ var DoSQLiteRecordAdapter = class _DoSQLiteRecordAdapter extends SharedSqlRecord
1576
1589
  super(exec, config);
1577
1590
  }
1578
1591
  /**
1579
- * Create (or reattach to) the adapter for a DO instance. There is no
1580
- * initialize()/open() split the way file-based adapters need one: a DO
1581
- * id either already has a config record (a previous call created it —
1582
- * reattach, opts.entityId/timezone ignored in favor of what's stored)
1583
- * or it doesn't (first call — opts.entityId/timezone become the config).
1592
+ * Open the adapter for a DO instance, initializing its storage on first
1593
+ * use — LocalAdapter.openOrInitialize()'s counterpart. There is no
1594
+ * separate initialize()/open() the way file-based adapters need one: a
1595
+ * DO id either already has a config record (reattach,
1596
+ * opts.ownerEntityId/timezone ignored in favor of what's stored) or it
1597
+ * doesn't (first call — opts.ownerEntityId/timezone become the config).
1584
1598
  * Schema DDL is `CREATE TABLE IF NOT EXISTS`, so running it every call
1585
1599
  * is idempotent and cheap.
1586
1600
  *
@@ -1588,10 +1602,10 @@ var DoSQLiteRecordAdapter = class _DoSQLiteRecordAdapter extends SharedSqlRecord
1588
1602
  * outright ("not authorized") — verified against the real runtime, not
1589
1603
  * assumed — hence `wal: false`, unlike record-adapter-sqlite.
1590
1604
  */
1591
- static async create(storage, opts) {
1605
+ static async openOrInitialize(storage, opts) {
1592
1606
  const exec = new DurableObjectSqliteExecutor(storage);
1593
1607
  applyRecordSchema(exec);
1594
- const config = tryReadStackConfig(exec) ?? insertConfigRecord(exec, opts.entityId, opts.timezone);
1608
+ const config = tryReadStackConfig(exec) ?? insertConfigRecord(exec, opts.ownerEntityId, opts.timezone);
1595
1609
  return new _DoSQLiteRecordAdapter(exec, config);
1596
1610
  }
1597
1611
  // -------------------------------------------------------