@atscript/db-memory 0.1.131 → 0.1.133
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/README.md +1 -1
- package/dist/index.cjs +237 -37
- package/dist/index.d.cts +18 -12
- package/dist/index.d.mts +18 -12
- package/dist/index.mjs +238 -38
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ Deliberate — matching a real engine here is hard or impossible and unnecessary
|
|
|
96
96
|
- **No array-element matching** — the dot-path getter matches scalars and nested-object paths; Mongo-style implicit array-element / `$elemMatch` matching is not provided.
|
|
97
97
|
- **Non-atomic stored batch writes** — `insertMany`/`updateMany`/`replaceMany`/`deleteMany` apply sequentially with no rollback; a mid-batch conflict leaves earlier items written. Single writes are safe. (Provider tables are read-only, so this never applies to the primary mode.)
|
|
98
98
|
- **Provider tables are read-only, with no cross-request pagination stability** — page 1 and page 2 are separate requests over separate snapshots.
|
|
99
|
-
- **
|
|
99
|
+
- **Aggregation is an in-process scan** — `$groupBy` / aggregates / calendar buckets / `$having` / `$count` run in JS over one snapshot with SQL semantics (null and missing group together; `sum`/`avg` over no value are `null`). Fine for the small sets this adapter targets; there is no index-backed grouping.
|
|
100
100
|
- **Relations `$with`** — resolved by core's app-level batch loading, not natively.
|
|
101
101
|
- **No FTS / vector / geo / `$search`** — unsupported.
|
|
102
102
|
- **Not a production datastore** — nothing is persisted or shared across processes.
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _atscript_db = require("@atscript/db");
|
|
3
|
+
let _atscript_db_agg = require("@atscript/db/agg");
|
|
4
|
+
let _uniqu_core = require("@uniqu/core");
|
|
3
5
|
//#region src/lib/memory-filter.ts
|
|
4
6
|
/**
|
|
5
7
|
* Dot-path getter. Splits `path` on `.` and walks plain objects, returning the
|
|
@@ -22,22 +24,20 @@ function getPath(row, path) {
|
|
|
22
24
|
return current;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `$exists` distinguish a `null`-valued field (present) from an absent one —
|
|
28
|
-
* a distinction {@link getPath} alone cannot make (both would read back as a
|
|
29
|
-
* nullish value). Same array limitation as {@link getPath}.
|
|
27
|
+
* {@link getPath} compiled for one path: split once, and a single segment is
|
|
28
|
+
* a direct property read — for callers that read the same path off many rows.
|
|
30
29
|
*/
|
|
31
|
-
function
|
|
30
|
+
function pathReader(path) {
|
|
31
|
+
if (!path.includes(".")) return (row) => row[path];
|
|
32
32
|
const segments = path.split(".");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
return (row) => {
|
|
34
|
+
let current = row;
|
|
35
|
+
for (const seg of segments) {
|
|
36
|
+
if (current === null || typeof current !== "object" || Array.isArray(current)) return;
|
|
37
|
+
current = current[seg];
|
|
38
|
+
}
|
|
39
|
+
return current;
|
|
40
|
+
};
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Deep-equality for leaf values, used by `$eq`/`$ne`/`$in`/`$nin`.
|
|
@@ -170,7 +170,7 @@ const memoryVisitor = {
|
|
|
170
170
|
return fieldValue != null && regex.test(stringifyLeaf(fieldValue));
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
|
-
case "$exists": return (row) => value ===
|
|
173
|
+
case "$exists": return (row) => value === !evalEq(row, field, null);
|
|
174
174
|
default: throw new _atscript_db.DbError("INVALID_QUERY", [{
|
|
175
175
|
path: field,
|
|
176
176
|
message: `Unsupported filter operator: ${op}`
|
|
@@ -290,6 +290,18 @@ function sortRows(rows, $sort, tieBreak) {
|
|
|
290
290
|
}).map((decorated) => decorated.row);
|
|
291
291
|
}
|
|
292
292
|
/**
|
|
293
|
+
* Applies `$skip` then `$limit` (both optional) via a single slice. No-op
|
|
294
|
+
* pagination (the common unpaginated read) returns the input as-is: callers
|
|
295
|
+
* pass a fresh, non-store-aliased array and copy what they return, so the
|
|
296
|
+
* whole-array `.slice` copy that `slice(0, undefined)` would make is skipped.
|
|
297
|
+
*/
|
|
298
|
+
function paginate(rows, skip, limit) {
|
|
299
|
+
const start = skip ?? 0;
|
|
300
|
+
const end = limit === void 0 ? void 0 : start + limit;
|
|
301
|
+
if (start === 0 && end === void 0) return rows;
|
|
302
|
+
return rows.slice(start, end);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
293
305
|
* Projects a plain row per a `{ path: 0 | 1 }` projection map. Decoupled from
|
|
294
306
|
* `UniquSelect`: the caller passes the resolved projection map (e.g. from
|
|
295
307
|
* `$select.asProjection`) so the engine has no query-layer dependency.
|
|
@@ -324,6 +336,195 @@ function projectRow(row, projection, opts) {
|
|
|
324
336
|
return out;
|
|
325
337
|
}
|
|
326
338
|
//#endregion
|
|
339
|
+
//#region src/lib/memory-aggregate.ts
|
|
340
|
+
/**
|
|
341
|
+
* The in-memory grouping engine behind {@link MemoryAdapter.aggregate}: a pure
|
|
342
|
+
* function over already-filtered rows (nested PHYSICAL documents), so stored
|
|
343
|
+
* and provider (read-through) mode share it unchanged. Pipeline: group →
|
|
344
|
+
* accumulate → `$having` → `$sort` → `$skip`/`$limit`, or → `$count` (the
|
|
345
|
+
* groups that survive `$having`).
|
|
346
|
+
*
|
|
347
|
+
* Decisions (the SQL adapters' semantics):
|
|
348
|
+
* - null and missing form ONE group; a calendar-bucket key is the
|
|
349
|
+
* `@uniqu/core` kernel label (the SQLite UDF runs the same kernel);
|
|
350
|
+
* - `sum` / `avg` over no numeric value are `null`; `min` / `max` order with
|
|
351
|
+
* the `$sort` comparator; any other `$fn` is `INVALID_QUERY`;
|
|
352
|
+
* - group identity is type-tagged: `1` and `"1"` never share a group (a
|
|
353
|
+
* number and a bigint of one value do), `Date`s group by instant, JSON
|
|
354
|
+
* values by a key-order-independent serialization;
|
|
355
|
+
* - the returned rows carry exactly what `$select` asks for (every group key
|
|
356
|
+
* without one), nested like Mongo's `$project` yields them, with
|
|
357
|
+
* object-valued leaves copied so no store-owned value escapes.
|
|
358
|
+
*/
|
|
359
|
+
function aggregateRows(rows, controls) {
|
|
360
|
+
const $select = controls.$select;
|
|
361
|
+
const groupBy = controls.$groupBy ?? [];
|
|
362
|
+
const aggregates = $select?.aggregates ?? [];
|
|
363
|
+
const aliases = aggregates.map((expr) => (0, _atscript_db_agg.resolveAlias)(expr));
|
|
364
|
+
const keyReaders = groupBy.map((key) => groupKeyReader(key, controls));
|
|
365
|
+
const accumulators = aggregates.map(accumulatorFactory);
|
|
366
|
+
const newGroup = (values) => ({
|
|
367
|
+
values,
|
|
368
|
+
accs: accumulators.map((create) => create())
|
|
369
|
+
});
|
|
370
|
+
let groups;
|
|
371
|
+
if (keyReaders.length === 0) {
|
|
372
|
+
const group = newGroup([]);
|
|
373
|
+
for (const row of rows) for (const acc of group.accs) acc.add(row);
|
|
374
|
+
groups = [group];
|
|
375
|
+
} else {
|
|
376
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const row of rows) {
|
|
378
|
+
const values = keyReaders.map((read) => read(row));
|
|
379
|
+
const identity = groupIdentity(values);
|
|
380
|
+
let group = byIdentity.get(identity);
|
|
381
|
+
if (!group) {
|
|
382
|
+
group = newGroup(values);
|
|
383
|
+
byIdentity.set(identity, group);
|
|
384
|
+
}
|
|
385
|
+
for (const acc of group.accs) acc.add(row);
|
|
386
|
+
}
|
|
387
|
+
groups = byIdentity.values();
|
|
388
|
+
}
|
|
389
|
+
let out = [];
|
|
390
|
+
for (const { values, accs } of groups) {
|
|
391
|
+
const row = {};
|
|
392
|
+
for (let i = 0; i < groupBy.length; i++) setPath(row, groupBy[i], values[i]);
|
|
393
|
+
for (let i = 0; i < aliases.length; i++) row[aliases[i]] = accs[i].result();
|
|
394
|
+
out.push(row);
|
|
395
|
+
}
|
|
396
|
+
const having = controls.$having;
|
|
397
|
+
if (having) out = out.filter(buildMemoryPredicate(having));
|
|
398
|
+
if (controls.$count) return [{ count: out.length }];
|
|
399
|
+
const paged = paginate(sortRows(out, controls.$sort), controls.$skip, controls.$limit);
|
|
400
|
+
const fields = ($select === void 0 ? groupBy : $select.asArray ?? []).map((field) => [field, pathReader(field)]);
|
|
401
|
+
const outputAliases = $select === void 0 ? [] : [...($select.buckets ?? []).map((b) => b.alias), ...aliases];
|
|
402
|
+
return paged.map((row) => {
|
|
403
|
+
const picked = {};
|
|
404
|
+
for (const [field, read] of fields) setPath(picked, field, copyValue(read(row) ?? null));
|
|
405
|
+
for (const alias of outputAliases) picked[alias] = copyValue(row[alias]);
|
|
406
|
+
return picked;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/** Group values and min / max results may be store-owned objects (JSON values): hand back copies. */
|
|
410
|
+
function copyValue(value) {
|
|
411
|
+
return value !== null && typeof value === "object" ? structuredClone(value) : value;
|
|
412
|
+
}
|
|
413
|
+
/** Reads one `$groupBy` key off a row: a bucket alias → its label, a path → its value. */
|
|
414
|
+
function groupKeyReader(key, controls) {
|
|
415
|
+
const bucket = controls.$select?.bucketByAlias(key);
|
|
416
|
+
if (bucket) {
|
|
417
|
+
const label = (0, _uniqu_core.bucketer)(bucket.unit, bucket.tz, bucket.weekStart);
|
|
418
|
+
const read = pathReader(bucket.field);
|
|
419
|
+
return (row) => label(read(row));
|
|
420
|
+
}
|
|
421
|
+
const read = pathReader(key);
|
|
422
|
+
return (row) => read(row) ?? null;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* The identity of a group's key values: one {@link identityToken} per key,
|
|
426
|
+
* length-prefixed when there are several, so no value can forge a separator.
|
|
427
|
+
*/
|
|
428
|
+
function groupIdentity(values) {
|
|
429
|
+
if (values.length === 1) return identityToken(values[0]);
|
|
430
|
+
let identity = "";
|
|
431
|
+
for (const value of values) {
|
|
432
|
+
const token = identityToken(value);
|
|
433
|
+
identity += `${token.length}:${token}`;
|
|
434
|
+
}
|
|
435
|
+
return identity;
|
|
436
|
+
}
|
|
437
|
+
/** A type-tagged identity token for one key value (see {@link aggregateRows}). */
|
|
438
|
+
function identityToken(value) {
|
|
439
|
+
if (value === null || value === void 0) return "z";
|
|
440
|
+
if (value instanceof Date) return `D${value.getTime()}`;
|
|
441
|
+
switch (typeof value) {
|
|
442
|
+
case "string": return `s${value}`;
|
|
443
|
+
case "number":
|
|
444
|
+
case "bigint": return `n${String(value)}`;
|
|
445
|
+
case "boolean": return value ? "b1" : "b0";
|
|
446
|
+
default: return `j${stableJson(value)}`;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/** `JSON.stringify` with object keys sorted at every level. */
|
|
450
|
+
function stableJson(value) {
|
|
451
|
+
return JSON.stringify(value, (_key, v) => {
|
|
452
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) return v;
|
|
453
|
+
const sorted = {};
|
|
454
|
+
for (const k of Object.keys(v).toSorted()) sorted[k] = v[k];
|
|
455
|
+
return sorted;
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/** Numeric value of a measure, or `undefined` for null / missing / non-numeric. */
|
|
459
|
+
function numericValue(value) {
|
|
460
|
+
if (typeof value === "number") return Number.isNaN(value) ? void 0 : value;
|
|
461
|
+
if (typeof value === "bigint") return Number(value);
|
|
462
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
463
|
+
const n = Number(value);
|
|
464
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Resolves an aggregate expression to an accumulator factory (one accumulator
|
|
469
|
+
* per group). Validated up front, so an unknown `$fn` is rejected even when
|
|
470
|
+
* there are no rows.
|
|
471
|
+
*/
|
|
472
|
+
function accumulatorFactory(expr) {
|
|
473
|
+
const field = expr.$field;
|
|
474
|
+
const read = pathReader(field);
|
|
475
|
+
switch (expr.$fn) {
|
|
476
|
+
case "count": {
|
|
477
|
+
const all = field === "*";
|
|
478
|
+
return () => {
|
|
479
|
+
let n = 0;
|
|
480
|
+
return {
|
|
481
|
+
add: (row) => {
|
|
482
|
+
if (all || read(row) != null) n++;
|
|
483
|
+
},
|
|
484
|
+
result: () => n
|
|
485
|
+
};
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
case "sum":
|
|
489
|
+
case "avg": {
|
|
490
|
+
const avg = expr.$fn === "avg";
|
|
491
|
+
return () => {
|
|
492
|
+
let sum = 0;
|
|
493
|
+
let n = 0;
|
|
494
|
+
return {
|
|
495
|
+
add: (row) => {
|
|
496
|
+
const v = numericValue(read(row));
|
|
497
|
+
if (v !== void 0) {
|
|
498
|
+
sum += v;
|
|
499
|
+
n++;
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
result: () => n === 0 ? null : avg ? sum / n : sum
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
case "min":
|
|
507
|
+
case "max": {
|
|
508
|
+
const sign = expr.$fn === "min" ? -1 : 1;
|
|
509
|
+
return () => {
|
|
510
|
+
let best = null;
|
|
511
|
+
return {
|
|
512
|
+
add: (row) => {
|
|
513
|
+
const v = read(row);
|
|
514
|
+
if (v == null) return;
|
|
515
|
+
if (best === null || compareLeaves(v, best) * sign > 0) best = v;
|
|
516
|
+
},
|
|
517
|
+
result: () => best
|
|
518
|
+
};
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
default: throw new _atscript_db.DbError("INVALID_QUERY", [{
|
|
522
|
+
path: "$select",
|
|
523
|
+
message: `Unsupported aggregate function "${String(expr.$fn)}" — use count, sum, avg, min or max`
|
|
524
|
+
}]);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
//#endregion
|
|
327
528
|
//#region src/lib/memory-adapter.ts
|
|
328
529
|
/**
|
|
329
530
|
* In-memory {@link BaseDbAdapter} implementation.
|
|
@@ -561,8 +762,7 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
561
762
|
* Write guard for provider-backed (read-only) mode. Called first in every one
|
|
562
763
|
* of the 8 write methods so all mutation entry points reject identically. Uses
|
|
563
764
|
* `INVALID_QUERY` (moost-db's validation interceptor maps it to HTTP 400, NOT
|
|
564
|
-
* 500
|
|
565
|
-
* read-only error code.
|
|
765
|
+
* 500) since there is no dedicated read-only error code.
|
|
566
766
|
*/
|
|
567
767
|
_assertWritable() {
|
|
568
768
|
if (this._provider) throw new _atscript_db.DbError("INVALID_QUERY", [{
|
|
@@ -803,13 +1003,6 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
803
1003
|
_sortRows(rows, $sort) {
|
|
804
1004
|
return sortRows(rows, $sort, (r) => this.pkKey(r));
|
|
805
1005
|
}
|
|
806
|
-
/** Applies `$skip` then `$limit` (both optional) via a single slice. */
|
|
807
|
-
_paginate(rows, skip, limit) {
|
|
808
|
-
const start = skip ?? 0;
|
|
809
|
-
const end = limit === void 0 ? void 0 : start + limit;
|
|
810
|
-
if (start === 0 && end === void 0) return rows;
|
|
811
|
-
return rows.slice(start, end);
|
|
812
|
-
}
|
|
813
1006
|
/**
|
|
814
1007
|
* Projects a stored row per `$select` and returns a fresh, deep-cloned object
|
|
815
1008
|
* so the store can never be mutated through a returned value.
|
|
@@ -875,25 +1068,32 @@ var MemoryAdapter = class extends _atscript_db.BaseDbAdapter {
|
|
|
875
1068
|
async findManyWithCount(query) {
|
|
876
1069
|
const filtered = await this._filteredRows(query);
|
|
877
1070
|
const { $sort, $skip, $limit, $select } = this._readControls(query.controls ?? {});
|
|
878
|
-
const sorted = this._sortRows(filtered, $sort);
|
|
879
1071
|
return {
|
|
880
|
-
data: this.
|
|
1072
|
+
data: paginate(this._sortRows(filtered, $sort), $skip, $limit).map((row) => this._projectAndClone(row, $select)),
|
|
881
1073
|
count: filtered.length
|
|
882
1074
|
};
|
|
883
1075
|
}
|
|
884
1076
|
/**
|
|
885
|
-
*
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
1077
|
+
* Grouped query (`$groupBy` + aggregates, calendar buckets, `$having`,
|
|
1078
|
+
* `$sort`, `$skip` / `$limit`, `$count`) over ONE filtered snapshot — the
|
|
1079
|
+
* pure {@link aggregateRows} engine, so provider (read-through) mode works
|
|
1080
|
+
* unchanged. SQL-adapter semantics: null and missing group together,
|
|
1081
|
+
* `sum` / `avg` over no numeric value are `null`, `$count` counts the groups
|
|
1082
|
+
* that survive `$having`.
|
|
1083
|
+
*
|
|
1084
|
+
* `$search` never reaches here: this adapter is not searchable, so the core
|
|
1085
|
+
* rejects a grouped search before dispatch.
|
|
891
1086
|
*/
|
|
892
|
-
async aggregate(
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1087
|
+
async aggregate(query) {
|
|
1088
|
+
return aggregateRows(await this._filteredRows(query), query.controls ?? {});
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Every calendar-bucket unit: labels come from the `@uniqu/core` kernel
|
|
1092
|
+
* (Node's ICU zone data — the list the core validates zones against, so
|
|
1093
|
+
* there is no zone this adapter cannot resolve).
|
|
1094
|
+
*/
|
|
1095
|
+
calendarBucketUnits() {
|
|
1096
|
+
return _atscript_db.ALL_BUCKET_UNITS;
|
|
897
1097
|
}
|
|
898
1098
|
async updateMany(filter, data, ops) {
|
|
899
1099
|
this._assertWritable();
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TFieldOps } from "@atscript/db";
|
|
1
|
+
import { 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
|
|
@@ -192,8 +192,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
192
192
|
* Write guard for provider-backed (read-only) mode. Called first in every one
|
|
193
193
|
* of the 8 write methods so all mutation entry points reject identically. Uses
|
|
194
194
|
* `INVALID_QUERY` (moost-db's validation interceptor maps it to HTTP 400, NOT
|
|
195
|
-
* 500
|
|
196
|
-
* read-only error code.
|
|
195
|
+
* 500) since there is no dedicated read-only error code.
|
|
197
196
|
*/
|
|
198
197
|
private _assertWritable;
|
|
199
198
|
/**
|
|
@@ -308,8 +307,6 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
308
307
|
* {@link sortRows}, injecting {@link pkKey} as the total-order tie-break.
|
|
309
308
|
*/
|
|
310
309
|
private _sortRows;
|
|
311
|
-
/** Applies `$skip` then `$limit` (both optional) via a single slice. */
|
|
312
|
-
private _paginate;
|
|
313
310
|
/**
|
|
314
311
|
* Projects a stored row per `$select` and returns a fresh, deep-cloned object
|
|
315
312
|
* so the store can never be mutated through a returned value.
|
|
@@ -353,14 +350,23 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
353
350
|
count: number;
|
|
354
351
|
}>;
|
|
355
352
|
/**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
353
|
+
* Grouped query (`$groupBy` + aggregates, calendar buckets, `$having`,
|
|
354
|
+
* `$sort`, `$skip` / `$limit`, `$count`) over ONE filtered snapshot — the
|
|
355
|
+
* pure {@link aggregateRows} engine, so provider (read-through) mode works
|
|
356
|
+
* unchanged. SQL-adapter semantics: null and missing group together,
|
|
357
|
+
* `sum` / `avg` over no numeric value are `null`, `$count` counts the groups
|
|
358
|
+
* that survive `$having`.
|
|
359
|
+
*
|
|
360
|
+
* `$search` never reaches here: this adapter is not searchable, so the core
|
|
361
|
+
* rejects a grouped search before dispatch.
|
|
362
|
+
*/
|
|
363
|
+
aggregate(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
364
|
+
/**
|
|
365
|
+
* Every calendar-bucket unit: labels come from the `@uniqu/core` kernel
|
|
366
|
+
* (Node's ICU zone data — the list the core validates zones against, so
|
|
367
|
+
* there is no zone this adapter cannot resolve).
|
|
362
368
|
*/
|
|
363
|
-
|
|
369
|
+
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
364
370
|
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
365
371
|
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
366
372
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TFieldOps } from "@atscript/db";
|
|
1
|
+
import { 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
|
|
@@ -192,8 +192,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
192
192
|
* Write guard for provider-backed (read-only) mode. Called first in every one
|
|
193
193
|
* of the 8 write methods so all mutation entry points reject identically. Uses
|
|
194
194
|
* `INVALID_QUERY` (moost-db's validation interceptor maps it to HTTP 400, NOT
|
|
195
|
-
* 500
|
|
196
|
-
* read-only error code.
|
|
195
|
+
* 500) since there is no dedicated read-only error code.
|
|
197
196
|
*/
|
|
198
197
|
private _assertWritable;
|
|
199
198
|
/**
|
|
@@ -308,8 +307,6 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
308
307
|
* {@link sortRows}, injecting {@link pkKey} as the total-order tie-break.
|
|
309
308
|
*/
|
|
310
309
|
private _sortRows;
|
|
311
|
-
/** Applies `$skip` then `$limit` (both optional) via a single slice. */
|
|
312
|
-
private _paginate;
|
|
313
310
|
/**
|
|
314
311
|
* Projects a stored row per `$select` and returns a fresh, deep-cloned object
|
|
315
312
|
* so the store can never be mutated through a returned value.
|
|
@@ -353,14 +350,23 @@ declare class MemoryAdapter extends BaseDbAdapter {
|
|
|
353
350
|
count: number;
|
|
354
351
|
}>;
|
|
355
352
|
/**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
353
|
+
* Grouped query (`$groupBy` + aggregates, calendar buckets, `$having`,
|
|
354
|
+
* `$sort`, `$skip` / `$limit`, `$count`) over ONE filtered snapshot — the
|
|
355
|
+
* pure {@link aggregateRows} engine, so provider (read-through) mode works
|
|
356
|
+
* unchanged. SQL-adapter semantics: null and missing group together,
|
|
357
|
+
* `sum` / `avg` over no numeric value are `null`, `$count` counts the groups
|
|
358
|
+
* that survive `$having`.
|
|
359
|
+
*
|
|
360
|
+
* `$search` never reaches here: this adapter is not searchable, so the core
|
|
361
|
+
* rejects a grouped search before dispatch.
|
|
362
|
+
*/
|
|
363
|
+
aggregate(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
364
|
+
/**
|
|
365
|
+
* Every calendar-bucket unit: labels come from the `@uniqu/core` kernel
|
|
366
|
+
* (Node's ICU zone data — the list the core validates zones against, so
|
|
367
|
+
* there is no zone this adapter cannot resolve).
|
|
362
368
|
*/
|
|
363
|
-
|
|
369
|
+
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
364
370
|
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
365
371
|
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
366
372
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbError, DbSpace, walkFilter } from "@atscript/db";
|
|
1
|
+
import { ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, walkFilter } from "@atscript/db";
|
|
2
|
+
import { resolveAlias } from "@atscript/db/agg";
|
|
3
|
+
import { bucketer } from "@uniqu/core";
|
|
2
4
|
//#region src/lib/memory-filter.ts
|
|
3
5
|
/**
|
|
4
6
|
* Dot-path getter. Splits `path` on `.` and walks plain objects, returning the
|
|
@@ -21,22 +23,20 @@ function getPath(row, path) {
|
|
|
21
23
|
return current;
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `$exists` distinguish a `null`-valued field (present) from an absent one —
|
|
27
|
-
* a distinction {@link getPath} alone cannot make (both would read back as a
|
|
28
|
-
* nullish value). Same array limitation as {@link getPath}.
|
|
26
|
+
* {@link getPath} compiled for one path: split once, and a single segment is
|
|
27
|
+
* a direct property read — for callers that read the same path off many rows.
|
|
29
28
|
*/
|
|
30
|
-
function
|
|
29
|
+
function pathReader(path) {
|
|
30
|
+
if (!path.includes(".")) return (row) => row[path];
|
|
31
31
|
const segments = path.split(".");
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
return (row) => {
|
|
33
|
+
let current = row;
|
|
34
|
+
for (const seg of segments) {
|
|
35
|
+
if (current === null || typeof current !== "object" || Array.isArray(current)) return;
|
|
36
|
+
current = current[seg];
|
|
37
|
+
}
|
|
38
|
+
return current;
|
|
39
|
+
};
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Deep-equality for leaf values, used by `$eq`/`$ne`/`$in`/`$nin`.
|
|
@@ -169,7 +169,7 @@ const memoryVisitor = {
|
|
|
169
169
|
return fieldValue != null && regex.test(stringifyLeaf(fieldValue));
|
|
170
170
|
};
|
|
171
171
|
}
|
|
172
|
-
case "$exists": return (row) => value ===
|
|
172
|
+
case "$exists": return (row) => value === !evalEq(row, field, null);
|
|
173
173
|
default: throw new DbError("INVALID_QUERY", [{
|
|
174
174
|
path: field,
|
|
175
175
|
message: `Unsupported filter operator: ${op}`
|
|
@@ -289,6 +289,18 @@ function sortRows(rows, $sort, tieBreak) {
|
|
|
289
289
|
}).map((decorated) => decorated.row);
|
|
290
290
|
}
|
|
291
291
|
/**
|
|
292
|
+
* Applies `$skip` then `$limit` (both optional) via a single slice. No-op
|
|
293
|
+
* pagination (the common unpaginated read) returns the input as-is: callers
|
|
294
|
+
* pass a fresh, non-store-aliased array and copy what they return, so the
|
|
295
|
+
* whole-array `.slice` copy that `slice(0, undefined)` would make is skipped.
|
|
296
|
+
*/
|
|
297
|
+
function paginate(rows, skip, limit) {
|
|
298
|
+
const start = skip ?? 0;
|
|
299
|
+
const end = limit === void 0 ? void 0 : start + limit;
|
|
300
|
+
if (start === 0 && end === void 0) return rows;
|
|
301
|
+
return rows.slice(start, end);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
292
304
|
* Projects a plain row per a `{ path: 0 | 1 }` projection map. Decoupled from
|
|
293
305
|
* `UniquSelect`: the caller passes the resolved projection map (e.g. from
|
|
294
306
|
* `$select.asProjection`) so the engine has no query-layer dependency.
|
|
@@ -323,6 +335,195 @@ function projectRow(row, projection, opts) {
|
|
|
323
335
|
return out;
|
|
324
336
|
}
|
|
325
337
|
//#endregion
|
|
338
|
+
//#region src/lib/memory-aggregate.ts
|
|
339
|
+
/**
|
|
340
|
+
* The in-memory grouping engine behind {@link MemoryAdapter.aggregate}: a pure
|
|
341
|
+
* function over already-filtered rows (nested PHYSICAL documents), so stored
|
|
342
|
+
* and provider (read-through) mode share it unchanged. Pipeline: group →
|
|
343
|
+
* accumulate → `$having` → `$sort` → `$skip`/`$limit`, or → `$count` (the
|
|
344
|
+
* groups that survive `$having`).
|
|
345
|
+
*
|
|
346
|
+
* Decisions (the SQL adapters' semantics):
|
|
347
|
+
* - null and missing form ONE group; a calendar-bucket key is the
|
|
348
|
+
* `@uniqu/core` kernel label (the SQLite UDF runs the same kernel);
|
|
349
|
+
* - `sum` / `avg` over no numeric value are `null`; `min` / `max` order with
|
|
350
|
+
* the `$sort` comparator; any other `$fn` is `INVALID_QUERY`;
|
|
351
|
+
* - group identity is type-tagged: `1` and `"1"` never share a group (a
|
|
352
|
+
* number and a bigint of one value do), `Date`s group by instant, JSON
|
|
353
|
+
* values by a key-order-independent serialization;
|
|
354
|
+
* - the returned rows carry exactly what `$select` asks for (every group key
|
|
355
|
+
* without one), nested like Mongo's `$project` yields them, with
|
|
356
|
+
* object-valued leaves copied so no store-owned value escapes.
|
|
357
|
+
*/
|
|
358
|
+
function aggregateRows(rows, controls) {
|
|
359
|
+
const $select = controls.$select;
|
|
360
|
+
const groupBy = controls.$groupBy ?? [];
|
|
361
|
+
const aggregates = $select?.aggregates ?? [];
|
|
362
|
+
const aliases = aggregates.map((expr) => resolveAlias(expr));
|
|
363
|
+
const keyReaders = groupBy.map((key) => groupKeyReader(key, controls));
|
|
364
|
+
const accumulators = aggregates.map(accumulatorFactory);
|
|
365
|
+
const newGroup = (values) => ({
|
|
366
|
+
values,
|
|
367
|
+
accs: accumulators.map((create) => create())
|
|
368
|
+
});
|
|
369
|
+
let groups;
|
|
370
|
+
if (keyReaders.length === 0) {
|
|
371
|
+
const group = newGroup([]);
|
|
372
|
+
for (const row of rows) for (const acc of group.accs) acc.add(row);
|
|
373
|
+
groups = [group];
|
|
374
|
+
} else {
|
|
375
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
376
|
+
for (const row of rows) {
|
|
377
|
+
const values = keyReaders.map((read) => read(row));
|
|
378
|
+
const identity = groupIdentity(values);
|
|
379
|
+
let group = byIdentity.get(identity);
|
|
380
|
+
if (!group) {
|
|
381
|
+
group = newGroup(values);
|
|
382
|
+
byIdentity.set(identity, group);
|
|
383
|
+
}
|
|
384
|
+
for (const acc of group.accs) acc.add(row);
|
|
385
|
+
}
|
|
386
|
+
groups = byIdentity.values();
|
|
387
|
+
}
|
|
388
|
+
let out = [];
|
|
389
|
+
for (const { values, accs } of groups) {
|
|
390
|
+
const row = {};
|
|
391
|
+
for (let i = 0; i < groupBy.length; i++) setPath(row, groupBy[i], values[i]);
|
|
392
|
+
for (let i = 0; i < aliases.length; i++) row[aliases[i]] = accs[i].result();
|
|
393
|
+
out.push(row);
|
|
394
|
+
}
|
|
395
|
+
const having = controls.$having;
|
|
396
|
+
if (having) out = out.filter(buildMemoryPredicate(having));
|
|
397
|
+
if (controls.$count) return [{ count: out.length }];
|
|
398
|
+
const paged = paginate(sortRows(out, controls.$sort), controls.$skip, controls.$limit);
|
|
399
|
+
const fields = ($select === void 0 ? groupBy : $select.asArray ?? []).map((field) => [field, pathReader(field)]);
|
|
400
|
+
const outputAliases = $select === void 0 ? [] : [...($select.buckets ?? []).map((b) => b.alias), ...aliases];
|
|
401
|
+
return paged.map((row) => {
|
|
402
|
+
const picked = {};
|
|
403
|
+
for (const [field, read] of fields) setPath(picked, field, copyValue(read(row) ?? null));
|
|
404
|
+
for (const alias of outputAliases) picked[alias] = copyValue(row[alias]);
|
|
405
|
+
return picked;
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
/** Group values and min / max results may be store-owned objects (JSON values): hand back copies. */
|
|
409
|
+
function copyValue(value) {
|
|
410
|
+
return value !== null && typeof value === "object" ? structuredClone(value) : value;
|
|
411
|
+
}
|
|
412
|
+
/** Reads one `$groupBy` key off a row: a bucket alias → its label, a path → its value. */
|
|
413
|
+
function groupKeyReader(key, controls) {
|
|
414
|
+
const bucket = controls.$select?.bucketByAlias(key);
|
|
415
|
+
if (bucket) {
|
|
416
|
+
const label = bucketer(bucket.unit, bucket.tz, bucket.weekStart);
|
|
417
|
+
const read = pathReader(bucket.field);
|
|
418
|
+
return (row) => label(read(row));
|
|
419
|
+
}
|
|
420
|
+
const read = pathReader(key);
|
|
421
|
+
return (row) => read(row) ?? null;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* The identity of a group's key values: one {@link identityToken} per key,
|
|
425
|
+
* length-prefixed when there are several, so no value can forge a separator.
|
|
426
|
+
*/
|
|
427
|
+
function groupIdentity(values) {
|
|
428
|
+
if (values.length === 1) return identityToken(values[0]);
|
|
429
|
+
let identity = "";
|
|
430
|
+
for (const value of values) {
|
|
431
|
+
const token = identityToken(value);
|
|
432
|
+
identity += `${token.length}:${token}`;
|
|
433
|
+
}
|
|
434
|
+
return identity;
|
|
435
|
+
}
|
|
436
|
+
/** A type-tagged identity token for one key value (see {@link aggregateRows}). */
|
|
437
|
+
function identityToken(value) {
|
|
438
|
+
if (value === null || value === void 0) return "z";
|
|
439
|
+
if (value instanceof Date) return `D${value.getTime()}`;
|
|
440
|
+
switch (typeof value) {
|
|
441
|
+
case "string": return `s${value}`;
|
|
442
|
+
case "number":
|
|
443
|
+
case "bigint": return `n${String(value)}`;
|
|
444
|
+
case "boolean": return value ? "b1" : "b0";
|
|
445
|
+
default: return `j${stableJson(value)}`;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/** `JSON.stringify` with object keys sorted at every level. */
|
|
449
|
+
function stableJson(value) {
|
|
450
|
+
return JSON.stringify(value, (_key, v) => {
|
|
451
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) return v;
|
|
452
|
+
const sorted = {};
|
|
453
|
+
for (const k of Object.keys(v).toSorted()) sorted[k] = v[k];
|
|
454
|
+
return sorted;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/** Numeric value of a measure, or `undefined` for null / missing / non-numeric. */
|
|
458
|
+
function numericValue(value) {
|
|
459
|
+
if (typeof value === "number") return Number.isNaN(value) ? void 0 : value;
|
|
460
|
+
if (typeof value === "bigint") return Number(value);
|
|
461
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
462
|
+
const n = Number(value);
|
|
463
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Resolves an aggregate expression to an accumulator factory (one accumulator
|
|
468
|
+
* per group). Validated up front, so an unknown `$fn` is rejected even when
|
|
469
|
+
* there are no rows.
|
|
470
|
+
*/
|
|
471
|
+
function accumulatorFactory(expr) {
|
|
472
|
+
const field = expr.$field;
|
|
473
|
+
const read = pathReader(field);
|
|
474
|
+
switch (expr.$fn) {
|
|
475
|
+
case "count": {
|
|
476
|
+
const all = field === "*";
|
|
477
|
+
return () => {
|
|
478
|
+
let n = 0;
|
|
479
|
+
return {
|
|
480
|
+
add: (row) => {
|
|
481
|
+
if (all || read(row) != null) n++;
|
|
482
|
+
},
|
|
483
|
+
result: () => n
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
case "sum":
|
|
488
|
+
case "avg": {
|
|
489
|
+
const avg = expr.$fn === "avg";
|
|
490
|
+
return () => {
|
|
491
|
+
let sum = 0;
|
|
492
|
+
let n = 0;
|
|
493
|
+
return {
|
|
494
|
+
add: (row) => {
|
|
495
|
+
const v = numericValue(read(row));
|
|
496
|
+
if (v !== void 0) {
|
|
497
|
+
sum += v;
|
|
498
|
+
n++;
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
result: () => n === 0 ? null : avg ? sum / n : sum
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
case "min":
|
|
506
|
+
case "max": {
|
|
507
|
+
const sign = expr.$fn === "min" ? -1 : 1;
|
|
508
|
+
return () => {
|
|
509
|
+
let best = null;
|
|
510
|
+
return {
|
|
511
|
+
add: (row) => {
|
|
512
|
+
const v = read(row);
|
|
513
|
+
if (v == null) return;
|
|
514
|
+
if (best === null || compareLeaves(v, best) * sign > 0) best = v;
|
|
515
|
+
},
|
|
516
|
+
result: () => best
|
|
517
|
+
};
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
default: throw new DbError("INVALID_QUERY", [{
|
|
521
|
+
path: "$select",
|
|
522
|
+
message: `Unsupported aggregate function "${String(expr.$fn)}" — use count, sum, avg, min or max`
|
|
523
|
+
}]);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
//#endregion
|
|
326
527
|
//#region src/lib/memory-adapter.ts
|
|
327
528
|
/**
|
|
328
529
|
* In-memory {@link BaseDbAdapter} implementation.
|
|
@@ -560,8 +761,7 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
560
761
|
* Write guard for provider-backed (read-only) mode. Called first in every one
|
|
561
762
|
* of the 8 write methods so all mutation entry points reject identically. Uses
|
|
562
763
|
* `INVALID_QUERY` (moost-db's validation interceptor maps it to HTTP 400, NOT
|
|
563
|
-
* 500
|
|
564
|
-
* read-only error code.
|
|
764
|
+
* 500) since there is no dedicated read-only error code.
|
|
565
765
|
*/
|
|
566
766
|
_assertWritable() {
|
|
567
767
|
if (this._provider) throw new DbError("INVALID_QUERY", [{
|
|
@@ -802,13 +1002,6 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
802
1002
|
_sortRows(rows, $sort) {
|
|
803
1003
|
return sortRows(rows, $sort, (r) => this.pkKey(r));
|
|
804
1004
|
}
|
|
805
|
-
/** Applies `$skip` then `$limit` (both optional) via a single slice. */
|
|
806
|
-
_paginate(rows, skip, limit) {
|
|
807
|
-
const start = skip ?? 0;
|
|
808
|
-
const end = limit === void 0 ? void 0 : start + limit;
|
|
809
|
-
if (start === 0 && end === void 0) return rows;
|
|
810
|
-
return rows.slice(start, end);
|
|
811
|
-
}
|
|
812
1005
|
/**
|
|
813
1006
|
* Projects a stored row per `$select` and returns a fresh, deep-cloned object
|
|
814
1007
|
* so the store can never be mutated through a returned value.
|
|
@@ -874,25 +1067,32 @@ var MemoryAdapter = class extends BaseDbAdapter {
|
|
|
874
1067
|
async findManyWithCount(query) {
|
|
875
1068
|
const filtered = await this._filteredRows(query);
|
|
876
1069
|
const { $sort, $skip, $limit, $select } = this._readControls(query.controls ?? {});
|
|
877
|
-
const sorted = this._sortRows(filtered, $sort);
|
|
878
1070
|
return {
|
|
879
|
-
data: this.
|
|
1071
|
+
data: paginate(this._sortRows(filtered, $sort), $skip, $limit).map((row) => this._projectAndClone(row, $select)),
|
|
880
1072
|
count: filtered.length
|
|
881
1073
|
};
|
|
882
1074
|
}
|
|
883
1075
|
/**
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
*
|
|
1076
|
+
* Grouped query (`$groupBy` + aggregates, calendar buckets, `$having`,
|
|
1077
|
+
* `$sort`, `$skip` / `$limit`, `$count`) over ONE filtered snapshot — the
|
|
1078
|
+
* pure {@link aggregateRows} engine, so provider (read-through) mode works
|
|
1079
|
+
* unchanged. SQL-adapter semantics: null and missing group together,
|
|
1080
|
+
* `sum` / `avg` over no numeric value are `null`, `$count` counts the groups
|
|
1081
|
+
* that survive `$having`.
|
|
1082
|
+
*
|
|
1083
|
+
* `$search` never reaches here: this adapter is not searchable, so the core
|
|
1084
|
+
* rejects a grouped search before dispatch.
|
|
890
1085
|
*/
|
|
891
|
-
async aggregate(
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1086
|
+
async aggregate(query) {
|
|
1087
|
+
return aggregateRows(await this._filteredRows(query), query.controls ?? {});
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Every calendar-bucket unit: labels come from the `@uniqu/core` kernel
|
|
1091
|
+
* (Node's ICU zone data — the list the core validates zones against, so
|
|
1092
|
+
* there is no zone this adapter cannot resolve).
|
|
1093
|
+
*/
|
|
1094
|
+
calendarBucketUnits() {
|
|
1095
|
+
return ALL_BUCKET_UNITS;
|
|
896
1096
|
}
|
|
897
1097
|
async updateMany(filter, data, ops) {
|
|
898
1098
|
this._assertWritable();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.133",
|
|
4
4
|
"description": "In-memory adapter for atscript-db.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -38,12 +38,14 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@atscript/core": "^0.1.92",
|
|
40
40
|
"@atscript/typescript": "^0.1.92",
|
|
41
|
+
"@uniqu/core": "^0.1.10",
|
|
41
42
|
"unplugin-atscript": "^0.1.92"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"@atscript/core": "^0.1.92",
|
|
45
46
|
"@atscript/typescript": "^0.1.92",
|
|
46
|
-
"@
|
|
47
|
+
"@uniqu/core": "^0.1.10",
|
|
48
|
+
"@atscript/db": "^0.1.133"
|
|
47
49
|
},
|
|
48
50
|
"scripts": {
|
|
49
51
|
"postinstall": "asc -f dts",
|