@atscript/db-memory 0.1.135 → 0.1.137
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 +173 -67
- package/dist/index.d.cts +55 -25
- package/dist/index.d.mts +55 -25
- package/dist/index.mjs +174 -68
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -348,7 +348,8 @@ function projectRow(row, projection, opts) {
|
|
|
348
348
|
* - null and missing form ONE group; a calendar-bucket key is the
|
|
349
349
|
* `@uniqu/core` kernel label (the SQLite UDF runs the same kernel);
|
|
350
350
|
* - `sum` / `avg` over no numeric value are `null`; `min` / `max` order with
|
|
351
|
-
* the `$sort` comparator;
|
|
351
|
+
* the `$sort` comparator; `countDistinct` counts distinct non-null values
|
|
352
|
+
* by group identity (below); any other `$fn` is `INVALID_QUERY`;
|
|
352
353
|
* - group identity is type-tagged: `1` and `"1"` never share a group (a
|
|
353
354
|
* number and a bigint of one value do), `Date`s group by instant, JSON
|
|
354
355
|
* values by a key-order-independent serialization;
|
|
@@ -504,6 +505,19 @@ function accumulatorFactory(expr) {
|
|
|
504
505
|
};
|
|
505
506
|
};
|
|
506
507
|
}
|
|
508
|
+
case "countDistinct": return () => {
|
|
509
|
+
const plain = /* @__PURE__ */ new Set();
|
|
510
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
511
|
+
return {
|
|
512
|
+
add: (row) => {
|
|
513
|
+
const v = read(row);
|
|
514
|
+
if (v == null) return;
|
|
515
|
+
if (typeof v === "string" || typeof v === "boolean") plain.add(v);
|
|
516
|
+
else tokens.add(identityToken(v));
|
|
517
|
+
},
|
|
518
|
+
result: () => plain.size + tokens.size
|
|
519
|
+
};
|
|
520
|
+
};
|
|
507
521
|
case "min":
|
|
508
522
|
case "max": {
|
|
509
523
|
const sign = expr.$fn === "min" ? -1 : 1;
|
|
@@ -525,12 +539,19 @@ function accumulatorFactory(expr) {
|
|
|
525
539
|
//#endregion
|
|
526
540
|
//#region src/lib/memory-adapter.ts
|
|
527
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
|
+
/**
|
|
528
548
|
* In-memory {@link BaseDbAdapter} implementation.
|
|
529
549
|
*
|
|
530
550
|
* Runs in one of two modes:
|
|
531
|
-
* - STORED mode (default):
|
|
532
|
-
*
|
|
533
|
-
*
|
|
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
|
|
534
555
|
* shape (no flattening), which is why {@link supportsNestedObjects} is `true`.
|
|
535
556
|
* Full CRUD surface: inserts, reads, update / replace / delete with
|
|
536
557
|
* optimistic-concurrency CAS.
|
|
@@ -542,12 +563,10 @@ function accumulatorFactory(expr) {
|
|
|
542
563
|
*/
|
|
543
564
|
var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
544
565
|
/**
|
|
545
|
-
* The
|
|
546
|
-
*
|
|
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.
|
|
547
568
|
*/
|
|
548
|
-
|
|
549
|
-
/** Unique indexes recorded by {@link syncIndexes}. Enforced on insert. */
|
|
550
|
-
uniqueIndexes = [];
|
|
569
|
+
_db = /* @__PURE__ */ new Map();
|
|
551
570
|
/** Memoized physical PK field names — stable for the adapter's lifetime. */
|
|
552
571
|
_pkFieldsCache;
|
|
553
572
|
/**
|
|
@@ -557,14 +576,6 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
557
576
|
*/
|
|
558
577
|
_incrementFieldsCache;
|
|
559
578
|
/**
|
|
560
|
-
* Running per-field auto-increment counters (PHYSICAL name → last value
|
|
561
|
-
* handed out). Lives on the adapter INSTANCE, so it resets whenever a new
|
|
562
|
-
* DbSpace/adapter is built — correct for an in-memory store (parity with
|
|
563
|
-
* SQLite `:memory:`, whose sequence also restarts with a fresh DB). Never
|
|
564
|
-
* persisted.
|
|
565
|
-
*/
|
|
566
|
-
_incrementCounters = /* @__PURE__ */ new Map();
|
|
567
|
-
/**
|
|
568
579
|
* Provider (read-through) backing closure. When set, this adapter is
|
|
569
580
|
* PROVIDER-BACKED: reads recompute rows from this closure per request and all
|
|
570
581
|
* writes are rejected (see {@link _assertWritable}). `undefined` ⇒ stored mode.
|
|
@@ -626,6 +637,43 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
626
637
|
}
|
|
627
638
|
return String(id);
|
|
628
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
|
+
}
|
|
629
677
|
/**
|
|
630
678
|
* Physical names of the primary-key field(s). Single `@meta.id` resolves via
|
|
631
679
|
* {@link AtscriptDbReadable.metaIdPhysical}; a composite key maps each logical
|
|
@@ -674,7 +722,9 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
674
722
|
* are computed so an increment PRIMARY KEY produces a real `insertedId` and
|
|
675
723
|
* stores under a real key. The memory analogue of the Mongo adapter's
|
|
676
724
|
* insert-time increment (Mongo uses an atomic `__atscript_counters`
|
|
677
|
-
* collection; an in-memory store
|
|
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):
|
|
678
728
|
*
|
|
679
729
|
* - No value for the field → assign the next counter value. First use starts
|
|
680
730
|
* at `start ?? 1` (`max(counter, (start ?? 1) - 1) + 1`); thereafter it is
|
|
@@ -684,18 +734,18 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
684
734
|
* - Explicit value present → keep it, but advance the counter to
|
|
685
735
|
* `max(counter, value)` so a later auto value can never collide with it.
|
|
686
736
|
*/
|
|
687
|
-
_applyIncrements(row) {
|
|
737
|
+
_applyIncrements(counters, row) {
|
|
688
738
|
const fields = this._incrementFields();
|
|
689
739
|
if (fields.size === 0) return;
|
|
690
740
|
for (const [physical, start] of fields) {
|
|
691
741
|
const floor = (start ?? 1) - 1;
|
|
692
|
-
const base =
|
|
742
|
+
const base = counters.get(physical) ?? floor;
|
|
693
743
|
const current = row[physical];
|
|
694
744
|
if (current === void 0 || current === null) {
|
|
695
745
|
const next = base + 1;
|
|
696
746
|
row[physical] = next;
|
|
697
|
-
|
|
698
|
-
} else if (typeof current === "number")
|
|
747
|
+
counters.set(physical, next);
|
|
748
|
+
} else if (typeof current === "number") counters.set(physical, Math.max(base, current));
|
|
699
749
|
}
|
|
700
750
|
}
|
|
701
751
|
/**
|
|
@@ -771,7 +821,8 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
771
821
|
/**
|
|
772
822
|
* Snapshot seam for reads. Returns the current rows.
|
|
773
823
|
*
|
|
774
|
-
* - Stored mode reads the
|
|
824
|
+
* - Stored mode reads the table's Map directly (insertion order preserved);
|
|
825
|
+
* a table that does not exist reads as empty.
|
|
775
826
|
* - Provider (read-through) mode calls {@link _provider} to recompute a fresh
|
|
776
827
|
* snapshot per read.
|
|
777
828
|
*
|
|
@@ -788,7 +839,7 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
788
839
|
*/
|
|
789
840
|
_loadRows() {
|
|
790
841
|
if (this._provider) return this._provider();
|
|
791
|
-
return [...this.rows.values()];
|
|
842
|
+
return [...this._peekState()?.rows.values() ?? []];
|
|
792
843
|
}
|
|
793
844
|
/**
|
|
794
845
|
* Clones the payload in, applies the version default, generates any
|
|
@@ -798,15 +849,15 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
798
849
|
* the store. Called once per item by both `insertOne` and `insertMany`, so
|
|
799
850
|
* increment values advance sequentially across a batch.
|
|
800
851
|
*/
|
|
801
|
-
_insertRow(data) {
|
|
852
|
+
_insertRow(state, data) {
|
|
802
853
|
const row = structuredClone(data);
|
|
803
854
|
const versionColumn = this._table.versionColumn;
|
|
804
855
|
if (versionColumn !== void 0 && !(versionColumn in row)) row[versionColumn] = 0;
|
|
805
|
-
this._applyIncrements(row);
|
|
856
|
+
this._applyIncrements(state.incrementCounters, row);
|
|
806
857
|
const key = this.pkKey(row);
|
|
807
|
-
if (
|
|
808
|
-
this._enforceUniqueIndexes(row);
|
|
809
|
-
|
|
858
|
+
if (state.rows.has(key)) throw this._pkConflict();
|
|
859
|
+
this._enforceUniqueIndexes(state, row);
|
|
860
|
+
state.rows.set(key, row);
|
|
810
861
|
const fallback = this._table.metaIdPhysical ? void 0 : this._compositeInsertedId(row);
|
|
811
862
|
return this._resolveInsertedId(row, fallback);
|
|
812
863
|
}
|
|
@@ -819,8 +870,8 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
819
870
|
* `excludeKey` (when given) skips the row stored under that {@link pkKey} — so
|
|
820
871
|
* a row updating its own unique value does not false-conflict with itself.
|
|
821
872
|
*/
|
|
822
|
-
_enforceUniqueIndexes(row, excludeKey) {
|
|
823
|
-
for (const index of
|
|
873
|
+
_enforceUniqueIndexes(state, row, excludeKey) {
|
|
874
|
+
for (const index of state.uniqueIndexes) {
|
|
824
875
|
const tuple = [];
|
|
825
876
|
let skip = false;
|
|
826
877
|
for (const field of index.fields) {
|
|
@@ -832,7 +883,7 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
832
883
|
tuple.push(value);
|
|
833
884
|
}
|
|
834
885
|
if (skip) continue;
|
|
835
|
-
for (const [existingKey, existing] of
|
|
886
|
+
for (const [existingKey, existing] of state.rows) {
|
|
836
887
|
if (excludeKey !== void 0 && existingKey === excludeKey) continue;
|
|
837
888
|
let equal = true;
|
|
838
889
|
for (let i = 0; i < index.fields.length; i++) if (!valuesEqual(getPath(existing, index.fields[i]), tuple[i])) {
|
|
@@ -848,11 +899,12 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
848
899
|
}
|
|
849
900
|
async insertOne(data) {
|
|
850
901
|
this._assertWritable();
|
|
851
|
-
return { insertedId: this._insertRow(data) };
|
|
902
|
+
return { insertedId: this._insertRow(this._state(), data) };
|
|
852
903
|
}
|
|
853
904
|
async insertMany(data) {
|
|
854
905
|
this._assertWritable();
|
|
855
|
-
const
|
|
906
|
+
const state = this._state();
|
|
907
|
+
const insertedIds = data.map((item) => this._insertRow(state, item));
|
|
856
908
|
return {
|
|
857
909
|
insertedCount: data.length,
|
|
858
910
|
insertedIds
|
|
@@ -860,7 +912,7 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
860
912
|
}
|
|
861
913
|
/**
|
|
862
914
|
* Selects the stored rows a write should touch, as `{ key, row }` pairs so
|
|
863
|
-
* callers can mutate in place and re-key. Stored mode scans the
|
|
915
|
+
* callers can mutate in place and re-key. Stored mode scans the table's Map
|
|
864
916
|
* directly (writes are authoritative against the store, unlike reads which go
|
|
865
917
|
* through the {@link _loadRows} snapshot seam).
|
|
866
918
|
*
|
|
@@ -871,14 +923,15 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
871
923
|
* that has no version column is a misconfiguration and throws (mirrors the
|
|
872
924
|
* Mongo adapter's `_buildCasFilter`).
|
|
873
925
|
*
|
|
874
|
-
* 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.
|
|
875
928
|
*/
|
|
876
|
-
_selectForWrite(filter, expectedVersion, many) {
|
|
929
|
+
_selectForWrite(state, filter, expectedVersion, many) {
|
|
877
930
|
const versionColumn = this._table.versionColumn;
|
|
878
931
|
if (expectedVersion !== void 0 && versionColumn === void 0) throw new Error("expectedVersion requires a versioned table");
|
|
879
932
|
const match = buildMemoryPredicate(filter);
|
|
880
933
|
const matched = [];
|
|
881
|
-
for (const [key, row] of
|
|
934
|
+
for (const [key, row] of state?.rows ?? []) {
|
|
882
935
|
if (!match(row)) continue;
|
|
883
936
|
if (expectedVersion !== void 0 && row[versionColumn] !== expectedVersion) continue;
|
|
884
937
|
matched.push({
|
|
@@ -933,11 +986,11 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
933
986
|
* NEW key (some other row already owns it) throws `CONFLICT`. Throws BEFORE
|
|
934
987
|
* touching the Map so a failed re-key leaves the store unchanged.
|
|
935
988
|
*/
|
|
936
|
-
_commitRow(oldKey, next) {
|
|
989
|
+
_commitRow(rows, oldKey, next) {
|
|
937
990
|
const newKey = this.pkKey(next);
|
|
938
|
-
if (newKey !== oldKey &&
|
|
939
|
-
if (newKey !== oldKey)
|
|
940
|
-
|
|
991
|
+
if (newKey !== oldKey && rows.has(newKey)) throw this._pkConflict();
|
|
992
|
+
if (newKey !== oldKey) rows.delete(oldKey);
|
|
993
|
+
rows.set(newKey, next);
|
|
941
994
|
}
|
|
942
995
|
/**
|
|
943
996
|
* Update-then-commit for a single matched row: clones the pristine `row`,
|
|
@@ -947,25 +1000,26 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
947
1000
|
* commits. Nothing is written to the store until both the unique and PK checks
|
|
948
1001
|
* pass, so a conflict leaves the store untouched.
|
|
949
1002
|
*/
|
|
950
|
-
_commitUpdate(oldKey, row, data, ops) {
|
|
1003
|
+
_commitUpdate(state, oldKey, row, data, ops) {
|
|
951
1004
|
const next = structuredClone(row);
|
|
952
1005
|
this._applyUpdate(next, data, ops);
|
|
953
1006
|
this._bumpVersion(next, row);
|
|
954
|
-
this._enforceUniqueIndexes(next, oldKey);
|
|
955
|
-
this._commitRow(oldKey, next);
|
|
1007
|
+
this._enforceUniqueIndexes(state, next, oldKey);
|
|
1008
|
+
this._commitRow(state.rows, oldKey, next);
|
|
956
1009
|
}
|
|
957
1010
|
async replaceOne(filter, data, expectedVersion) {
|
|
958
1011
|
this._assertWritable();
|
|
959
|
-
const
|
|
960
|
-
|
|
1012
|
+
const state = this._peekState();
|
|
1013
|
+
const matched = this._selectForWrite(state, filter, expectedVersion, false);
|
|
1014
|
+
if (!state || matched.length === 0) return {
|
|
961
1015
|
matchedCount: 0,
|
|
962
1016
|
modifiedCount: 0
|
|
963
1017
|
};
|
|
964
1018
|
const { key, row } = matched[0];
|
|
965
1019
|
const next = structuredClone(data);
|
|
966
1020
|
this._bumpVersion(next, row);
|
|
967
|
-
this._enforceUniqueIndexes(next, key);
|
|
968
|
-
this._commitRow(key, next);
|
|
1021
|
+
this._enforceUniqueIndexes(state, next, key);
|
|
1022
|
+
this._commitRow(state.rows, key, next);
|
|
969
1023
|
return {
|
|
970
1024
|
matchedCount: 1,
|
|
971
1025
|
modifiedCount: 1
|
|
@@ -973,13 +1027,14 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
973
1027
|
}
|
|
974
1028
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
975
1029
|
this._assertWritable();
|
|
976
|
-
const
|
|
977
|
-
|
|
1030
|
+
const state = this._peekState();
|
|
1031
|
+
const matched = this._selectForWrite(state, filter, expectedVersion, false);
|
|
1032
|
+
if (!state || matched.length === 0) return {
|
|
978
1033
|
matchedCount: 0,
|
|
979
1034
|
modifiedCount: 0
|
|
980
1035
|
};
|
|
981
1036
|
const { key, row } = matched[0];
|
|
982
|
-
this._commitUpdate(key, row, data, ops);
|
|
1037
|
+
this._commitUpdate(state, key, row, data, ops);
|
|
983
1038
|
return {
|
|
984
1039
|
matchedCount: 1,
|
|
985
1040
|
modifiedCount: 1
|
|
@@ -987,9 +1042,10 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
987
1042
|
}
|
|
988
1043
|
async deleteOne(filter) {
|
|
989
1044
|
this._assertWritable();
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
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);
|
|
993
1049
|
return { deletedCount: 1 };
|
|
994
1050
|
}
|
|
995
1051
|
/**
|
|
@@ -1093,10 +1149,19 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
1093
1149
|
calendarBucketUnits() {
|
|
1094
1150
|
return _atscript_db.ALL_BUCKET_UNITS;
|
|
1095
1151
|
}
|
|
1152
|
+
/** Every aggregate function, `countDistinct` included. */
|
|
1153
|
+
aggregateFns() {
|
|
1154
|
+
return _atscript_db.ALL_AGGREGATE_FNS;
|
|
1155
|
+
}
|
|
1096
1156
|
async updateMany(filter, data, ops) {
|
|
1097
1157
|
this._assertWritable();
|
|
1098
|
-
const
|
|
1099
|
-
|
|
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);
|
|
1100
1165
|
return {
|
|
1101
1166
|
matchedCount: matched.length,
|
|
1102
1167
|
modifiedCount: matched.length
|
|
@@ -1104,8 +1169,13 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
1104
1169
|
}
|
|
1105
1170
|
async replaceMany(filter, data) {
|
|
1106
1171
|
this._assertWritable();
|
|
1107
|
-
const
|
|
1108
|
-
|
|
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);
|
|
1109
1179
|
return {
|
|
1110
1180
|
matchedCount: matched.length,
|
|
1111
1181
|
modifiedCount: matched.length
|
|
@@ -1113,8 +1183,10 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
1113
1183
|
}
|
|
1114
1184
|
async deleteMany(filter) {
|
|
1115
1185
|
this._assertWritable();
|
|
1116
|
-
const
|
|
1117
|
-
|
|
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);
|
|
1118
1190
|
return { deletedCount: matched.length };
|
|
1119
1191
|
}
|
|
1120
1192
|
/**
|
|
@@ -1123,21 +1195,54 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
1123
1195
|
* in-memory scan needs no plain/fulltext/geo index to answer queries.
|
|
1124
1196
|
*/
|
|
1125
1197
|
async syncIndexes() {
|
|
1126
|
-
|
|
1198
|
+
const uniqueIndexes = [];
|
|
1127
1199
|
for (const index of this._table.indexes.values()) {
|
|
1128
1200
|
if (index.type !== "unique") continue;
|
|
1129
|
-
|
|
1201
|
+
uniqueIndexes.push({
|
|
1130
1202
|
name: index.name,
|
|
1131
1203
|
fields: index.fields.map((f) => f.name),
|
|
1132
1204
|
optionalFields: new Set(index.fields.filter((f) => f.optional).map((f) => f.name))
|
|
1133
1205
|
});
|
|
1134
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();
|
|
1135
1212
|
}
|
|
1136
1213
|
/**
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
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
|
|
1139
1219
|
*/
|
|
1140
|
-
async
|
|
1220
|
+
async tableExists() {
|
|
1221
|
+
if ((0, _atscript_db.isAtscriptDbView)(this._table) && this._table.isExternal) return true;
|
|
1222
|
+
return this._peekState() !== void 0;
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Drops a table by name: its rows, unique indexes and increment counters go,
|
|
1226
|
+
* so the table added back starts empty. A missing table is not an error;
|
|
1227
|
+
* a view under that name is (`dropViewByName` drops views).
|
|
1228
|
+
* @since 0.1.137
|
|
1229
|
+
*/
|
|
1230
|
+
async dropTableByName(tableName) {
|
|
1231
|
+
this._dropByName(tableName, "table");
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Drops a view by name — its (empty) state; a view holds no rows here.
|
|
1235
|
+
* A missing view is not an error; a table under that name is.
|
|
1236
|
+
* @since 0.1.137
|
|
1237
|
+
*/
|
|
1238
|
+
async dropViewByName(viewName) {
|
|
1239
|
+
this._dropByName(viewName, "view");
|
|
1240
|
+
}
|
|
1241
|
+
_dropByName(name, kind) {
|
|
1242
|
+
const state = this._db.get(name);
|
|
1243
|
+
if (state && state.kind !== kind) throw new Error(`Cannot drop ${kind} "${name}": it is a ${state.kind}`);
|
|
1244
|
+
this._db.delete(name);
|
|
1245
|
+
}
|
|
1141
1246
|
};
|
|
1142
1247
|
/**
|
|
1143
1248
|
* Ergonomic late-binding entry point for provider (read-through) mode. Resolves
|
|
@@ -1159,7 +1264,8 @@ function setMemoryProvider(space, type, fn) {
|
|
|
1159
1264
|
/**
|
|
1160
1265
|
* Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
|
|
1161
1266
|
*
|
|
1162
|
-
* Tables default to STORED mode (
|
|
1267
|
+
* Tables default to STORED mode (a `Map` per table in the space's in-memory
|
|
1268
|
+
* database, shared by every adapter of the space). To make one table
|
|
1163
1269
|
* read-only and read-through from a runtime closure, call `setMemoryProvider`
|
|
1164
1270
|
* (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
|
|
1165
1271
|
* adapter has been built.
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { 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, 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):
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
|
30
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
/**
|
|
@@ -367,6 +375,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
367
375
|
* there is no zone this adapter cannot resolve).
|
|
368
376
|
*/
|
|
369
377
|
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
378
|
+
/** Every aggregate function, `countDistinct` included. */
|
|
379
|
+
aggregateFns(): ReadonlySet<AggregateFn>;
|
|
370
380
|
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
371
381
|
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
372
382
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
@@ -376,11 +386,30 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
376
386
|
* in-memory scan needs no plain/fulltext/geo index to answer queries.
|
|
377
387
|
*/
|
|
378
388
|
syncIndexes(): Promise<void>;
|
|
389
|
+
/** Creates the table's (empty) state when it does not exist. Idempotent. */
|
|
390
|
+
ensureTable(): Promise<void>;
|
|
379
391
|
/**
|
|
380
|
-
*
|
|
381
|
-
*
|
|
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
|
|
382
397
|
*/
|
|
383
|
-
|
|
398
|
+
tableExists(): Promise<boolean>;
|
|
399
|
+
/**
|
|
400
|
+
* Drops a table by name: its rows, unique indexes and increment counters go,
|
|
401
|
+
* so the table added back starts empty. A missing table is not an error;
|
|
402
|
+
* a view under that name is (`dropViewByName` drops views).
|
|
403
|
+
* @since 0.1.137
|
|
404
|
+
*/
|
|
405
|
+
dropTableByName(tableName: string): Promise<void>;
|
|
406
|
+
/**
|
|
407
|
+
* Drops a view by name — its (empty) state; a view holds no rows here.
|
|
408
|
+
* A missing view is not an error; a table under that name is.
|
|
409
|
+
* @since 0.1.137
|
|
410
|
+
*/
|
|
411
|
+
dropViewByName(viewName: string): Promise<void>;
|
|
412
|
+
private _dropByName;
|
|
384
413
|
}
|
|
385
414
|
/**
|
|
386
415
|
* Ergonomic late-binding entry point for provider (read-through) mode. Resolves
|
|
@@ -480,7 +509,8 @@ declare function projectRow(row: Record<string, unknown>, projection?: Record<st
|
|
|
480
509
|
/**
|
|
481
510
|
* Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
|
|
482
511
|
*
|
|
483
|
-
* Tables default to STORED mode (
|
|
512
|
+
* Tables default to STORED mode (a `Map` per table in the space's in-memory
|
|
513
|
+
* database, shared by every adapter of the space). To make one table
|
|
484
514
|
* read-only and read-through from a runtime closure, call `setMemoryProvider`
|
|
485
515
|
* (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
|
|
486
516
|
* adapter has been built.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { 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, 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):
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
|
30
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
/**
|
|
@@ -367,6 +375,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
367
375
|
* there is no zone this adapter cannot resolve).
|
|
368
376
|
*/
|
|
369
377
|
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
378
|
+
/** Every aggregate function, `countDistinct` included. */
|
|
379
|
+
aggregateFns(): ReadonlySet<AggregateFn>;
|
|
370
380
|
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
371
381
|
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
372
382
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
@@ -376,11 +386,30 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
376
386
|
* in-memory scan needs no plain/fulltext/geo index to answer queries.
|
|
377
387
|
*/
|
|
378
388
|
syncIndexes(): Promise<void>;
|
|
389
|
+
/** Creates the table's (empty) state when it does not exist. Idempotent. */
|
|
390
|
+
ensureTable(): Promise<void>;
|
|
379
391
|
/**
|
|
380
|
-
*
|
|
381
|
-
*
|
|
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
|
|
382
397
|
*/
|
|
383
|
-
|
|
398
|
+
tableExists(): Promise<boolean>;
|
|
399
|
+
/**
|
|
400
|
+
* Drops a table by name: its rows, unique indexes and increment counters go,
|
|
401
|
+
* so the table added back starts empty. A missing table is not an error;
|
|
402
|
+
* a view under that name is (`dropViewByName` drops views).
|
|
403
|
+
* @since 0.1.137
|
|
404
|
+
*/
|
|
405
|
+
dropTableByName(tableName: string): Promise<void>;
|
|
406
|
+
/**
|
|
407
|
+
* Drops a view by name — its (empty) state; a view holds no rows here.
|
|
408
|
+
* A missing view is not an error; a table under that name is.
|
|
409
|
+
* @since 0.1.137
|
|
410
|
+
*/
|
|
411
|
+
dropViewByName(viewName: string): Promise<void>;
|
|
412
|
+
private _dropByName;
|
|
384
413
|
}
|
|
385
414
|
/**
|
|
386
415
|
* Ergonomic late-binding entry point for provider (read-through) mode. Resolves
|
|
@@ -480,7 +509,8 @@ declare function projectRow(row: Record<string, unknown>, projection?: Record<st
|
|
|
480
509
|
/**
|
|
481
510
|
* Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
|
|
482
511
|
*
|
|
483
|
-
* Tables default to STORED mode (
|
|
512
|
+
* Tables default to STORED mode (a `Map` per table in the space's in-memory
|
|
513
|
+
* database, shared by every adapter of the space). To make one table
|
|
484
514
|
* read-only and read-through from a runtime closure, call `setMemoryProvider`
|
|
485
515
|
* (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
|
|
486
516
|
* adapter has been built.
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { 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
|
|
@@ -347,7 +347,8 @@ function projectRow(row, projection, opts) {
|
|
|
347
347
|
* - null and missing form ONE group; a calendar-bucket key is the
|
|
348
348
|
* `@uniqu/core` kernel label (the SQLite UDF runs the same kernel);
|
|
349
349
|
* - `sum` / `avg` over no numeric value are `null`; `min` / `max` order with
|
|
350
|
-
* the `$sort` comparator;
|
|
350
|
+
* the `$sort` comparator; `countDistinct` counts distinct non-null values
|
|
351
|
+
* by group identity (below); any other `$fn` is `INVALID_QUERY`;
|
|
351
352
|
* - group identity is type-tagged: `1` and `"1"` never share a group (a
|
|
352
353
|
* number and a bigint of one value do), `Date`s group by instant, JSON
|
|
353
354
|
* values by a key-order-independent serialization;
|
|
@@ -503,6 +504,19 @@ function accumulatorFactory(expr) {
|
|
|
503
504
|
};
|
|
504
505
|
};
|
|
505
506
|
}
|
|
507
|
+
case "countDistinct": return () => {
|
|
508
|
+
const plain = /* @__PURE__ */ new Set();
|
|
509
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
510
|
+
return {
|
|
511
|
+
add: (row) => {
|
|
512
|
+
const v = read(row);
|
|
513
|
+
if (v == null) return;
|
|
514
|
+
if (typeof v === "string" || typeof v === "boolean") plain.add(v);
|
|
515
|
+
else tokens.add(identityToken(v));
|
|
516
|
+
},
|
|
517
|
+
result: () => plain.size + tokens.size
|
|
518
|
+
};
|
|
519
|
+
};
|
|
506
520
|
case "min":
|
|
507
521
|
case "max": {
|
|
508
522
|
const sign = expr.$fn === "min" ? -1 : 1;
|
|
@@ -524,12 +538,19 @@ function accumulatorFactory(expr) {
|
|
|
524
538
|
//#endregion
|
|
525
539
|
//#region src/lib/memory-adapter.ts
|
|
526
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
|
+
/**
|
|
527
547
|
* In-memory {@link BaseDbAdapter} implementation.
|
|
528
548
|
*
|
|
529
549
|
* Runs in one of two modes:
|
|
530
|
-
* - STORED mode (default):
|
|
531
|
-
*
|
|
532
|
-
*
|
|
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
|
|
533
554
|
* shape (no flattening), which is why {@link supportsNestedObjects} is `true`.
|
|
534
555
|
* Full CRUD surface: inserts, reads, update / replace / delete with
|
|
535
556
|
* optimistic-concurrency CAS.
|
|
@@ -541,12 +562,10 @@ function accumulatorFactory(expr) {
|
|
|
541
562
|
*/
|
|
542
563
|
var MemoryAdapter = class extends BaseDbAdapter {
|
|
543
564
|
/**
|
|
544
|
-
* The
|
|
545
|
-
*
|
|
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.
|
|
546
567
|
*/
|
|
547
|
-
|
|
548
|
-
/** Unique indexes recorded by {@link syncIndexes}. Enforced on insert. */
|
|
549
|
-
uniqueIndexes = [];
|
|
568
|
+
_db = /* @__PURE__ */ new Map();
|
|
550
569
|
/** Memoized physical PK field names — stable for the adapter's lifetime. */
|
|
551
570
|
_pkFieldsCache;
|
|
552
571
|
/**
|
|
@@ -556,14 +575,6 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
556
575
|
*/
|
|
557
576
|
_incrementFieldsCache;
|
|
558
577
|
/**
|
|
559
|
-
* Running per-field auto-increment counters (PHYSICAL name → last value
|
|
560
|
-
* handed out). Lives on the adapter INSTANCE, so it resets whenever a new
|
|
561
|
-
* DbSpace/adapter is built — correct for an in-memory store (parity with
|
|
562
|
-
* SQLite `:memory:`, whose sequence also restarts with a fresh DB). Never
|
|
563
|
-
* persisted.
|
|
564
|
-
*/
|
|
565
|
-
_incrementCounters = /* @__PURE__ */ new Map();
|
|
566
|
-
/**
|
|
567
578
|
* Provider (read-through) backing closure. When set, this adapter is
|
|
568
579
|
* PROVIDER-BACKED: reads recompute rows from this closure per request and all
|
|
569
580
|
* writes are rejected (see {@link _assertWritable}). `undefined` ⇒ stored mode.
|
|
@@ -625,6 +636,43 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
625
636
|
}
|
|
626
637
|
return String(id);
|
|
627
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
|
+
}
|
|
628
676
|
/**
|
|
629
677
|
* Physical names of the primary-key field(s). Single `@meta.id` resolves via
|
|
630
678
|
* {@link AtscriptDbReadable.metaIdPhysical}; a composite key maps each logical
|
|
@@ -673,7 +721,9 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
673
721
|
* are computed so an increment PRIMARY KEY produces a real `insertedId` and
|
|
674
722
|
* stores under a real key. The memory analogue of the Mongo adapter's
|
|
675
723
|
* insert-time increment (Mongo uses an atomic `__atscript_counters`
|
|
676
|
-
* collection; an in-memory store
|
|
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):
|
|
677
727
|
*
|
|
678
728
|
* - No value for the field → assign the next counter value. First use starts
|
|
679
729
|
* at `start ?? 1` (`max(counter, (start ?? 1) - 1) + 1`); thereafter it is
|
|
@@ -683,18 +733,18 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
683
733
|
* - Explicit value present → keep it, but advance the counter to
|
|
684
734
|
* `max(counter, value)` so a later auto value can never collide with it.
|
|
685
735
|
*/
|
|
686
|
-
_applyIncrements(row) {
|
|
736
|
+
_applyIncrements(counters, row) {
|
|
687
737
|
const fields = this._incrementFields();
|
|
688
738
|
if (fields.size === 0) return;
|
|
689
739
|
for (const [physical, start] of fields) {
|
|
690
740
|
const floor = (start ?? 1) - 1;
|
|
691
|
-
const base =
|
|
741
|
+
const base = counters.get(physical) ?? floor;
|
|
692
742
|
const current = row[physical];
|
|
693
743
|
if (current === void 0 || current === null) {
|
|
694
744
|
const next = base + 1;
|
|
695
745
|
row[physical] = next;
|
|
696
|
-
|
|
697
|
-
} else if (typeof current === "number")
|
|
746
|
+
counters.set(physical, next);
|
|
747
|
+
} else if (typeof current === "number") counters.set(physical, Math.max(base, current));
|
|
698
748
|
}
|
|
699
749
|
}
|
|
700
750
|
/**
|
|
@@ -770,7 +820,8 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
770
820
|
/**
|
|
771
821
|
* Snapshot seam for reads. Returns the current rows.
|
|
772
822
|
*
|
|
773
|
-
* - Stored mode reads the
|
|
823
|
+
* - Stored mode reads the table's Map directly (insertion order preserved);
|
|
824
|
+
* a table that does not exist reads as empty.
|
|
774
825
|
* - Provider (read-through) mode calls {@link _provider} to recompute a fresh
|
|
775
826
|
* snapshot per read.
|
|
776
827
|
*
|
|
@@ -787,7 +838,7 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
787
838
|
*/
|
|
788
839
|
_loadRows() {
|
|
789
840
|
if (this._provider) return this._provider();
|
|
790
|
-
return [...this.rows.values()];
|
|
841
|
+
return [...this._peekState()?.rows.values() ?? []];
|
|
791
842
|
}
|
|
792
843
|
/**
|
|
793
844
|
* Clones the payload in, applies the version default, generates any
|
|
@@ -797,15 +848,15 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
797
848
|
* the store. Called once per item by both `insertOne` and `insertMany`, so
|
|
798
849
|
* increment values advance sequentially across a batch.
|
|
799
850
|
*/
|
|
800
|
-
_insertRow(data) {
|
|
851
|
+
_insertRow(state, data) {
|
|
801
852
|
const row = structuredClone(data);
|
|
802
853
|
const versionColumn = this._table.versionColumn;
|
|
803
854
|
if (versionColumn !== void 0 && !(versionColumn in row)) row[versionColumn] = 0;
|
|
804
|
-
this._applyIncrements(row);
|
|
855
|
+
this._applyIncrements(state.incrementCounters, row);
|
|
805
856
|
const key = this.pkKey(row);
|
|
806
|
-
if (
|
|
807
|
-
this._enforceUniqueIndexes(row);
|
|
808
|
-
|
|
857
|
+
if (state.rows.has(key)) throw this._pkConflict();
|
|
858
|
+
this._enforceUniqueIndexes(state, row);
|
|
859
|
+
state.rows.set(key, row);
|
|
809
860
|
const fallback = this._table.metaIdPhysical ? void 0 : this._compositeInsertedId(row);
|
|
810
861
|
return this._resolveInsertedId(row, fallback);
|
|
811
862
|
}
|
|
@@ -818,8 +869,8 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
818
869
|
* `excludeKey` (when given) skips the row stored under that {@link pkKey} — so
|
|
819
870
|
* a row updating its own unique value does not false-conflict with itself.
|
|
820
871
|
*/
|
|
821
|
-
_enforceUniqueIndexes(row, excludeKey) {
|
|
822
|
-
for (const index of
|
|
872
|
+
_enforceUniqueIndexes(state, row, excludeKey) {
|
|
873
|
+
for (const index of state.uniqueIndexes) {
|
|
823
874
|
const tuple = [];
|
|
824
875
|
let skip = false;
|
|
825
876
|
for (const field of index.fields) {
|
|
@@ -831,7 +882,7 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
831
882
|
tuple.push(value);
|
|
832
883
|
}
|
|
833
884
|
if (skip) continue;
|
|
834
|
-
for (const [existingKey, existing] of
|
|
885
|
+
for (const [existingKey, existing] of state.rows) {
|
|
835
886
|
if (excludeKey !== void 0 && existingKey === excludeKey) continue;
|
|
836
887
|
let equal = true;
|
|
837
888
|
for (let i = 0; i < index.fields.length; i++) if (!valuesEqual(getPath(existing, index.fields[i]), tuple[i])) {
|
|
@@ -847,11 +898,12 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
847
898
|
}
|
|
848
899
|
async insertOne(data) {
|
|
849
900
|
this._assertWritable();
|
|
850
|
-
return { insertedId: this._insertRow(data) };
|
|
901
|
+
return { insertedId: this._insertRow(this._state(), data) };
|
|
851
902
|
}
|
|
852
903
|
async insertMany(data) {
|
|
853
904
|
this._assertWritable();
|
|
854
|
-
const
|
|
905
|
+
const state = this._state();
|
|
906
|
+
const insertedIds = data.map((item) => this._insertRow(state, item));
|
|
855
907
|
return {
|
|
856
908
|
insertedCount: data.length,
|
|
857
909
|
insertedIds
|
|
@@ -859,7 +911,7 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
859
911
|
}
|
|
860
912
|
/**
|
|
861
913
|
* Selects the stored rows a write should touch, as `{ key, row }` pairs so
|
|
862
|
-
* callers can mutate in place and re-key. Stored mode scans the
|
|
914
|
+
* callers can mutate in place and re-key. Stored mode scans the table's Map
|
|
863
915
|
* directly (writes are authoritative against the store, unlike reads which go
|
|
864
916
|
* through the {@link _loadRows} snapshot seam).
|
|
865
917
|
*
|
|
@@ -870,14 +922,15 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
870
922
|
* that has no version column is a misconfiguration and throws (mirrors the
|
|
871
923
|
* Mongo adapter's `_buildCasFilter`).
|
|
872
924
|
*
|
|
873
|
-
* 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.
|
|
874
927
|
*/
|
|
875
|
-
_selectForWrite(filter, expectedVersion, many) {
|
|
928
|
+
_selectForWrite(state, filter, expectedVersion, many) {
|
|
876
929
|
const versionColumn = this._table.versionColumn;
|
|
877
930
|
if (expectedVersion !== void 0 && versionColumn === void 0) throw new Error("expectedVersion requires a versioned table");
|
|
878
931
|
const match = buildMemoryPredicate(filter);
|
|
879
932
|
const matched = [];
|
|
880
|
-
for (const [key, row] of
|
|
933
|
+
for (const [key, row] of state?.rows ?? []) {
|
|
881
934
|
if (!match(row)) continue;
|
|
882
935
|
if (expectedVersion !== void 0 && row[versionColumn] !== expectedVersion) continue;
|
|
883
936
|
matched.push({
|
|
@@ -932,11 +985,11 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
932
985
|
* NEW key (some other row already owns it) throws `CONFLICT`. Throws BEFORE
|
|
933
986
|
* touching the Map so a failed re-key leaves the store unchanged.
|
|
934
987
|
*/
|
|
935
|
-
_commitRow(oldKey, next) {
|
|
988
|
+
_commitRow(rows, oldKey, next) {
|
|
936
989
|
const newKey = this.pkKey(next);
|
|
937
|
-
if (newKey !== oldKey &&
|
|
938
|
-
if (newKey !== oldKey)
|
|
939
|
-
|
|
990
|
+
if (newKey !== oldKey && rows.has(newKey)) throw this._pkConflict();
|
|
991
|
+
if (newKey !== oldKey) rows.delete(oldKey);
|
|
992
|
+
rows.set(newKey, next);
|
|
940
993
|
}
|
|
941
994
|
/**
|
|
942
995
|
* Update-then-commit for a single matched row: clones the pristine `row`,
|
|
@@ -946,25 +999,26 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
946
999
|
* commits. Nothing is written to the store until both the unique and PK checks
|
|
947
1000
|
* pass, so a conflict leaves the store untouched.
|
|
948
1001
|
*/
|
|
949
|
-
_commitUpdate(oldKey, row, data, ops) {
|
|
1002
|
+
_commitUpdate(state, oldKey, row, data, ops) {
|
|
950
1003
|
const next = structuredClone(row);
|
|
951
1004
|
this._applyUpdate(next, data, ops);
|
|
952
1005
|
this._bumpVersion(next, row);
|
|
953
|
-
this._enforceUniqueIndexes(next, oldKey);
|
|
954
|
-
this._commitRow(oldKey, next);
|
|
1006
|
+
this._enforceUniqueIndexes(state, next, oldKey);
|
|
1007
|
+
this._commitRow(state.rows, oldKey, next);
|
|
955
1008
|
}
|
|
956
1009
|
async replaceOne(filter, data, expectedVersion) {
|
|
957
1010
|
this._assertWritable();
|
|
958
|
-
const
|
|
959
|
-
|
|
1011
|
+
const state = this._peekState();
|
|
1012
|
+
const matched = this._selectForWrite(state, filter, expectedVersion, false);
|
|
1013
|
+
if (!state || matched.length === 0) return {
|
|
960
1014
|
matchedCount: 0,
|
|
961
1015
|
modifiedCount: 0
|
|
962
1016
|
};
|
|
963
1017
|
const { key, row } = matched[0];
|
|
964
1018
|
const next = structuredClone(data);
|
|
965
1019
|
this._bumpVersion(next, row);
|
|
966
|
-
this._enforceUniqueIndexes(next, key);
|
|
967
|
-
this._commitRow(key, next);
|
|
1020
|
+
this._enforceUniqueIndexes(state, next, key);
|
|
1021
|
+
this._commitRow(state.rows, key, next);
|
|
968
1022
|
return {
|
|
969
1023
|
matchedCount: 1,
|
|
970
1024
|
modifiedCount: 1
|
|
@@ -972,13 +1026,14 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
972
1026
|
}
|
|
973
1027
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
974
1028
|
this._assertWritable();
|
|
975
|
-
const
|
|
976
|
-
|
|
1029
|
+
const state = this._peekState();
|
|
1030
|
+
const matched = this._selectForWrite(state, filter, expectedVersion, false);
|
|
1031
|
+
if (!state || matched.length === 0) return {
|
|
977
1032
|
matchedCount: 0,
|
|
978
1033
|
modifiedCount: 0
|
|
979
1034
|
};
|
|
980
1035
|
const { key, row } = matched[0];
|
|
981
|
-
this._commitUpdate(key, row, data, ops);
|
|
1036
|
+
this._commitUpdate(state, key, row, data, ops);
|
|
982
1037
|
return {
|
|
983
1038
|
matchedCount: 1,
|
|
984
1039
|
modifiedCount: 1
|
|
@@ -986,9 +1041,10 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
986
1041
|
}
|
|
987
1042
|
async deleteOne(filter) {
|
|
988
1043
|
this._assertWritable();
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
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);
|
|
992
1048
|
return { deletedCount: 1 };
|
|
993
1049
|
}
|
|
994
1050
|
/**
|
|
@@ -1092,10 +1148,19 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
1092
1148
|
calendarBucketUnits() {
|
|
1093
1149
|
return ALL_BUCKET_UNITS;
|
|
1094
1150
|
}
|
|
1151
|
+
/** Every aggregate function, `countDistinct` included. */
|
|
1152
|
+
aggregateFns() {
|
|
1153
|
+
return ALL_AGGREGATE_FNS;
|
|
1154
|
+
}
|
|
1095
1155
|
async updateMany(filter, data, ops) {
|
|
1096
1156
|
this._assertWritable();
|
|
1097
|
-
const
|
|
1098
|
-
|
|
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);
|
|
1099
1164
|
return {
|
|
1100
1165
|
matchedCount: matched.length,
|
|
1101
1166
|
modifiedCount: matched.length
|
|
@@ -1103,8 +1168,13 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
1103
1168
|
}
|
|
1104
1169
|
async replaceMany(filter, data) {
|
|
1105
1170
|
this._assertWritable();
|
|
1106
|
-
const
|
|
1107
|
-
|
|
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);
|
|
1108
1178
|
return {
|
|
1109
1179
|
matchedCount: matched.length,
|
|
1110
1180
|
modifiedCount: matched.length
|
|
@@ -1112,8 +1182,10 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
1112
1182
|
}
|
|
1113
1183
|
async deleteMany(filter) {
|
|
1114
1184
|
this._assertWritable();
|
|
1115
|
-
const
|
|
1116
|
-
|
|
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);
|
|
1117
1189
|
return { deletedCount: matched.length };
|
|
1118
1190
|
}
|
|
1119
1191
|
/**
|
|
@@ -1122,21 +1194,54 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
1122
1194
|
* in-memory scan needs no plain/fulltext/geo index to answer queries.
|
|
1123
1195
|
*/
|
|
1124
1196
|
async syncIndexes() {
|
|
1125
|
-
|
|
1197
|
+
const uniqueIndexes = [];
|
|
1126
1198
|
for (const index of this._table.indexes.values()) {
|
|
1127
1199
|
if (index.type !== "unique") continue;
|
|
1128
|
-
|
|
1200
|
+
uniqueIndexes.push({
|
|
1129
1201
|
name: index.name,
|
|
1130
1202
|
fields: index.fields.map((f) => f.name),
|
|
1131
1203
|
optionalFields: new Set(index.fields.filter((f) => f.optional).map((f) => f.name))
|
|
1132
1204
|
});
|
|
1133
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();
|
|
1134
1211
|
}
|
|
1135
1212
|
/**
|
|
1136
|
-
*
|
|
1137
|
-
*
|
|
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
|
|
1138
1218
|
*/
|
|
1139
|
-
async
|
|
1219
|
+
async tableExists() {
|
|
1220
|
+
if (isAtscriptDbView(this._table) && this._table.isExternal) return true;
|
|
1221
|
+
return this._peekState() !== void 0;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Drops a table by name: its rows, unique indexes and increment counters go,
|
|
1225
|
+
* so the table added back starts empty. A missing table is not an error;
|
|
1226
|
+
* a view under that name is (`dropViewByName` drops views).
|
|
1227
|
+
* @since 0.1.137
|
|
1228
|
+
*/
|
|
1229
|
+
async dropTableByName(tableName) {
|
|
1230
|
+
this._dropByName(tableName, "table");
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Drops a view by name — its (empty) state; a view holds no rows here.
|
|
1234
|
+
* A missing view is not an error; a table under that name is.
|
|
1235
|
+
* @since 0.1.137
|
|
1236
|
+
*/
|
|
1237
|
+
async dropViewByName(viewName) {
|
|
1238
|
+
this._dropByName(viewName, "view");
|
|
1239
|
+
}
|
|
1240
|
+
_dropByName(name, kind) {
|
|
1241
|
+
const state = this._db.get(name);
|
|
1242
|
+
if (state && state.kind !== kind) throw new Error(`Cannot drop ${kind} "${name}": it is a ${state.kind}`);
|
|
1243
|
+
this._db.delete(name);
|
|
1244
|
+
}
|
|
1140
1245
|
};
|
|
1141
1246
|
/**
|
|
1142
1247
|
* Ergonomic late-binding entry point for provider (read-through) mode. Resolves
|
|
@@ -1158,7 +1263,8 @@ function setMemoryProvider(space, type, fn) {
|
|
|
1158
1263
|
/**
|
|
1159
1264
|
* Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
|
|
1160
1265
|
*
|
|
1161
|
-
* Tables default to STORED mode (
|
|
1266
|
+
* Tables default to STORED mode (a `Map` per table in the space's in-memory
|
|
1267
|
+
* database, shared by every adapter of the space). To make one table
|
|
1162
1268
|
* read-only and read-through from a runtime closure, call `setMemoryProvider`
|
|
1163
1269
|
* (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
|
|
1164
1270
|
* adapter has been built.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.137",
|
|
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.
|
|
48
|
+
"@atscript/db": "^0.1.137"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"postinstall": "asc -f dts",
|