@atscript/db-memory 0.1.136 → 0.1.138

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.cjs CHANGED
@@ -539,12 +539,19 @@ function accumulatorFactory(expr) {
539
539
  //#endregion
540
540
  //#region src/lib/memory-adapter.ts
541
541
  /**
542
+ * One database per {@link DbSpace}, shared by every adapter the space builds —
543
+ * the administrative adapter schema sync drops tables through included. A
544
+ * space is the lifetime of an in-memory database: a fresh space starts empty.
545
+ */
546
+ const databases = /* @__PURE__ */ new WeakMap();
547
+ /**
542
548
  * In-memory {@link BaseDbAdapter} implementation.
543
549
  *
544
550
  * Runs in one of two modes:
545
- * - STORED mode (default): storage is a plain `Map` living on the adapter
546
- * instance — adapter instances are 1:1 with a readable (table/view), so the
547
- * Map is this table's whole store. Documents are kept in their nested PHYSICAL
551
+ * - STORED mode (default): rows live in a `Map` of the space's in-memory
552
+ * database, keyed by table name (see {@link databases}), so every adapter of
553
+ * the space that serves a name sees the same table, and the administrative
554
+ * adapter can drop it by name. Documents are kept in their nested PHYSICAL
548
555
  * shape (no flattening), which is why {@link supportsNestedObjects} is `true`.
549
556
  * Full CRUD surface: inserts, reads, update / replace / delete with
550
557
  * optimistic-concurrency CAS.
@@ -556,12 +563,10 @@ function accumulatorFactory(expr) {
556
563
  */
557
564
  var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
558
565
  /**
559
- * The table's store, keyed by {@link pkKey}. Values are the stored rows in
560
- * nested physical shape. An instance field — no `ensureTable` DDL needed.
566
+ * The in-memory database: the space's (see {@link databases}), resolved once
567
+ * in {@link registerSpace}; an adapter used without a space keeps this own one.
561
568
  */
562
- rows = /* @__PURE__ */ new Map();
563
- /** Unique indexes recorded by {@link syncIndexes}. Enforced on insert. */
564
- uniqueIndexes = [];
569
+ _db = /* @__PURE__ */ new Map();
565
570
  /** Memoized physical PK field names — stable for the adapter's lifetime. */
566
571
  _pkFieldsCache;
567
572
  /**
@@ -571,14 +576,6 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
571
576
  */
572
577
  _incrementFieldsCache;
573
578
  /**
574
- * Running per-field auto-increment counters (PHYSICAL name → last value
575
- * handed out). Lives on the adapter INSTANCE, so it resets whenever a new
576
- * DbSpace/adapter is built — correct for an in-memory store (parity with
577
- * SQLite `:memory:`, whose sequence also restarts with a fresh DB). Never
578
- * persisted.
579
- */
580
- _incrementCounters = /* @__PURE__ */ new Map();
581
- /**
582
579
  * Provider (read-through) backing closure. When set, this adapter is
583
580
  * PROVIDER-BACKED: reads recompute rows from this closure per request and all
584
581
  * writes are rejected (see {@link _assertWritable}). `undefined` ⇒ stored mode.
@@ -640,6 +637,43 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
640
637
  }
641
638
  return String(id);
642
639
  }
640
+ /** Joins the space's in-memory database (see {@link databases}), creating it on first use. */
641
+ registerSpace(space) {
642
+ let db = databases.get(space);
643
+ if (!db) {
644
+ db = /* @__PURE__ */ new Map();
645
+ databases.set(space, db);
646
+ }
647
+ this._db = db;
648
+ }
649
+ /**
650
+ * This table's state, or `undefined` while it does not exist (never synced,
651
+ * or dropped) — or when no readable is registered.
652
+ */
653
+ _peekState() {
654
+ const table = this._table;
655
+ return table && this._db.get(table.tableName);
656
+ }
657
+ /**
658
+ * This table's state, created on first use — by `ensureTable`, or by a write
659
+ * to a table that was never synced (implicit create, like a MongoDB
660
+ * collection). Reads never create it.
661
+ */
662
+ _state() {
663
+ const db = this._db;
664
+ const name = this._table.tableName;
665
+ let state = db.get(name);
666
+ if (!state) {
667
+ state = {
668
+ kind: this._table.isView ? "view" : "table",
669
+ rows: /* @__PURE__ */ new Map(),
670
+ uniqueIndexes: [],
671
+ incrementCounters: /* @__PURE__ */ new Map()
672
+ };
673
+ db.set(name, state);
674
+ }
675
+ return state;
676
+ }
643
677
  /**
644
678
  * Physical names of the primary-key field(s). Single `@meta.id` resolves via
645
679
  * {@link AtscriptDbReadable.metaIdPhysical}; a composite key maps each logical
@@ -688,7 +722,9 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
688
722
  * are computed so an increment PRIMARY KEY produces a real `insertedId` and
689
723
  * stores under a real key. The memory analogue of the Mongo adapter's
690
724
  * insert-time increment (Mongo uses an atomic `__atscript_counters`
691
- * collection; an in-memory store just keeps the counter on the instance):
725
+ * collection; an in-memory store keeps the counter in the table's state, so
726
+ * it restarts with a fresh space or when the table is dropped — parity with
727
+ * SQLite `:memory:`; never persisted):
692
728
  *
693
729
  * - No value for the field → assign the next counter value. First use starts
694
730
  * at `start ?? 1` (`max(counter, (start ?? 1) - 1) + 1`); thereafter it is
@@ -698,18 +734,18 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
698
734
  * - Explicit value present → keep it, but advance the counter to
699
735
  * `max(counter, value)` so a later auto value can never collide with it.
700
736
  */
701
- _applyIncrements(row) {
737
+ _applyIncrements(counters, row) {
702
738
  const fields = this._incrementFields();
703
739
  if (fields.size === 0) return;
704
740
  for (const [physical, start] of fields) {
705
741
  const floor = (start ?? 1) - 1;
706
- const base = this._incrementCounters.get(physical) ?? floor;
742
+ const base = counters.get(physical) ?? floor;
707
743
  const current = row[physical];
708
744
  if (current === void 0 || current === null) {
709
745
  const next = base + 1;
710
746
  row[physical] = next;
711
- this._incrementCounters.set(physical, next);
712
- } else if (typeof current === "number") this._incrementCounters.set(physical, Math.max(base, current));
747
+ counters.set(physical, next);
748
+ } else if (typeof current === "number") counters.set(physical, Math.max(base, current));
713
749
  }
714
750
  }
715
751
  /**
@@ -785,7 +821,8 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
785
821
  /**
786
822
  * Snapshot seam for reads. Returns the current rows.
787
823
  *
788
- * - Stored mode reads the instance Map directly (insertion order preserved).
824
+ * - Stored mode reads the table's Map directly (insertion order preserved);
825
+ * a table that does not exist reads as empty.
789
826
  * - Provider (read-through) mode calls {@link _provider} to recompute a fresh
790
827
  * snapshot per read.
791
828
  *
@@ -802,7 +839,7 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
802
839
  */
803
840
  _loadRows() {
804
841
  if (this._provider) return this._provider();
805
- return [...this.rows.values()];
842
+ return [...this._peekState()?.rows.values() ?? []];
806
843
  }
807
844
  /**
808
845
  * Clones the payload in, applies the version default, generates any
@@ -812,15 +849,15 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
812
849
  * the store. Called once per item by both `insertOne` and `insertMany`, so
813
850
  * increment values advance sequentially across a batch.
814
851
  */
815
- _insertRow(data) {
852
+ _insertRow(state, data) {
816
853
  const row = structuredClone(data);
817
854
  const versionColumn = this._table.versionColumn;
818
855
  if (versionColumn !== void 0 && !(versionColumn in row)) row[versionColumn] = 0;
819
- this._applyIncrements(row);
856
+ this._applyIncrements(state.incrementCounters, row);
820
857
  const key = this.pkKey(row);
821
- if (this.rows.has(key)) throw this._pkConflict();
822
- this._enforceUniqueIndexes(row);
823
- this.rows.set(key, row);
858
+ if (state.rows.has(key)) throw this._pkConflict();
859
+ this._enforceUniqueIndexes(state, row);
860
+ state.rows.set(key, row);
824
861
  const fallback = this._table.metaIdPhysical ? void 0 : this._compositeInsertedId(row);
825
862
  return this._resolveInsertedId(row, fallback);
826
863
  }
@@ -833,8 +870,8 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
833
870
  * `excludeKey` (when given) skips the row stored under that {@link pkKey} — so
834
871
  * a row updating its own unique value does not false-conflict with itself.
835
872
  */
836
- _enforceUniqueIndexes(row, excludeKey) {
837
- for (const index of this.uniqueIndexes) {
873
+ _enforceUniqueIndexes(state, row, excludeKey) {
874
+ for (const index of state.uniqueIndexes) {
838
875
  const tuple = [];
839
876
  let skip = false;
840
877
  for (const field of index.fields) {
@@ -846,7 +883,7 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
846
883
  tuple.push(value);
847
884
  }
848
885
  if (skip) continue;
849
- for (const [existingKey, existing] of this.rows) {
886
+ for (const [existingKey, existing] of state.rows) {
850
887
  if (excludeKey !== void 0 && existingKey === excludeKey) continue;
851
888
  let equal = true;
852
889
  for (let i = 0; i < index.fields.length; i++) if (!valuesEqual(getPath(existing, index.fields[i]), tuple[i])) {
@@ -862,11 +899,12 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
862
899
  }
863
900
  async insertOne(data) {
864
901
  this._assertWritable();
865
- return { insertedId: this._insertRow(data) };
902
+ return { insertedId: this._insertRow(this._state(), data) };
866
903
  }
867
904
  async insertMany(data) {
868
905
  this._assertWritable();
869
- const insertedIds = data.map((item) => this._insertRow(item));
906
+ const state = this._state();
907
+ const insertedIds = data.map((item) => this._insertRow(state, item));
870
908
  return {
871
909
  insertedCount: data.length,
872
910
  insertedIds
@@ -874,7 +912,7 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
874
912
  }
875
913
  /**
876
914
  * Selects the stored rows a write should touch, as `{ key, row }` pairs so
877
- * callers can mutate in place and re-key. Stored mode scans the instance Map
915
+ * callers can mutate in place and re-key. Stored mode scans the table's Map
878
916
  * directly (writes are authoritative against the store, unlike reads which go
879
917
  * through the {@link _loadRows} snapshot seam).
880
918
  *
@@ -885,14 +923,15 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
885
923
  * that has no version column is a misconfiguration and throws (mirrors the
886
924
  * Mongo adapter's `_buildCasFilter`).
887
925
  *
888
- * When `many` is `false` at most the first match is returned.
926
+ * When `many` is `false` at most the first match is returned. A table that
927
+ * does not exist (`state` undefined) matches nothing and is not created.
889
928
  */
890
- _selectForWrite(filter, expectedVersion, many) {
929
+ _selectForWrite(state, filter, expectedVersion, many) {
891
930
  const versionColumn = this._table.versionColumn;
892
931
  if (expectedVersion !== void 0 && versionColumn === void 0) throw new Error("expectedVersion requires a versioned table");
893
932
  const match = buildMemoryPredicate(filter);
894
933
  const matched = [];
895
- for (const [key, row] of this.rows) {
934
+ for (const [key, row] of state?.rows ?? []) {
896
935
  if (!match(row)) continue;
897
936
  if (expectedVersion !== void 0 && row[versionColumn] !== expectedVersion) continue;
898
937
  matched.push({
@@ -947,11 +986,11 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
947
986
  * NEW key (some other row already owns it) throws `CONFLICT`. Throws BEFORE
948
987
  * touching the Map so a failed re-key leaves the store unchanged.
949
988
  */
950
- _commitRow(oldKey, next) {
989
+ _commitRow(rows, oldKey, next) {
951
990
  const newKey = this.pkKey(next);
952
- if (newKey !== oldKey && this.rows.has(newKey)) throw this._pkConflict();
953
- if (newKey !== oldKey) this.rows.delete(oldKey);
954
- this.rows.set(newKey, next);
991
+ if (newKey !== oldKey && rows.has(newKey)) throw this._pkConflict();
992
+ if (newKey !== oldKey) rows.delete(oldKey);
993
+ rows.set(newKey, next);
955
994
  }
956
995
  /**
957
996
  * Update-then-commit for a single matched row: clones the pristine `row`,
@@ -961,25 +1000,26 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
961
1000
  * commits. Nothing is written to the store until both the unique and PK checks
962
1001
  * pass, so a conflict leaves the store untouched.
963
1002
  */
964
- _commitUpdate(oldKey, row, data, ops) {
1003
+ _commitUpdate(state, oldKey, row, data, ops) {
965
1004
  const next = structuredClone(row);
966
1005
  this._applyUpdate(next, data, ops);
967
1006
  this._bumpVersion(next, row);
968
- this._enforceUniqueIndexes(next, oldKey);
969
- this._commitRow(oldKey, next);
1007
+ this._enforceUniqueIndexes(state, next, oldKey);
1008
+ this._commitRow(state.rows, oldKey, next);
970
1009
  }
971
1010
  async replaceOne(filter, data, expectedVersion) {
972
1011
  this._assertWritable();
973
- const matched = this._selectForWrite(filter, expectedVersion, false);
974
- if (matched.length === 0) return {
1012
+ const state = this._peekState();
1013
+ const matched = this._selectForWrite(state, filter, expectedVersion, false);
1014
+ if (!state || matched.length === 0) return {
975
1015
  matchedCount: 0,
976
1016
  modifiedCount: 0
977
1017
  };
978
1018
  const { key, row } = matched[0];
979
1019
  const next = structuredClone(data);
980
1020
  this._bumpVersion(next, row);
981
- this._enforceUniqueIndexes(next, key);
982
- this._commitRow(key, next);
1021
+ this._enforceUniqueIndexes(state, next, key);
1022
+ this._commitRow(state.rows, key, next);
983
1023
  return {
984
1024
  matchedCount: 1,
985
1025
  modifiedCount: 1
@@ -987,13 +1027,14 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
987
1027
  }
988
1028
  async updateOne(filter, data, ops, expectedVersion) {
989
1029
  this._assertWritable();
990
- const matched = this._selectForWrite(filter, expectedVersion, false);
991
- if (matched.length === 0) return {
1030
+ const state = this._peekState();
1031
+ const matched = this._selectForWrite(state, filter, expectedVersion, false);
1032
+ if (!state || matched.length === 0) return {
992
1033
  matchedCount: 0,
993
1034
  modifiedCount: 0
994
1035
  };
995
1036
  const { key, row } = matched[0];
996
- this._commitUpdate(key, row, data, ops);
1037
+ this._commitUpdate(state, key, row, data, ops);
997
1038
  return {
998
1039
  matchedCount: 1,
999
1040
  modifiedCount: 1
@@ -1001,9 +1042,10 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
1001
1042
  }
1002
1043
  async deleteOne(filter) {
1003
1044
  this._assertWritable();
1004
- const matched = this._selectForWrite(filter, void 0, false);
1005
- if (matched.length === 0) return { deletedCount: 0 };
1006
- this.rows.delete(matched[0].key);
1045
+ const state = this._peekState();
1046
+ const matched = this._selectForWrite(state, filter, void 0, false);
1047
+ if (!state || matched.length === 0) return { deletedCount: 0 };
1048
+ state.rows.delete(matched[0].key);
1007
1049
  return { deletedCount: 1 };
1008
1050
  }
1009
1051
  /**
@@ -1113,8 +1155,13 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
1113
1155
  }
1114
1156
  async updateMany(filter, data, ops) {
1115
1157
  this._assertWritable();
1116
- const matched = this._selectForWrite(filter, void 0, true);
1117
- for (const { key, row } of matched) this._commitUpdate(key, row, data, ops);
1158
+ const state = this._peekState();
1159
+ if (!state) return {
1160
+ matchedCount: 0,
1161
+ modifiedCount: 0
1162
+ };
1163
+ const matched = this._selectForWrite(state, filter, void 0, true);
1164
+ for (const { key, row } of matched) this._commitUpdate(state, key, row, data, ops);
1118
1165
  return {
1119
1166
  matchedCount: matched.length,
1120
1167
  modifiedCount: matched.length
@@ -1122,8 +1169,13 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
1122
1169
  }
1123
1170
  async replaceMany(filter, data) {
1124
1171
  this._assertWritable();
1125
- const matched = this._selectForWrite(filter, void 0, true);
1126
- for (const { key, row } of matched) this._commitUpdate(key, row, data);
1172
+ const state = this._peekState();
1173
+ if (!state) return {
1174
+ matchedCount: 0,
1175
+ modifiedCount: 0
1176
+ };
1177
+ const matched = this._selectForWrite(state, filter, void 0, true);
1178
+ for (const { key, row } of matched) this._commitUpdate(state, key, row, data);
1127
1179
  return {
1128
1180
  matchedCount: matched.length,
1129
1181
  modifiedCount: matched.length
@@ -1131,8 +1183,10 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
1131
1183
  }
1132
1184
  async deleteMany(filter) {
1133
1185
  this._assertWritable();
1134
- const matched = this._selectForWrite(filter, void 0, true);
1135
- for (const { key } of matched) this.rows.delete(key);
1186
+ const state = this._peekState();
1187
+ if (!state) return { deletedCount: 0 };
1188
+ const matched = this._selectForWrite(state, filter, void 0, true);
1189
+ for (const { key } of matched) state.rows.delete(key);
1136
1190
  return { deletedCount: matched.length };
1137
1191
  }
1138
1192
  /**
@@ -1141,21 +1195,63 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
1141
1195
  * in-memory scan needs no plain/fulltext/geo index to answer queries.
1142
1196
  */
1143
1197
  async syncIndexes() {
1144
- this.uniqueIndexes = [];
1198
+ const uniqueIndexes = [];
1145
1199
  for (const index of this._table.indexes.values()) {
1146
1200
  if (index.type !== "unique") continue;
1147
- this.uniqueIndexes.push({
1201
+ uniqueIndexes.push({
1148
1202
  name: index.name,
1149
1203
  fields: index.fields.map((f) => f.name),
1150
1204
  optionalFields: new Set(index.fields.filter((f) => f.optional).map((f) => f.name))
1151
1205
  });
1152
1206
  }
1207
+ this._state().uniqueIndexes = uniqueIndexes;
1208
+ }
1209
+ /** Creates the table's (empty) state when it does not exist. Idempotent. */
1210
+ async ensureTable() {
1211
+ this._state();
1212
+ }
1213
+ /**
1214
+ * Whether the table exists in the space's in-memory database — created by
1215
+ * {@link ensureTable} (or a first write) and not dropped since. Schema sync
1216
+ * reports a table that does not exist as `create`. An external view always
1217
+ * exists: nothing in memory creates one, its rows come from a provider.
1218
+ * @since 0.1.137
1219
+ */
1220
+ async tableExists() {
1221
+ if ((0, _atscript_db.isAtscriptDbView)(this._table) && this._table.isExternal) return true;
1222
+ return this._peekState() !== void 0;
1153
1223
  }
1154
1224
  /**
1155
- * No-op: the store is the instance-level {@link rows} Map, which already
1156
- * exists. Safe to call repeatedly.
1225
+ * Kind of the object stored under `name` in the space's in-memory database,
1226
+ * or `undefined` when nothing is. Lets schema sync refuse a table declared
1227
+ * where a view exists (and the reverse) and an FK to a table that is missing.
1228
+ * @since 0.1.138
1157
1229
  */
1158
- async ensureTable() {}
1230
+ async getObjectKind(name) {
1231
+ return this._db.get(name)?.kind;
1232
+ }
1233
+ /**
1234
+ * Drops a table by name: its rows, unique indexes and increment counters go,
1235
+ * so the table added back starts empty. A missing table is not an error;
1236
+ * a view under that name is (`dropViewByName` drops views).
1237
+ * @since 0.1.137
1238
+ */
1239
+ async dropTableByName(tableName) {
1240
+ this._dropByName(tableName, "table");
1241
+ }
1242
+ /**
1243
+ * Drops a view by name — its (empty) state; a view holds no rows here.
1244
+ * A missing view is not an error; a table under that name is.
1245
+ * @since 0.1.137
1246
+ */
1247
+ async dropViewByName(viewName) {
1248
+ this._dropByName(viewName, "view");
1249
+ }
1250
+ _dropByName(name, kind) {
1251
+ const state = this._db.get(name);
1252
+ if (state && state.kind !== kind) throw new Error(`Cannot drop ${kind} "${name}": it is a ${state.kind}`);
1253
+ this._db.delete(name);
1254
+ }
1159
1255
  };
1160
1256
  /**
1161
1257
  * Ergonomic late-binding entry point for provider (read-through) mode. Resolves
@@ -1177,7 +1273,8 @@ function setMemoryProvider(space, type, fn) {
1177
1273
  /**
1178
1274
  * Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
1179
1275
  *
1180
- * Tables default to STORED mode (an instance-level `Map`). To make one table
1276
+ * Tables default to STORED mode (a `Map` per table in the space's in-memory
1277
+ * database, shared by every adapter of the space). To make one table
1181
1278
  * read-only and read-through from a runtime closure, call `setMemoryProvider`
1182
1279
  * (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
1183
1280
  * adapter has been built.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TFieldOps } from "@atscript/db";
1
+ import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TFieldOps } from "@atscript/db";
2
2
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
3
 
4
4
  //#region src/lib/memory-adapter.d.ts
@@ -12,9 +12,10 @@ type MemoryProviderFn = () => Array<Record<string, unknown>> | Promise<Array<Rec
12
12
  * In-memory {@link BaseDbAdapter} implementation.
13
13
  *
14
14
  * Runs in one of two modes:
15
- * - STORED mode (default): storage is a plain `Map` living on the adapter
16
- * instance — adapter instances are 1:1 with a readable (table/view), so the
17
- * Map is this table's whole store. Documents are kept in their nested PHYSICAL
15
+ * - STORED mode (default): rows live in a `Map` of the space's in-memory
16
+ * database, keyed by table name (see {@link databases}), so every adapter of
17
+ * the space that serves a name sees the same table, and the administrative
18
+ * adapter can drop it by name. Documents are kept in their nested PHYSICAL
18
19
  * shape (no flattening), which is why {@link supportsNestedObjects} is `true`.
19
20
  * Full CRUD surface: inserts, reads, update / replace / delete with
20
21
  * optimistic-concurrency CAS.
@@ -26,12 +27,10 @@ type MemoryProviderFn = () => Array<Record<string, unknown>> | Promise<Array<Rec
26
27
  */
27
28
  declare class MemoryAdapter extends BaseDbAdapter {
28
29
  /**
29
- * The table's store, keyed by {@link pkKey}. Values are the stored rows in
30
- * nested physical shape. An instance field — no `ensureTable` DDL needed.
30
+ * The in-memory database: the space's (see {@link databases}), resolved once
31
+ * in {@link registerSpace}; an adapter used without a space keeps this own one.
31
32
  */
32
- private rows;
33
- /** Unique indexes recorded by {@link syncIndexes}. Enforced on insert. */
34
- private uniqueIndexes;
33
+ private _db;
35
34
  /** Memoized physical PK field names — stable for the adapter's lifetime. */
36
35
  private _pkFieldsCache?;
37
36
  /**
@@ -40,14 +39,6 @@ declare class MemoryAdapter extends BaseDbAdapter {
40
39
  * {@link _incrementFields}).
41
40
  */
42
41
  private _incrementFieldsCache?;
43
- /**
44
- * Running per-field auto-increment counters (PHYSICAL name → last value
45
- * handed out). Lives on the adapter INSTANCE, so it resets whenever a new
46
- * DbSpace/adapter is built — correct for an in-memory store (parity with
47
- * SQLite `:memory:`, whose sequence also restarts with a fresh DB). Never
48
- * persisted.
49
- */
50
- private _incrementCounters;
51
42
  /**
52
43
  * Provider (read-through) backing closure. When set, this adapter is
53
44
  * PROVIDER-BACKED: reads recompute rows from this closure per request and all
@@ -100,6 +91,19 @@ declare class MemoryAdapter extends BaseDbAdapter {
100
91
  * coerces via `Number(id)`, everything else via `String(id)`.
101
92
  */
102
93
  prepareId(id: unknown, _fieldType: unknown): unknown;
94
+ /** Joins the space's in-memory database (see {@link databases}), creating it on first use. */
95
+ registerSpace(space: DbSpace): void;
96
+ /**
97
+ * This table's state, or `undefined` while it does not exist (never synced,
98
+ * or dropped) — or when no readable is registered.
99
+ */
100
+ private _peekState;
101
+ /**
102
+ * This table's state, created on first use — by `ensureTable`, or by a write
103
+ * to a table that was never synced (implicit create, like a MongoDB
104
+ * collection). Reads never create it.
105
+ */
106
+ private _state;
103
107
  /**
104
108
  * Physical names of the primary-key field(s). Single `@meta.id` resolves via
105
109
  * {@link AtscriptDbReadable.metaIdPhysical}; a composite key maps each logical
@@ -133,7 +137,9 @@ declare class MemoryAdapter extends BaseDbAdapter {
133
137
  * are computed so an increment PRIMARY KEY produces a real `insertedId` and
134
138
  * stores under a real key. The memory analogue of the Mongo adapter's
135
139
  * insert-time increment (Mongo uses an atomic `__atscript_counters`
136
- * collection; an in-memory store just keeps the counter on the instance):
140
+ * collection; an in-memory store keeps the counter in the table's state, so
141
+ * it restarts with a fresh space or when the table is dropped — parity with
142
+ * SQLite `:memory:`; never persisted):
137
143
  *
138
144
  * - No value for the field → assign the next counter value. First use starts
139
145
  * at `start ?? 1` (`max(counter, (start ?? 1) - 1) + 1`); thereafter it is
@@ -198,7 +204,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
198
204
  /**
199
205
  * Snapshot seam for reads. Returns the current rows.
200
206
  *
201
- * - Stored mode reads the instance Map directly (insertion order preserved).
207
+ * - Stored mode reads the table's Map directly (insertion order preserved);
208
+ * a table that does not exist reads as empty.
202
209
  * - Provider (read-through) mode calls {@link _provider} to recompute a fresh
203
210
  * snapshot per read.
204
211
  *
@@ -237,7 +244,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
237
244
  insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
238
245
  /**
239
246
  * Selects the stored rows a write should touch, as `{ key, row }` pairs so
240
- * callers can mutate in place and re-key. Stored mode scans the instance Map
247
+ * callers can mutate in place and re-key. Stored mode scans the table's Map
241
248
  * directly (writes are authoritative against the store, unlike reads which go
242
249
  * through the {@link _loadRows} snapshot seam).
243
250
  *
@@ -248,7 +255,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
248
255
  * that has no version column is a misconfiguration and throws (mirrors the
249
256
  * Mongo adapter's `_buildCasFilter`).
250
257
  *
251
- * When `many` is `false` at most the first match is returned.
258
+ * When `many` is `false` at most the first match is returned. A table that
259
+ * does not exist (`state` undefined) matches nothing and is not created.
252
260
  */
253
261
  private _selectForWrite;
254
262
  /**
@@ -378,11 +386,37 @@ declare class MemoryAdapter extends BaseDbAdapter {
378
386
  * in-memory scan needs no plain/fulltext/geo index to answer queries.
379
387
  */
380
388
  syncIndexes(): Promise<void>;
389
+ /** Creates the table's (empty) state when it does not exist. Idempotent. */
390
+ ensureTable(): Promise<void>;
381
391
  /**
382
- * No-op: the store is the instance-level {@link rows} Map, which already
383
- * exists. Safe to call repeatedly.
392
+ * Whether the table exists in the space's in-memory database — created by
393
+ * {@link ensureTable} (or a first write) and not dropped since. Schema sync
394
+ * reports a table that does not exist as `create`. An external view always
395
+ * exists: nothing in memory creates one, its rows come from a provider.
396
+ * @since 0.1.137
384
397
  */
385
- ensureTable(): Promise<void>;
398
+ tableExists(): Promise<boolean>;
399
+ /**
400
+ * Kind of the object stored under `name` in the space's in-memory database,
401
+ * or `undefined` when nothing is. Lets schema sync refuse a table declared
402
+ * where a view exists (and the reverse) and an FK to a table that is missing.
403
+ * @since 0.1.138
404
+ */
405
+ getObjectKind(name: string): Promise<TDbObjectKind | undefined>;
406
+ /**
407
+ * Drops a table by name: its rows, unique indexes and increment counters go,
408
+ * so the table added back starts empty. A missing table is not an error;
409
+ * a view under that name is (`dropViewByName` drops views).
410
+ * @since 0.1.137
411
+ */
412
+ dropTableByName(tableName: string): Promise<void>;
413
+ /**
414
+ * Drops a view by name — its (empty) state; a view holds no rows here.
415
+ * A missing view is not an error; a table under that name is.
416
+ * @since 0.1.137
417
+ */
418
+ dropViewByName(viewName: string): Promise<void>;
419
+ private _dropByName;
386
420
  }
387
421
  /**
388
422
  * Ergonomic late-binding entry point for provider (read-through) mode. Resolves
@@ -482,7 +516,8 @@ declare function projectRow(row: Record<string, unknown>, projection?: Record<st
482
516
  /**
483
517
  * Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
484
518
  *
485
- * Tables default to STORED mode (an instance-level `Map`). To make one table
519
+ * Tables default to STORED mode (a `Map` per table in the space's in-memory
520
+ * database, shared by every adapter of the space). To make one table
486
521
  * read-only and read-through from a runtime closure, call `setMemoryProvider`
487
522
  * (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
488
523
  * adapter has been built.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TFieldOps } from "@atscript/db";
1
+ import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TFieldOps } from "@atscript/db";
2
2
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
3
 
4
4
  //#region src/lib/memory-adapter.d.ts
@@ -12,9 +12,10 @@ type MemoryProviderFn = () => Array<Record<string, unknown>> | Promise<Array<Rec
12
12
  * In-memory {@link BaseDbAdapter} implementation.
13
13
  *
14
14
  * Runs in one of two modes:
15
- * - STORED mode (default): storage is a plain `Map` living on the adapter
16
- * instance — adapter instances are 1:1 with a readable (table/view), so the
17
- * Map is this table's whole store. Documents are kept in their nested PHYSICAL
15
+ * - STORED mode (default): rows live in a `Map` of the space's in-memory
16
+ * database, keyed by table name (see {@link databases}), so every adapter of
17
+ * the space that serves a name sees the same table, and the administrative
18
+ * adapter can drop it by name. Documents are kept in their nested PHYSICAL
18
19
  * shape (no flattening), which is why {@link supportsNestedObjects} is `true`.
19
20
  * Full CRUD surface: inserts, reads, update / replace / delete with
20
21
  * optimistic-concurrency CAS.
@@ -26,12 +27,10 @@ type MemoryProviderFn = () => Array<Record<string, unknown>> | Promise<Array<Rec
26
27
  */
27
28
  declare class MemoryAdapter extends BaseDbAdapter {
28
29
  /**
29
- * The table's store, keyed by {@link pkKey}. Values are the stored rows in
30
- * nested physical shape. An instance field — no `ensureTable` DDL needed.
30
+ * The in-memory database: the space's (see {@link databases}), resolved once
31
+ * in {@link registerSpace}; an adapter used without a space keeps this own one.
31
32
  */
32
- private rows;
33
- /** Unique indexes recorded by {@link syncIndexes}. Enforced on insert. */
34
- private uniqueIndexes;
33
+ private _db;
35
34
  /** Memoized physical PK field names — stable for the adapter's lifetime. */
36
35
  private _pkFieldsCache?;
37
36
  /**
@@ -40,14 +39,6 @@ declare class MemoryAdapter extends BaseDbAdapter {
40
39
  * {@link _incrementFields}).
41
40
  */
42
41
  private _incrementFieldsCache?;
43
- /**
44
- * Running per-field auto-increment counters (PHYSICAL name → last value
45
- * handed out). Lives on the adapter INSTANCE, so it resets whenever a new
46
- * DbSpace/adapter is built — correct for an in-memory store (parity with
47
- * SQLite `:memory:`, whose sequence also restarts with a fresh DB). Never
48
- * persisted.
49
- */
50
- private _incrementCounters;
51
42
  /**
52
43
  * Provider (read-through) backing closure. When set, this adapter is
53
44
  * PROVIDER-BACKED: reads recompute rows from this closure per request and all
@@ -100,6 +91,19 @@ declare class MemoryAdapter extends BaseDbAdapter {
100
91
  * coerces via `Number(id)`, everything else via `String(id)`.
101
92
  */
102
93
  prepareId(id: unknown, _fieldType: unknown): unknown;
94
+ /** Joins the space's in-memory database (see {@link databases}), creating it on first use. */
95
+ registerSpace(space: DbSpace): void;
96
+ /**
97
+ * This table's state, or `undefined` while it does not exist (never synced,
98
+ * or dropped) — or when no readable is registered.
99
+ */
100
+ private _peekState;
101
+ /**
102
+ * This table's state, created on first use — by `ensureTable`, or by a write
103
+ * to a table that was never synced (implicit create, like a MongoDB
104
+ * collection). Reads never create it.
105
+ */
106
+ private _state;
103
107
  /**
104
108
  * Physical names of the primary-key field(s). Single `@meta.id` resolves via
105
109
  * {@link AtscriptDbReadable.metaIdPhysical}; a composite key maps each logical
@@ -133,7 +137,9 @@ declare class MemoryAdapter extends BaseDbAdapter {
133
137
  * are computed so an increment PRIMARY KEY produces a real `insertedId` and
134
138
  * stores under a real key. The memory analogue of the Mongo adapter's
135
139
  * insert-time increment (Mongo uses an atomic `__atscript_counters`
136
- * collection; an in-memory store just keeps the counter on the instance):
140
+ * collection; an in-memory store keeps the counter in the table's state, so
141
+ * it restarts with a fresh space or when the table is dropped — parity with
142
+ * SQLite `:memory:`; never persisted):
137
143
  *
138
144
  * - No value for the field → assign the next counter value. First use starts
139
145
  * at `start ?? 1` (`max(counter, (start ?? 1) - 1) + 1`); thereafter it is
@@ -198,7 +204,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
198
204
  /**
199
205
  * Snapshot seam for reads. Returns the current rows.
200
206
  *
201
- * - Stored mode reads the instance Map directly (insertion order preserved).
207
+ * - Stored mode reads the table's Map directly (insertion order preserved);
208
+ * a table that does not exist reads as empty.
202
209
  * - Provider (read-through) mode calls {@link _provider} to recompute a fresh
203
210
  * snapshot per read.
204
211
  *
@@ -237,7 +244,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
237
244
  insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
238
245
  /**
239
246
  * Selects the stored rows a write should touch, as `{ key, row }` pairs so
240
- * callers can mutate in place and re-key. Stored mode scans the instance Map
247
+ * callers can mutate in place and re-key. Stored mode scans the table's Map
241
248
  * directly (writes are authoritative against the store, unlike reads which go
242
249
  * through the {@link _loadRows} snapshot seam).
243
250
  *
@@ -248,7 +255,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
248
255
  * that has no version column is a misconfiguration and throws (mirrors the
249
256
  * Mongo adapter's `_buildCasFilter`).
250
257
  *
251
- * When `many` is `false` at most the first match is returned.
258
+ * When `many` is `false` at most the first match is returned. A table that
259
+ * does not exist (`state` undefined) matches nothing and is not created.
252
260
  */
253
261
  private _selectForWrite;
254
262
  /**
@@ -378,11 +386,37 @@ declare class MemoryAdapter extends BaseDbAdapter {
378
386
  * in-memory scan needs no plain/fulltext/geo index to answer queries.
379
387
  */
380
388
  syncIndexes(): Promise<void>;
389
+ /** Creates the table's (empty) state when it does not exist. Idempotent. */
390
+ ensureTable(): Promise<void>;
381
391
  /**
382
- * No-op: the store is the instance-level {@link rows} Map, which already
383
- * exists. Safe to call repeatedly.
392
+ * Whether the table exists in the space's in-memory database — created by
393
+ * {@link ensureTable} (or a first write) and not dropped since. Schema sync
394
+ * reports a table that does not exist as `create`. An external view always
395
+ * exists: nothing in memory creates one, its rows come from a provider.
396
+ * @since 0.1.137
384
397
  */
385
- ensureTable(): Promise<void>;
398
+ tableExists(): Promise<boolean>;
399
+ /**
400
+ * Kind of the object stored under `name` in the space's in-memory database,
401
+ * or `undefined` when nothing is. Lets schema sync refuse a table declared
402
+ * where a view exists (and the reverse) and an FK to a table that is missing.
403
+ * @since 0.1.138
404
+ */
405
+ getObjectKind(name: string): Promise<TDbObjectKind | undefined>;
406
+ /**
407
+ * Drops a table by name: its rows, unique indexes and increment counters go,
408
+ * so the table added back starts empty. A missing table is not an error;
409
+ * a view under that name is (`dropViewByName` drops views).
410
+ * @since 0.1.137
411
+ */
412
+ dropTableByName(tableName: string): Promise<void>;
413
+ /**
414
+ * Drops a view by name — its (empty) state; a view holds no rows here.
415
+ * A missing view is not an error; a table under that name is.
416
+ * @since 0.1.137
417
+ */
418
+ dropViewByName(viewName: string): Promise<void>;
419
+ private _dropByName;
386
420
  }
387
421
  /**
388
422
  * Ergonomic late-binding entry point for provider (read-through) mode. Resolves
@@ -482,7 +516,8 @@ declare function projectRow(row: Record<string, unknown>, projection?: Record<st
482
516
  /**
483
517
  * Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
484
518
  *
485
- * Tables default to STORED mode (an instance-level `Map`). To make one table
519
+ * Tables default to STORED mode (a `Map` per table in the space's in-memory
520
+ * database, shared by every adapter of the space). To make one table
486
521
  * read-only and read-through from a runtime closure, call `setMemoryProvider`
487
522
  * (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
488
523
  * adapter has been built.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, walkFilter } from "@atscript/db";
1
+ import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, isAtscriptDbView, walkFilter } from "@atscript/db";
2
2
  import { assertAggregateFn, resolveAlias } from "@atscript/db/agg";
3
3
  import { bucketer } from "@uniqu/core";
4
4
  //#region src/lib/memory-filter.ts
@@ -538,12 +538,19 @@ function accumulatorFactory(expr) {
538
538
  //#endregion
539
539
  //#region src/lib/memory-adapter.ts
540
540
  /**
541
+ * One database per {@link DbSpace}, shared by every adapter the space builds —
542
+ * the administrative adapter schema sync drops tables through included. A
543
+ * space is the lifetime of an in-memory database: a fresh space starts empty.
544
+ */
545
+ const databases = /* @__PURE__ */ new WeakMap();
546
+ /**
541
547
  * In-memory {@link BaseDbAdapter} implementation.
542
548
  *
543
549
  * Runs in one of two modes:
544
- * - STORED mode (default): storage is a plain `Map` living on the adapter
545
- * instance — adapter instances are 1:1 with a readable (table/view), so the
546
- * Map is this table's whole store. Documents are kept in their nested PHYSICAL
550
+ * - STORED mode (default): rows live in a `Map` of the space's in-memory
551
+ * database, keyed by table name (see {@link databases}), so every adapter of
552
+ * the space that serves a name sees the same table, and the administrative
553
+ * adapter can drop it by name. Documents are kept in their nested PHYSICAL
547
554
  * shape (no flattening), which is why {@link supportsNestedObjects} is `true`.
548
555
  * Full CRUD surface: inserts, reads, update / replace / delete with
549
556
  * optimistic-concurrency CAS.
@@ -555,12 +562,10 @@ function accumulatorFactory(expr) {
555
562
  */
556
563
  var MemoryAdapter = class extends BaseDbAdapter {
557
564
  /**
558
- * The table's store, keyed by {@link pkKey}. Values are the stored rows in
559
- * nested physical shape. An instance field — no `ensureTable` DDL needed.
565
+ * The in-memory database: the space's (see {@link databases}), resolved once
566
+ * in {@link registerSpace}; an adapter used without a space keeps this own one.
560
567
  */
561
- rows = /* @__PURE__ */ new Map();
562
- /** Unique indexes recorded by {@link syncIndexes}. Enforced on insert. */
563
- uniqueIndexes = [];
568
+ _db = /* @__PURE__ */ new Map();
564
569
  /** Memoized physical PK field names — stable for the adapter's lifetime. */
565
570
  _pkFieldsCache;
566
571
  /**
@@ -570,14 +575,6 @@ var MemoryAdapter = class extends BaseDbAdapter {
570
575
  */
571
576
  _incrementFieldsCache;
572
577
  /**
573
- * Running per-field auto-increment counters (PHYSICAL name → last value
574
- * handed out). Lives on the adapter INSTANCE, so it resets whenever a new
575
- * DbSpace/adapter is built — correct for an in-memory store (parity with
576
- * SQLite `:memory:`, whose sequence also restarts with a fresh DB). Never
577
- * persisted.
578
- */
579
- _incrementCounters = /* @__PURE__ */ new Map();
580
- /**
581
578
  * Provider (read-through) backing closure. When set, this adapter is
582
579
  * PROVIDER-BACKED: reads recompute rows from this closure per request and all
583
580
  * writes are rejected (see {@link _assertWritable}). `undefined` ⇒ stored mode.
@@ -639,6 +636,43 @@ var MemoryAdapter = class extends BaseDbAdapter {
639
636
  }
640
637
  return String(id);
641
638
  }
639
+ /** Joins the space's in-memory database (see {@link databases}), creating it on first use. */
640
+ registerSpace(space) {
641
+ let db = databases.get(space);
642
+ if (!db) {
643
+ db = /* @__PURE__ */ new Map();
644
+ databases.set(space, db);
645
+ }
646
+ this._db = db;
647
+ }
648
+ /**
649
+ * This table's state, or `undefined` while it does not exist (never synced,
650
+ * or dropped) — or when no readable is registered.
651
+ */
652
+ _peekState() {
653
+ const table = this._table;
654
+ return table && this._db.get(table.tableName);
655
+ }
656
+ /**
657
+ * This table's state, created on first use — by `ensureTable`, or by a write
658
+ * to a table that was never synced (implicit create, like a MongoDB
659
+ * collection). Reads never create it.
660
+ */
661
+ _state() {
662
+ const db = this._db;
663
+ const name = this._table.tableName;
664
+ let state = db.get(name);
665
+ if (!state) {
666
+ state = {
667
+ kind: this._table.isView ? "view" : "table",
668
+ rows: /* @__PURE__ */ new Map(),
669
+ uniqueIndexes: [],
670
+ incrementCounters: /* @__PURE__ */ new Map()
671
+ };
672
+ db.set(name, state);
673
+ }
674
+ return state;
675
+ }
642
676
  /**
643
677
  * Physical names of the primary-key field(s). Single `@meta.id` resolves via
644
678
  * {@link AtscriptDbReadable.metaIdPhysical}; a composite key maps each logical
@@ -687,7 +721,9 @@ var MemoryAdapter = class extends BaseDbAdapter {
687
721
  * are computed so an increment PRIMARY KEY produces a real `insertedId` and
688
722
  * stores under a real key. The memory analogue of the Mongo adapter's
689
723
  * insert-time increment (Mongo uses an atomic `__atscript_counters`
690
- * collection; an in-memory store just keeps the counter on the instance):
724
+ * collection; an in-memory store keeps the counter in the table's state, so
725
+ * it restarts with a fresh space or when the table is dropped — parity with
726
+ * SQLite `:memory:`; never persisted):
691
727
  *
692
728
  * - No value for the field → assign the next counter value. First use starts
693
729
  * at `start ?? 1` (`max(counter, (start ?? 1) - 1) + 1`); thereafter it is
@@ -697,18 +733,18 @@ var MemoryAdapter = class extends BaseDbAdapter {
697
733
  * - Explicit value present → keep it, but advance the counter to
698
734
  * `max(counter, value)` so a later auto value can never collide with it.
699
735
  */
700
- _applyIncrements(row) {
736
+ _applyIncrements(counters, row) {
701
737
  const fields = this._incrementFields();
702
738
  if (fields.size === 0) return;
703
739
  for (const [physical, start] of fields) {
704
740
  const floor = (start ?? 1) - 1;
705
- const base = this._incrementCounters.get(physical) ?? floor;
741
+ const base = counters.get(physical) ?? floor;
706
742
  const current = row[physical];
707
743
  if (current === void 0 || current === null) {
708
744
  const next = base + 1;
709
745
  row[physical] = next;
710
- this._incrementCounters.set(physical, next);
711
- } else if (typeof current === "number") this._incrementCounters.set(physical, Math.max(base, current));
746
+ counters.set(physical, next);
747
+ } else if (typeof current === "number") counters.set(physical, Math.max(base, current));
712
748
  }
713
749
  }
714
750
  /**
@@ -784,7 +820,8 @@ var MemoryAdapter = class extends BaseDbAdapter {
784
820
  /**
785
821
  * Snapshot seam for reads. Returns the current rows.
786
822
  *
787
- * - Stored mode reads the instance Map directly (insertion order preserved).
823
+ * - Stored mode reads the table's Map directly (insertion order preserved);
824
+ * a table that does not exist reads as empty.
788
825
  * - Provider (read-through) mode calls {@link _provider} to recompute a fresh
789
826
  * snapshot per read.
790
827
  *
@@ -801,7 +838,7 @@ var MemoryAdapter = class extends BaseDbAdapter {
801
838
  */
802
839
  _loadRows() {
803
840
  if (this._provider) return this._provider();
804
- return [...this.rows.values()];
841
+ return [...this._peekState()?.rows.values() ?? []];
805
842
  }
806
843
  /**
807
844
  * Clones the payload in, applies the version default, generates any
@@ -811,15 +848,15 @@ var MemoryAdapter = class extends BaseDbAdapter {
811
848
  * the store. Called once per item by both `insertOne` and `insertMany`, so
812
849
  * increment values advance sequentially across a batch.
813
850
  */
814
- _insertRow(data) {
851
+ _insertRow(state, data) {
815
852
  const row = structuredClone(data);
816
853
  const versionColumn = this._table.versionColumn;
817
854
  if (versionColumn !== void 0 && !(versionColumn in row)) row[versionColumn] = 0;
818
- this._applyIncrements(row);
855
+ this._applyIncrements(state.incrementCounters, row);
819
856
  const key = this.pkKey(row);
820
- if (this.rows.has(key)) throw this._pkConflict();
821
- this._enforceUniqueIndexes(row);
822
- this.rows.set(key, row);
857
+ if (state.rows.has(key)) throw this._pkConflict();
858
+ this._enforceUniqueIndexes(state, row);
859
+ state.rows.set(key, row);
823
860
  const fallback = this._table.metaIdPhysical ? void 0 : this._compositeInsertedId(row);
824
861
  return this._resolveInsertedId(row, fallback);
825
862
  }
@@ -832,8 +869,8 @@ var MemoryAdapter = class extends BaseDbAdapter {
832
869
  * `excludeKey` (when given) skips the row stored under that {@link pkKey} — so
833
870
  * a row updating its own unique value does not false-conflict with itself.
834
871
  */
835
- _enforceUniqueIndexes(row, excludeKey) {
836
- for (const index of this.uniqueIndexes) {
872
+ _enforceUniqueIndexes(state, row, excludeKey) {
873
+ for (const index of state.uniqueIndexes) {
837
874
  const tuple = [];
838
875
  let skip = false;
839
876
  for (const field of index.fields) {
@@ -845,7 +882,7 @@ var MemoryAdapter = class extends BaseDbAdapter {
845
882
  tuple.push(value);
846
883
  }
847
884
  if (skip) continue;
848
- for (const [existingKey, existing] of this.rows) {
885
+ for (const [existingKey, existing] of state.rows) {
849
886
  if (excludeKey !== void 0 && existingKey === excludeKey) continue;
850
887
  let equal = true;
851
888
  for (let i = 0; i < index.fields.length; i++) if (!valuesEqual(getPath(existing, index.fields[i]), tuple[i])) {
@@ -861,11 +898,12 @@ var MemoryAdapter = class extends BaseDbAdapter {
861
898
  }
862
899
  async insertOne(data) {
863
900
  this._assertWritable();
864
- return { insertedId: this._insertRow(data) };
901
+ return { insertedId: this._insertRow(this._state(), data) };
865
902
  }
866
903
  async insertMany(data) {
867
904
  this._assertWritable();
868
- const insertedIds = data.map((item) => this._insertRow(item));
905
+ const state = this._state();
906
+ const insertedIds = data.map((item) => this._insertRow(state, item));
869
907
  return {
870
908
  insertedCount: data.length,
871
909
  insertedIds
@@ -873,7 +911,7 @@ var MemoryAdapter = class extends BaseDbAdapter {
873
911
  }
874
912
  /**
875
913
  * Selects the stored rows a write should touch, as `{ key, row }` pairs so
876
- * callers can mutate in place and re-key. Stored mode scans the instance Map
914
+ * callers can mutate in place and re-key. Stored mode scans the table's Map
877
915
  * directly (writes are authoritative against the store, unlike reads which go
878
916
  * through the {@link _loadRows} snapshot seam).
879
917
  *
@@ -884,14 +922,15 @@ var MemoryAdapter = class extends BaseDbAdapter {
884
922
  * that has no version column is a misconfiguration and throws (mirrors the
885
923
  * Mongo adapter's `_buildCasFilter`).
886
924
  *
887
- * When `many` is `false` at most the first match is returned.
925
+ * When `many` is `false` at most the first match is returned. A table that
926
+ * does not exist (`state` undefined) matches nothing and is not created.
888
927
  */
889
- _selectForWrite(filter, expectedVersion, many) {
928
+ _selectForWrite(state, filter, expectedVersion, many) {
890
929
  const versionColumn = this._table.versionColumn;
891
930
  if (expectedVersion !== void 0 && versionColumn === void 0) throw new Error("expectedVersion requires a versioned table");
892
931
  const match = buildMemoryPredicate(filter);
893
932
  const matched = [];
894
- for (const [key, row] of this.rows) {
933
+ for (const [key, row] of state?.rows ?? []) {
895
934
  if (!match(row)) continue;
896
935
  if (expectedVersion !== void 0 && row[versionColumn] !== expectedVersion) continue;
897
936
  matched.push({
@@ -946,11 +985,11 @@ var MemoryAdapter = class extends BaseDbAdapter {
946
985
  * NEW key (some other row already owns it) throws `CONFLICT`. Throws BEFORE
947
986
  * touching the Map so a failed re-key leaves the store unchanged.
948
987
  */
949
- _commitRow(oldKey, next) {
988
+ _commitRow(rows, oldKey, next) {
950
989
  const newKey = this.pkKey(next);
951
- if (newKey !== oldKey && this.rows.has(newKey)) throw this._pkConflict();
952
- if (newKey !== oldKey) this.rows.delete(oldKey);
953
- this.rows.set(newKey, next);
990
+ if (newKey !== oldKey && rows.has(newKey)) throw this._pkConflict();
991
+ if (newKey !== oldKey) rows.delete(oldKey);
992
+ rows.set(newKey, next);
954
993
  }
955
994
  /**
956
995
  * Update-then-commit for a single matched row: clones the pristine `row`,
@@ -960,25 +999,26 @@ var MemoryAdapter = class extends BaseDbAdapter {
960
999
  * commits. Nothing is written to the store until both the unique and PK checks
961
1000
  * pass, so a conflict leaves the store untouched.
962
1001
  */
963
- _commitUpdate(oldKey, row, data, ops) {
1002
+ _commitUpdate(state, oldKey, row, data, ops) {
964
1003
  const next = structuredClone(row);
965
1004
  this._applyUpdate(next, data, ops);
966
1005
  this._bumpVersion(next, row);
967
- this._enforceUniqueIndexes(next, oldKey);
968
- this._commitRow(oldKey, next);
1006
+ this._enforceUniqueIndexes(state, next, oldKey);
1007
+ this._commitRow(state.rows, oldKey, next);
969
1008
  }
970
1009
  async replaceOne(filter, data, expectedVersion) {
971
1010
  this._assertWritable();
972
- const matched = this._selectForWrite(filter, expectedVersion, false);
973
- if (matched.length === 0) return {
1011
+ const state = this._peekState();
1012
+ const matched = this._selectForWrite(state, filter, expectedVersion, false);
1013
+ if (!state || matched.length === 0) return {
974
1014
  matchedCount: 0,
975
1015
  modifiedCount: 0
976
1016
  };
977
1017
  const { key, row } = matched[0];
978
1018
  const next = structuredClone(data);
979
1019
  this._bumpVersion(next, row);
980
- this._enforceUniqueIndexes(next, key);
981
- this._commitRow(key, next);
1020
+ this._enforceUniqueIndexes(state, next, key);
1021
+ this._commitRow(state.rows, key, next);
982
1022
  return {
983
1023
  matchedCount: 1,
984
1024
  modifiedCount: 1
@@ -986,13 +1026,14 @@ var MemoryAdapter = class extends BaseDbAdapter {
986
1026
  }
987
1027
  async updateOne(filter, data, ops, expectedVersion) {
988
1028
  this._assertWritable();
989
- const matched = this._selectForWrite(filter, expectedVersion, false);
990
- if (matched.length === 0) return {
1029
+ const state = this._peekState();
1030
+ const matched = this._selectForWrite(state, filter, expectedVersion, false);
1031
+ if (!state || matched.length === 0) return {
991
1032
  matchedCount: 0,
992
1033
  modifiedCount: 0
993
1034
  };
994
1035
  const { key, row } = matched[0];
995
- this._commitUpdate(key, row, data, ops);
1036
+ this._commitUpdate(state, key, row, data, ops);
996
1037
  return {
997
1038
  matchedCount: 1,
998
1039
  modifiedCount: 1
@@ -1000,9 +1041,10 @@ var MemoryAdapter = class extends BaseDbAdapter {
1000
1041
  }
1001
1042
  async deleteOne(filter) {
1002
1043
  this._assertWritable();
1003
- const matched = this._selectForWrite(filter, void 0, false);
1004
- if (matched.length === 0) return { deletedCount: 0 };
1005
- this.rows.delete(matched[0].key);
1044
+ const state = this._peekState();
1045
+ const matched = this._selectForWrite(state, filter, void 0, false);
1046
+ if (!state || matched.length === 0) return { deletedCount: 0 };
1047
+ state.rows.delete(matched[0].key);
1006
1048
  return { deletedCount: 1 };
1007
1049
  }
1008
1050
  /**
@@ -1112,8 +1154,13 @@ var MemoryAdapter = class extends BaseDbAdapter {
1112
1154
  }
1113
1155
  async updateMany(filter, data, ops) {
1114
1156
  this._assertWritable();
1115
- const matched = this._selectForWrite(filter, void 0, true);
1116
- for (const { key, row } of matched) this._commitUpdate(key, row, data, ops);
1157
+ const state = this._peekState();
1158
+ if (!state) return {
1159
+ matchedCount: 0,
1160
+ modifiedCount: 0
1161
+ };
1162
+ const matched = this._selectForWrite(state, filter, void 0, true);
1163
+ for (const { key, row } of matched) this._commitUpdate(state, key, row, data, ops);
1117
1164
  return {
1118
1165
  matchedCount: matched.length,
1119
1166
  modifiedCount: matched.length
@@ -1121,8 +1168,13 @@ var MemoryAdapter = class extends BaseDbAdapter {
1121
1168
  }
1122
1169
  async replaceMany(filter, data) {
1123
1170
  this._assertWritable();
1124
- const matched = this._selectForWrite(filter, void 0, true);
1125
- for (const { key, row } of matched) this._commitUpdate(key, row, data);
1171
+ const state = this._peekState();
1172
+ if (!state) return {
1173
+ matchedCount: 0,
1174
+ modifiedCount: 0
1175
+ };
1176
+ const matched = this._selectForWrite(state, filter, void 0, true);
1177
+ for (const { key, row } of matched) this._commitUpdate(state, key, row, data);
1126
1178
  return {
1127
1179
  matchedCount: matched.length,
1128
1180
  modifiedCount: matched.length
@@ -1130,8 +1182,10 @@ var MemoryAdapter = class extends BaseDbAdapter {
1130
1182
  }
1131
1183
  async deleteMany(filter) {
1132
1184
  this._assertWritable();
1133
- const matched = this._selectForWrite(filter, void 0, true);
1134
- for (const { key } of matched) this.rows.delete(key);
1185
+ const state = this._peekState();
1186
+ if (!state) return { deletedCount: 0 };
1187
+ const matched = this._selectForWrite(state, filter, void 0, true);
1188
+ for (const { key } of matched) state.rows.delete(key);
1135
1189
  return { deletedCount: matched.length };
1136
1190
  }
1137
1191
  /**
@@ -1140,21 +1194,63 @@ var MemoryAdapter = class extends BaseDbAdapter {
1140
1194
  * in-memory scan needs no plain/fulltext/geo index to answer queries.
1141
1195
  */
1142
1196
  async syncIndexes() {
1143
- this.uniqueIndexes = [];
1197
+ const uniqueIndexes = [];
1144
1198
  for (const index of this._table.indexes.values()) {
1145
1199
  if (index.type !== "unique") continue;
1146
- this.uniqueIndexes.push({
1200
+ uniqueIndexes.push({
1147
1201
  name: index.name,
1148
1202
  fields: index.fields.map((f) => f.name),
1149
1203
  optionalFields: new Set(index.fields.filter((f) => f.optional).map((f) => f.name))
1150
1204
  });
1151
1205
  }
1206
+ this._state().uniqueIndexes = uniqueIndexes;
1207
+ }
1208
+ /** Creates the table's (empty) state when it does not exist. Idempotent. */
1209
+ async ensureTable() {
1210
+ this._state();
1211
+ }
1212
+ /**
1213
+ * Whether the table exists in the space's in-memory database — created by
1214
+ * {@link ensureTable} (or a first write) and not dropped since. Schema sync
1215
+ * reports a table that does not exist as `create`. An external view always
1216
+ * exists: nothing in memory creates one, its rows come from a provider.
1217
+ * @since 0.1.137
1218
+ */
1219
+ async tableExists() {
1220
+ if (isAtscriptDbView(this._table) && this._table.isExternal) return true;
1221
+ return this._peekState() !== void 0;
1152
1222
  }
1153
1223
  /**
1154
- * No-op: the store is the instance-level {@link rows} Map, which already
1155
- * exists. Safe to call repeatedly.
1224
+ * Kind of the object stored under `name` in the space's in-memory database,
1225
+ * or `undefined` when nothing is. Lets schema sync refuse a table declared
1226
+ * where a view exists (and the reverse) and an FK to a table that is missing.
1227
+ * @since 0.1.138
1156
1228
  */
1157
- async ensureTable() {}
1229
+ async getObjectKind(name) {
1230
+ return this._db.get(name)?.kind;
1231
+ }
1232
+ /**
1233
+ * Drops a table by name: its rows, unique indexes and increment counters go,
1234
+ * so the table added back starts empty. A missing table is not an error;
1235
+ * a view under that name is (`dropViewByName` drops views).
1236
+ * @since 0.1.137
1237
+ */
1238
+ async dropTableByName(tableName) {
1239
+ this._dropByName(tableName, "table");
1240
+ }
1241
+ /**
1242
+ * Drops a view by name — its (empty) state; a view holds no rows here.
1243
+ * A missing view is not an error; a table under that name is.
1244
+ * @since 0.1.137
1245
+ */
1246
+ async dropViewByName(viewName) {
1247
+ this._dropByName(viewName, "view");
1248
+ }
1249
+ _dropByName(name, kind) {
1250
+ const state = this._db.get(name);
1251
+ if (state && state.kind !== kind) throw new Error(`Cannot drop ${kind} "${name}": it is a ${state.kind}`);
1252
+ this._db.delete(name);
1253
+ }
1158
1254
  };
1159
1255
  /**
1160
1256
  * Ergonomic late-binding entry point for provider (read-through) mode. Resolves
@@ -1176,7 +1272,8 @@ function setMemoryProvider(space, type, fn) {
1176
1272
  /**
1177
1273
  * Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
1178
1274
  *
1179
- * Tables default to STORED mode (an instance-level `Map`). To make one table
1275
+ * Tables default to STORED mode (a `Map` per table in the space's in-memory
1276
+ * database, shared by every adapter of the space). To make one table
1180
1277
  * read-only and read-through from a runtime closure, call `setMemoryProvider`
1181
1278
  * (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
1182
1279
  * adapter has been built.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-memory",
3
- "version": "0.1.136",
3
+ "version": "0.1.138",
4
4
  "description": "In-memory adapter for atscript-db.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -45,7 +45,7 @@
45
45
  "@atscript/core": "^0.1.92",
46
46
  "@atscript/typescript": "^0.1.92",
47
47
  "@uniqu/core": "^0.1.11",
48
- "@atscript/db": "^0.1.136"
48
+ "@atscript/db": "^0.1.138"
49
49
  },
50
50
  "scripts": {
51
51
  "postinstall": "asc -f dts",