@atscript/moost-db 0.1.150 → 0.1.152
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 +549 -106
- package/dist/index.d.cts +111 -1
- package/dist/index.d.mts +111 -1
- package/dist/index.mjs +567 -125
- package/package.json +20 -20
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { a as resolveDbSpace, i as provideDbSpace, n as clearDbSpaces, r as closeDbSpaces, t as DEFAULT_DB_SPACE } from "./db-space-registry-Cdb0FGCn.mjs";
|
|
2
2
|
import { ValidatorError, defineAnnotatedType, isAnnotatedType, serializeAnnotatedType, throwFeatureDisabled } from "@atscript/typescript/utils";
|
|
3
|
-
import { Body, Delete, Get, HttpError, MoostHttp, Patch, Post, Put, Query, Url } from "@moostjs/event-http";
|
|
4
|
-
import { ApplyDecorators, Controller, Inherit, Inject, Intercept, Moost, Optional, Param, Pipe, Provide, Resolve, TInterceptorPriority, TPipePriority, defineBeforeInterceptor, defineInterceptor, definePipeFn, getMoostMate, useControllerContext, withControllerContext } from "moost";
|
|
3
|
+
import { Body, Delete, Get, HttpError, MoostHttp, Patch, Post, Put, Query, Url, httpKind, prerenderJson } from "@moostjs/event-http";
|
|
4
|
+
import { ApplyDecorators, Controller, Inherit, Inject, Intercept, Moost, Optional, Param, Pipe, Provide, Resolve, TInterceptorPriority, TPipePriority, current, defineBeforeInterceptor, defineInterceptor, definePipeFn, getMoostMate, useControllerContext, withControllerContext } from "moost";
|
|
5
5
|
import { parseUrl } from "@uniqu/url";
|
|
6
6
|
import { ADAPTER_FILTER_REASON, ALL_AGGREGATE_FNS, DbError, ENCRYPTED_REASON, acceptedOperatorsHint, aliasTargetOf, andFilters, bucketSourceVerdict, canFilterLeaf, checkHavingKeys, classifyQueryPath, collectQueryPaths, collectQueryPaths as collectQueryPaths$1, containsRelationFilter, findAncestorInSet, geoIndexNotFoundMessage, groupSourceVerdict, hasRelationOp, isEmptyObject, isJsonValueField, isPlainObject, isRelationOp, narrowerFilterOps, normalizeComputedSelect, numericOperandProblem, reconcileCas, searchIndexNotFoundMessage, searchMemberKind, selfOrAncestor, unsupportedOperatorMessage, vectorIndexNotFoundMessage } from "@atscript/db";
|
|
7
|
-
import { cached, current, defineWook, key, routeParamsKey, useRouteParams } from "@wooksjs/event-core";
|
|
7
|
+
import { cached, current as current$1, defineWook, key, routeParamsKey, useRouteParams } from "@wooksjs/event-core";
|
|
8
8
|
import { useBody } from "@wooksjs/http-body";
|
|
9
9
|
import { BUCKET_UNITS } from "@uniqu/core";
|
|
10
10
|
import { buildMemoryPredicate, projectRow, sortRows } from "@atscript/db-memory";
|
|
@@ -468,7 +468,7 @@ const ACTION_GATE_PRIORITY = TInterceptorPriority.AFTER_GUARD;
|
|
|
468
468
|
* controller's `prepareRequest` before the handler. No `prepareRequest` →
|
|
469
469
|
* returns without awaiting.
|
|
470
470
|
*/
|
|
471
|
-
const actionPrepareInterceptor = defineBeforeInterceptor(() => current().get(dbActionPreparedSlot), ACTION_GATE_PRIORITY);
|
|
471
|
+
const actionPrepareInterceptor = defineBeforeInterceptor(() => current$1().get(dbActionPreparedSlot), ACTION_GATE_PRIORITY);
|
|
472
472
|
//#endregion
|
|
473
473
|
//#region src/actions/controller-access.ts
|
|
474
474
|
const boundTableKey = key("atscript_db_action_bound_table");
|
|
@@ -593,30 +593,83 @@ function identityOf(row, fields, read) {
|
|
|
593
593
|
return id;
|
|
594
594
|
}
|
|
595
595
|
/**
|
|
596
|
+
* Sorted field lists per raw key order — every id of one shape sorts its keys
|
|
597
|
+
* once per call, not once per id.
|
|
598
|
+
*/
|
|
599
|
+
var IdShapes = class {
|
|
600
|
+
/** Raw key order (`Object.keys` joined) → the sorted fields. */
|
|
601
|
+
byRaw = /* @__PURE__ */ new Map();
|
|
602
|
+
/** Sorted signature → the sorted fields (one entry per distinct shape). */
|
|
603
|
+
bySorted = /* @__PURE__ */ new Map();
|
|
604
|
+
fieldsOf(id) {
|
|
605
|
+
const keys = Object.keys(id);
|
|
606
|
+
const raw = `${keys.length}:${keys.join("")}`;
|
|
607
|
+
let sorted = this.byRaw.get(raw);
|
|
608
|
+
if (sorted === void 0) {
|
|
609
|
+
const own = keys.length > 1 ? keys.toSorted() : keys;
|
|
610
|
+
const sig = `${own.length}:${own.join("")}`;
|
|
611
|
+
sorted = this.bySorted.get(sig);
|
|
612
|
+
if (sorted === void 0) this.bySorted.set(sig, sorted = own);
|
|
613
|
+
this.byRaw.set(raw, sorted);
|
|
614
|
+
}
|
|
615
|
+
return sorted;
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
/** A value `$in` matches exactly like the equality `{ field: value }` on every adapter. */
|
|
619
|
+
function isInSafe(value) {
|
|
620
|
+
const type = typeof value;
|
|
621
|
+
return type === "string" || type === "bigint" || type === "boolean" || type === "number" && Number.isFinite(value);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* The filter matching any of `ids` (deduped): `{ field: { $in } }` when they
|
|
625
|
+
* share one single-field shape with scalar values (one equality for a single
|
|
626
|
+
* id), else `{ $or: ids }`.
|
|
627
|
+
*/
|
|
628
|
+
function idsFilter(ids, shapes) {
|
|
629
|
+
if (ids.length > 0 && shapes.bySorted.size === 1) {
|
|
630
|
+
const [fields] = shapes.bySorted.values();
|
|
631
|
+
if (fields.length === 1) {
|
|
632
|
+
const field = fields[0];
|
|
633
|
+
const values = [];
|
|
634
|
+
for (const id of ids) {
|
|
635
|
+
const value = id[field];
|
|
636
|
+
if (!isInSafe(value)) return { $or: ids };
|
|
637
|
+
values.push(value);
|
|
638
|
+
}
|
|
639
|
+
return values.length === 1 ? { [field]: values[0] } : { [field]: { $in: values } };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return { $or: ids };
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
596
645
|
* The rows `ids` address that also match `scope` (none = every row),
|
|
597
646
|
* aligned with `ids` — `undefined` where nothing matched. One `findMany`
|
|
598
|
-
* over the deduped ids (`{
|
|
599
|
-
* every id field; ids of
|
|
600
|
-
* The action row loader and
|
|
647
|
+
* over the deduped ids (`{ field: { $in } }` for single-field ids, else
|
|
648
|
+
* `{ $or: ids }`; AND `scope`) selecting `select` plus every id field; ids of
|
|
649
|
+
* different identification shapes may be mixed. The action row loader and
|
|
650
|
+
* `$actions` scope check share it.
|
|
601
651
|
*/
|
|
602
652
|
async function findRowsByIds(source, ids, scope, select) {
|
|
603
653
|
if (ids.length === 0) return [];
|
|
604
654
|
const fields = new Set(select);
|
|
605
655
|
const dedupedIds = [];
|
|
606
656
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
657
|
+
const shapes = new IdShapes();
|
|
658
|
+
const idKeys = [];
|
|
607
659
|
for (const id of ids) {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
const key = idKey(id, sortedFields);
|
|
660
|
+
const key = idKey(id, shapes.fieldsOf(id));
|
|
661
|
+
idKeys.push(key);
|
|
611
662
|
if (key !== void 0 && !seenKeys.has(key)) {
|
|
612
663
|
seenKeys.add(key);
|
|
613
664
|
dedupedIds.push(id);
|
|
614
665
|
}
|
|
615
666
|
}
|
|
616
|
-
|
|
617
|
-
|
|
667
|
+
for (const sortedFields of shapes.bySorted.values()) for (const f of sortedFields) fields.add(f);
|
|
668
|
+
const match = idsFilter(dedupedIds, shapes);
|
|
669
|
+
return alignByKeys(await source.findMany({
|
|
670
|
+
filter: scope ? { $and: [match, scope] } : match,
|
|
618
671
|
controls: { $select: [...fields] }
|
|
619
|
-
}),
|
|
672
|
+
}), idKeys, shapes);
|
|
620
673
|
}
|
|
621
674
|
/**
|
|
622
675
|
* `rows` aligned with `ids` — per id, the first row with its key values
|
|
@@ -624,16 +677,12 @@ async function findRowsByIds(source, ids, scope, select) {
|
|
|
624
677
|
* identification shapes may be mixed.
|
|
625
678
|
*/
|
|
626
679
|
function alignRowsToIds(rows, ids) {
|
|
627
|
-
const shapes =
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const sig = sortedFields.join("");
|
|
632
|
-
if (!shapes.has(sig)) shapes.set(sig, sortedFields);
|
|
633
|
-
idKeys.push(idKey(id, sortedFields));
|
|
634
|
-
}
|
|
680
|
+
const shapes = new IdShapes();
|
|
681
|
+
return alignByKeys(rows, ids.map((id) => idKey(id, shapes.fieldsOf(id))), shapes);
|
|
682
|
+
}
|
|
683
|
+
function alignByKeys(rows, idKeys, shapes) {
|
|
635
684
|
const rowByKey = /* @__PURE__ */ new Map();
|
|
636
|
-
for (const row of rows) for (const sortedFields of shapes.values()) {
|
|
685
|
+
for (const row of rows) for (const sortedFields of shapes.bySorted.values()) {
|
|
637
686
|
const key = idKey(row, sortedFields);
|
|
638
687
|
if (key !== void 0 && !rowByKey.has(key)) rowByKey.set(key, row);
|
|
639
688
|
}
|
|
@@ -660,11 +709,43 @@ function exceedsFields(row, fields) {
|
|
|
660
709
|
for (const key in row) if (!fields.has(key)) return true;
|
|
661
710
|
return false;
|
|
662
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* `row` without its own `keys` — a NEW object (same key order) when it has
|
|
714
|
+
* any of them, else `row` itself. Rows are rebuilt rather than `delete`d
|
|
715
|
+
* from: a deleted key turns an object into a slow dictionary-mode one for
|
|
716
|
+
* everything after (decoration, serialization).
|
|
717
|
+
*/
|
|
718
|
+
function omitKeys(row, keys) {
|
|
719
|
+
let has = false;
|
|
720
|
+
for (const key of keys) if (Object.hasOwn(row, key)) {
|
|
721
|
+
has = true;
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
if (!has) return row;
|
|
725
|
+
const out = {};
|
|
726
|
+
for (const key of Object.keys(row)) {
|
|
727
|
+
if (keys.has(key)) continue;
|
|
728
|
+
if (key === "__proto__") Object.defineProperty(out, key, {
|
|
729
|
+
value: row[key],
|
|
730
|
+
enumerable: true,
|
|
731
|
+
writable: true,
|
|
732
|
+
configurable: true
|
|
733
|
+
});
|
|
734
|
+
else out[key] = row[key];
|
|
735
|
+
}
|
|
736
|
+
return out;
|
|
737
|
+
}
|
|
738
|
+
/** `fields` split at the dots — pass to {@link projectRow} when projecting many rows. */
|
|
739
|
+
function splitPaths(fields) {
|
|
740
|
+
const out = [];
|
|
741
|
+
for (const path of fields) out.push(path.split("."));
|
|
742
|
+
return out;
|
|
743
|
+
}
|
|
663
744
|
/** `row` narrowed to `fields` (dot paths copied into fresh nested objects). */
|
|
664
745
|
function projectRow$1(row, fields) {
|
|
665
746
|
const out = {};
|
|
666
747
|
for (const path of fields) {
|
|
667
|
-
const parts = path.split(".");
|
|
748
|
+
const parts = typeof path === "string" ? path.split(".") : path;
|
|
668
749
|
let src = row;
|
|
669
750
|
for (const p of parts) src = src?.[p];
|
|
670
751
|
if (src === void 0) continue;
|
|
@@ -1844,13 +1925,13 @@ function copyClientFilter(filter, freeze = false) {
|
|
|
1844
1925
|
} catch {
|
|
1845
1926
|
throw badRequest("", "Malformed query: the filter cannot be copied");
|
|
1846
1927
|
}
|
|
1847
|
-
if (freeze) deepFreeze(copy);
|
|
1928
|
+
if (freeze) deepFreeze$1(copy);
|
|
1848
1929
|
return copy;
|
|
1849
1930
|
}
|
|
1850
|
-
function deepFreeze(v) {
|
|
1931
|
+
function deepFreeze$1(v) {
|
|
1851
1932
|
if (v === null || typeof v !== "object" || Object.isFrozen(v)) return;
|
|
1852
1933
|
Object.freeze(v);
|
|
1853
|
-
for (const child of Object.values(v)) deepFreeze(child);
|
|
1934
|
+
for (const child of Object.values(v)) deepFreeze$1(child);
|
|
1854
1935
|
}
|
|
1855
1936
|
/** The predicate operator maps of a filter's own level (through `$and` / `$or` / `$not`). */
|
|
1856
1937
|
function collectPredicateMaps(filter, out) {
|
|
@@ -2107,6 +2188,129 @@ function walk(node, def, shallow) {
|
|
|
2107
2188
|
}
|
|
2108
2189
|
}
|
|
2109
2190
|
//#endregion
|
|
2191
|
+
//#region src/meta/meta-cache.ts
|
|
2192
|
+
/**
|
|
2193
|
+
* `/meta` identity memo (since 0.1.151). Each layer that turns the cached
|
|
2194
|
+
* envelope into a request's `/meta` object memoizes its output per INPUT
|
|
2195
|
+
* identity and per the request-dependent answers it reads (the key), so
|
|
2196
|
+
* equal inputs give the same object across requests. The final object is
|
|
2197
|
+
* then registered with wooks' `prerenderJson` (JSON serialized once, weak
|
|
2198
|
+
* ETag from the bytes).
|
|
2199
|
+
*
|
|
2200
|
+
* Soundness: a memoized object is never mutated (the `applyMetaOverlay`
|
|
2201
|
+
* contract), so its identity implies its bytes; every key covers every
|
|
2202
|
+
* request-dependent input its layer reads. Inputs that are not themselves
|
|
2203
|
+
* stable (an overlay that builds a fresh object per request) are not memoized —
|
|
2204
|
+
* their output is computed per request, as before, and gets no ETag.
|
|
2205
|
+
*/
|
|
2206
|
+
/** Objects whose identity implies their bytes: cached envelopes and memo outputs derived from one. */
|
|
2207
|
+
const stable = /* @__PURE__ */ new WeakSet();
|
|
2208
|
+
/**
|
|
2209
|
+
* Most memoized variants derived from one root envelope, over all chained
|
|
2210
|
+
* layers (planner → index visibility → delegated actions → own path); past it
|
|
2211
|
+
* a variant is computed per request (and is not stable).
|
|
2212
|
+
*/
|
|
2213
|
+
const MAX_VARIANTS_PER_ROOT = 128;
|
|
2214
|
+
/** The root envelope each memo output was derived from (for the budget). */
|
|
2215
|
+
const rootOf = /* @__PURE__ */ new WeakMap();
|
|
2216
|
+
/** Variants memoized so far per root envelope. */
|
|
2217
|
+
const rootBudget = /* @__PURE__ */ new WeakMap();
|
|
2218
|
+
/** Marks `obj` as identity-stable (never mutated after this point). Returns it. */
|
|
2219
|
+
function markStableMeta(obj) {
|
|
2220
|
+
stable.add(obj);
|
|
2221
|
+
return obj;
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Marks a `/meta` payload your override memoizes (an `applyMetaOverlay`
|
|
2225
|
+
* variant, a `metaForm` projection) as reusable: it is deep-frozen and, when
|
|
2226
|
+
* served again, sent from its serialized-once JSON with an `ETag`. Return the
|
|
2227
|
+
* same object for the same inputs only — its bytes are fixed from now on.
|
|
2228
|
+
*
|
|
2229
|
+
* @since 0.1.151
|
|
2230
|
+
*/
|
|
2231
|
+
function stableMeta(obj) {
|
|
2232
|
+
deepFreeze(obj);
|
|
2233
|
+
return markStableMeta(obj);
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* `obj` may be memoized on and registered for prerendering: a cached envelope,
|
|
2237
|
+
* a memo output, or an object passed to {@link stableMeta}. (Frozenness alone
|
|
2238
|
+
* is not enough: `Object.freeze` is shallow.)
|
|
2239
|
+
*/
|
|
2240
|
+
function isStableMeta(obj) {
|
|
2241
|
+
return stable.has(obj);
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* The memoized variant of `input` for `key` in `variants` — `compute()` once
|
|
2245
|
+
* per (input, key). Keys of different layers must not collide (prefix them).
|
|
2246
|
+
* An unstable input is not memoized (computed every time); past
|
|
2247
|
+
* {@link MAX_VARIANTS_PER_ROOT} variants of one envelope a variant is computed
|
|
2248
|
+
* per request (and is not stable).
|
|
2249
|
+
*/
|
|
2250
|
+
function metaVariant(variants, input, key, compute) {
|
|
2251
|
+
if (!isStableMeta(input)) return compute();
|
|
2252
|
+
let byKey = variants.get(input);
|
|
2253
|
+
const hit = byKey?.get(key);
|
|
2254
|
+
if (hit) return hit;
|
|
2255
|
+
const out = compute();
|
|
2256
|
+
const root = rootOf.get(input) ?? input;
|
|
2257
|
+
const used = rootBudget.get(root) ?? 0;
|
|
2258
|
+
if (used < MAX_VARIANTS_PER_ROOT) {
|
|
2259
|
+
rootBudget.set(root, used + 1);
|
|
2260
|
+
if (out !== input) {
|
|
2261
|
+
markStableMeta(out);
|
|
2262
|
+
rootOf.set(out, root);
|
|
2263
|
+
}
|
|
2264
|
+
if (!byKey) variants.set(input, byKey = /* @__PURE__ */ new Map());
|
|
2265
|
+
byKey.set(key, out);
|
|
2266
|
+
}
|
|
2267
|
+
return out;
|
|
2268
|
+
}
|
|
2269
|
+
/** Stable small ids for objects (memo keys over object identities). */
|
|
2270
|
+
const ids = /* @__PURE__ */ new WeakMap();
|
|
2271
|
+
let nextId = 1;
|
|
2272
|
+
/** A per-process id of `obj`'s identity. */
|
|
2273
|
+
function identityId(obj) {
|
|
2274
|
+
let id = ids.get(obj);
|
|
2275
|
+
if (id === void 0) ids.set(obj, id = nextId++);
|
|
2276
|
+
return id;
|
|
2277
|
+
}
|
|
2278
|
+
/** Bit string of `flags` (memo key). */
|
|
2279
|
+
function bits(flags) {
|
|
2280
|
+
let out = "";
|
|
2281
|
+
for (const flag of flags) out += flag ? "1" : "0";
|
|
2282
|
+
return out;
|
|
2283
|
+
}
|
|
2284
|
+
let freezeServed;
|
|
2285
|
+
const deepFrozen = /* @__PURE__ */ new WeakSet();
|
|
2286
|
+
/**
|
|
2287
|
+
* Shared objects are deep-frozen when `NODE_ENV` is `test` or
|
|
2288
|
+
* `development`, or `ATSCRIPT_DB_FREEZE_META=1` — an in-place mutation (which
|
|
2289
|
+
* would keep serving the old bytes) then throws instead.
|
|
2290
|
+
*/
|
|
2291
|
+
function shouldFreeze() {
|
|
2292
|
+
if (freezeServed === void 0) {
|
|
2293
|
+
const env = typeof process === "undefined" ? void 0 : process.env;
|
|
2294
|
+
freezeServed = env?.NODE_ENV === "test" || env?.NODE_ENV === "development" || env?.ATSCRIPT_DB_FREEZE_META === "1";
|
|
2295
|
+
}
|
|
2296
|
+
return freezeServed;
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Deep-freezes `obj` in dev / test (see {@link shouldFreeze}); a no-op
|
|
2300
|
+
* otherwise. For objects shared across requests (served `/meta` payloads,
|
|
2301
|
+
* memoized projections), which must never be mutated.
|
|
2302
|
+
*/
|
|
2303
|
+
function freezeShared(obj) {
|
|
2304
|
+
if (!shouldFreeze()) return;
|
|
2305
|
+
deepFreeze(obj);
|
|
2306
|
+
}
|
|
2307
|
+
function deepFreeze(value) {
|
|
2308
|
+
if (value === null || typeof value !== "object" || deepFrozen.has(value)) return;
|
|
2309
|
+
deepFrozen.add(value);
|
|
2310
|
+
Object.freeze(value);
|
|
2311
|
+
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
2312
|
+
}
|
|
2313
|
+
//#endregion
|
|
2110
2314
|
//#region src/http-path-design-time.ts
|
|
2111
2315
|
const KEY$1 = "db.http.path";
|
|
2112
2316
|
/** The `@db.http.path` value a model carried before moost-db first wrote its published path. */
|
|
@@ -2515,7 +2719,11 @@ function __decorateParam(paramIndex, decorator) {
|
|
|
2515
2719
|
}
|
|
2516
2720
|
//#endregion
|
|
2517
2721
|
//#region src/as-readable.controller.ts
|
|
2518
|
-
|
|
2722
|
+
var _AsReadableController;
|
|
2723
|
+
const DEFAULT_META_CACHE_CONTROL = "private, no-cache";
|
|
2724
|
+
const DEFAULT_META_VARY = ["Authorization", "Cookie"];
|
|
2725
|
+
const readHttpResponse = () => current().get(httpKind.keys.response);
|
|
2726
|
+
let AsReadableController = _AsReadableController = class AsReadableController {
|
|
2519
2727
|
/** The Atscript interface this controller serves. */
|
|
2520
2728
|
boundType;
|
|
2521
2729
|
/** Short human-readable name for logging (usually the table/source name). */
|
|
@@ -2537,6 +2745,12 @@ let AsReadableController = class AsReadableController {
|
|
|
2537
2745
|
/** Cached serialized form schemas keyed by `FormType.name` — populated lazily by {@link metaForm}. */
|
|
2538
2746
|
_formSchemas = /* @__PURE__ */ new Map();
|
|
2539
2747
|
/**
|
|
2748
|
+
* @internal Memoized `/meta` variants of this controller (own mount path,
|
|
2749
|
+
* index visibility, decorations, delegated actions) — per input object, then
|
|
2750
|
+
* per layer-prefixed key; see `meta/meta-cache.ts`.
|
|
2751
|
+
*/
|
|
2752
|
+
_metaVariants = /* @__PURE__ */ new WeakMap();
|
|
2753
|
+
/**
|
|
2540
2754
|
* @param opts.canonical Multi-mount models only: marks (`true`) or excludes
|
|
2541
2755
|
* (`false`) this controller as the model's published value-help route
|
|
2542
2756
|
* (since 0.1.150). Decorator-bound controllers use the decorator option.
|
|
@@ -2821,10 +3035,56 @@ let AsReadableController = class AsReadableController {
|
|
|
2821
3035
|
* static envelope is cached (rebuilt when {@link metaCacheKey} changes);
|
|
2822
3036
|
* {@link applyMetaOverlay} runs per request so subclasses can prune the
|
|
2823
3037
|
* response by principal.
|
|
3038
|
+
*
|
|
3039
|
+
* Since 0.1.151 the response is HTTP-cacheable ({@link metaHttpCaching}):
|
|
3040
|
+
* `Cache-Control: private, no-cache`, `Vary: Authorization, Cookie` and —
|
|
3041
|
+
* when the payload is the same object as for an earlier request with the
|
|
3042
|
+
* same inputs — a weak `ETag` computed from the serialized bytes, so a
|
|
3043
|
+
* matching `If-None-Match` answers `304 Not Modified`. The JSON is then
|
|
3044
|
+
* serialized once, not per request.
|
|
2824
3045
|
*/
|
|
2825
3046
|
async meta() {
|
|
2826
3047
|
await this.parseRequest("meta");
|
|
2827
|
-
return this._withOwnHttpPath(await this.resolveMeta());
|
|
3048
|
+
return this._serveMeta(this._withOwnHttpPath(await this.resolveMeta()), this.meta === _AsReadableController.prototype.meta);
|
|
3049
|
+
}
|
|
3050
|
+
/**
|
|
3051
|
+
* HTTP caching of `GET /meta` and `GET /meta/form/:name` (since 0.1.151).
|
|
3052
|
+
* Default `{}`: `Cache-Control: private, no-cache` (kept when the response
|
|
3053
|
+
* already carries one), `Vary: Authorization, Cookie` (merged into an
|
|
3054
|
+
* existing `Vary`), and a weak `ETag` from the response bytes with `304`
|
|
3055
|
+
* on a matching `If-None-Match`. Return `false` to send none of it (the
|
|
3056
|
+
* pre-0.1.151 response), or override the header values — e.g.
|
|
3057
|
+
* `{ cacheControl: "no-store" }` for deployments where no browser may keep
|
|
3058
|
+
* a copy.
|
|
3059
|
+
*
|
|
3060
|
+
* The `ETag` is a hash of the final bytes, computed after every overlay:
|
|
3061
|
+
* a request whose principal sees a different `/meta` gets a different tag
|
|
3062
|
+
* (a `200`), so a `304` only ever confirms the bytes the client already
|
|
3063
|
+
* holds. A refused request (`prepareRequest` throwing) is answered with its
|
|
3064
|
+
* error, never `304`.
|
|
3065
|
+
*/
|
|
3066
|
+
metaHttpCaching() {
|
|
3067
|
+
return {};
|
|
3068
|
+
}
|
|
3069
|
+
/**
|
|
3070
|
+
* Applies {@link metaHttpCaching} to the current response and — when `body`
|
|
3071
|
+
* is identity-stable (a cached / memoized object) — registers it for
|
|
3072
|
+
* prerendering with an ETag (deep-frozen in dev / test).
|
|
3073
|
+
*/
|
|
3074
|
+
_serveMeta(body, prerender = true) {
|
|
3075
|
+
const caching = this.metaHttpCaching();
|
|
3076
|
+
if (caching === false) return isStableMeta(body) ? { ...body } : body;
|
|
3077
|
+
let response;
|
|
3078
|
+
try {
|
|
3079
|
+
response = readHttpResponse();
|
|
3080
|
+
} catch {}
|
|
3081
|
+
if (!response) return body;
|
|
3082
|
+
applyMetaHeaders(response, caching);
|
|
3083
|
+
if (prerender && isStableMeta(body)) {
|
|
3084
|
+
freezeShared(body);
|
|
3085
|
+
prerenderJson(body, { etag: true });
|
|
3086
|
+
}
|
|
3087
|
+
return body;
|
|
2828
3088
|
}
|
|
2829
3089
|
/**
|
|
2830
3090
|
* The root of a controller's own `/meta` carries its own mount as
|
|
@@ -2834,7 +3094,6 @@ let AsReadableController = class AsReadableController {
|
|
|
2834
3094
|
* cached envelope.
|
|
2835
3095
|
*/
|
|
2836
3096
|
_withOwnHttpPath(meta) {
|
|
2837
|
-
const KEY = "db.http.path";
|
|
2838
3097
|
let prefix;
|
|
2839
3098
|
try {
|
|
2840
3099
|
prefix = useControllerContext().getPrefix();
|
|
@@ -2844,6 +3103,11 @@ let AsReadableController = class AsReadableController {
|
|
|
2844
3103
|
if (prefix === void 0 || prefix === null) return meta;
|
|
2845
3104
|
const own = normalizeHttpPath(prefix);
|
|
2846
3105
|
if (isParametricPath(own)) return meta;
|
|
3106
|
+
return this.getSerializeOptions === _AsReadableController.prototype.getSerializeOptions ? metaVariant(this._metaVariants, meta, `o:${own}`, () => this._ownHttpPathOf(meta, own)) : this._ownHttpPathOf(meta, own);
|
|
3107
|
+
}
|
|
3108
|
+
/** {@link _withOwnHttpPath} for the mount `own`. */
|
|
3109
|
+
_ownHttpPathOf(meta, own) {
|
|
3110
|
+
const KEY = "db.http.path";
|
|
2847
3111
|
const options = this.getSerializeOptions();
|
|
2848
3112
|
if (options.ignoreAnnotations?.includes(KEY)) return meta;
|
|
2849
3113
|
let key = KEY;
|
|
@@ -2888,7 +3152,7 @@ let AsReadableController = class AsReadableController {
|
|
|
2888
3152
|
this.enterScope(scope);
|
|
2889
3153
|
const key = this.metaCacheKey();
|
|
2890
3154
|
if (!this._metaResponse || key !== this._metaResponseKey) {
|
|
2891
|
-
this._metaResponse = this.buildScoped(scope, () => this.buildMetaResponse());
|
|
3155
|
+
this._metaResponse = markStableMeta(this.buildScoped(scope, () => this.buildMetaResponse()));
|
|
2892
3156
|
this._metaResponseKey = key;
|
|
2893
3157
|
}
|
|
2894
3158
|
return this.applyMetaOverlay(this._metaResponse);
|
|
@@ -2921,10 +3185,10 @@ let AsReadableController = class AsReadableController {
|
|
|
2921
3185
|
this.enterScope(scope);
|
|
2922
3186
|
let cached = this._formSchemas.get(name);
|
|
2923
3187
|
if (!cached) {
|
|
2924
|
-
cached = this.buildScoped(scope, () => this.serializeForMeta(formType));
|
|
3188
|
+
cached = markStableMeta(this.buildScoped(scope, () => this.serializeForMeta(formType)));
|
|
2925
3189
|
this._formSchemas.set(name, cached);
|
|
2926
3190
|
}
|
|
2927
|
-
return cached;
|
|
3191
|
+
return this._serveMeta(cached);
|
|
2928
3192
|
}
|
|
2929
3193
|
/**
|
|
2930
3194
|
* Per-request gate for `GET /meta/form/:name`: return `false` to refuse the
|
|
@@ -2981,6 +3245,12 @@ let AsReadableController = class AsReadableController {
|
|
|
2981
3245
|
* composables). The cached envelope MUST NOT be mutated — see
|
|
2982
3246
|
* `docs/http/permissions.md` for the full contract, including the
|
|
2983
3247
|
* "discoverability only" caveat.
|
|
3248
|
+
*
|
|
3249
|
+
* `/meta`'s serialize-once memo and `ETag` (since 0.1.151) need a stable
|
|
3250
|
+
* object: return the envelope itself when nothing changes, or memoize your
|
|
3251
|
+
* outputs and return them through `stableMeta()` (deep-freezes and marks
|
|
3252
|
+
* them) — never mutate one afterwards. A fresh object per request is
|
|
3253
|
+
* correct, just served without an `ETag`.
|
|
2984
3254
|
*/
|
|
2985
3255
|
applyMetaOverlay(meta) {
|
|
2986
3256
|
return meta;
|
|
@@ -2999,13 +3269,36 @@ __decorate([
|
|
|
2999
3269
|
__decorateMetadata("design:paramtypes", [String]),
|
|
3000
3270
|
__decorateMetadata("design:returntype", Promise)
|
|
3001
3271
|
], AsReadableController.prototype, "metaForm", null);
|
|
3002
|
-
AsReadableController = __decorate([UseValidationErrorTransform(), __decorateMetadata("design:paramtypes", [
|
|
3272
|
+
AsReadableController = _AsReadableController = __decorate([UseValidationErrorTransform(), __decorateMetadata("design:paramtypes", [
|
|
3003
3273
|
Object,
|
|
3004
3274
|
String,
|
|
3005
3275
|
typeof Moost === "undefined" ? Object : Moost,
|
|
3006
3276
|
Object,
|
|
3007
3277
|
Object
|
|
3008
3278
|
])], AsReadableController);
|
|
3279
|
+
/** Sets `/meta`'s `Cache-Control` (unless present) and merges its `Vary` tokens. */
|
|
3280
|
+
function applyMetaHeaders(response, caching) {
|
|
3281
|
+
const headers = response.headers();
|
|
3282
|
+
let cacheControlSet = false;
|
|
3283
|
+
let varyKey = "vary";
|
|
3284
|
+
for (const key in headers) {
|
|
3285
|
+
const lower = key.toLowerCase();
|
|
3286
|
+
if (lower === "cache-control") cacheControlSet = true;
|
|
3287
|
+
else if (lower === "vary") varyKey = key;
|
|
3288
|
+
}
|
|
3289
|
+
if (!cacheControlSet) response.setHeader("cache-control", caching.cacheControl ?? DEFAULT_META_CACHE_CONTROL);
|
|
3290
|
+
const wanted = caching.vary ?? DEFAULT_META_VARY;
|
|
3291
|
+
if (wanted.length === 0) return;
|
|
3292
|
+
const existing = headers[varyKey];
|
|
3293
|
+
const tokens = (Array.isArray(existing) ? existing.join(",") : existing ?? "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
3294
|
+
if (tokens.includes("*")) return;
|
|
3295
|
+
const seen = new Set(tokens.map((t) => t.toLowerCase()));
|
|
3296
|
+
for (const token of wanted) if (!seen.has(token.toLowerCase())) {
|
|
3297
|
+
seen.add(token.toLowerCase());
|
|
3298
|
+
tokens.push(token);
|
|
3299
|
+
}
|
|
3300
|
+
response.setHeader(varyKey, tokens.join(", "));
|
|
3301
|
+
}
|
|
3009
3302
|
//#endregion
|
|
3010
3303
|
//#region src/actions/verdict.ts
|
|
3011
3304
|
/** Assert that a `disabled` predicate returned an array of the expected length; throws HTTP 500 otherwise. */
|
|
@@ -3068,7 +3361,8 @@ function computeStripFields(candidates, resolvedProjection) {
|
|
|
3068
3361
|
/**
|
|
3069
3362
|
* Sets `$actions` on every row (plus `$disabledReasons` on rows where a
|
|
3070
3363
|
* predicate returned a reason string) and strips the columns fetched only for an
|
|
3071
|
-
* action's `requiredFields` — IN PLACE
|
|
3364
|
+
* action's `requiredFields` — IN PLACE in `rows` (a stripped row is replaced
|
|
3365
|
+
* by a rebuilt copy, see `omitKeys`); returns the same array, typed as
|
|
3072
3366
|
* augmented.
|
|
3073
3367
|
*/
|
|
3074
3368
|
function augmentRowsWithActions(args) {
|
|
@@ -3078,7 +3372,11 @@ function augmentRowsWithActions(args) {
|
|
|
3078
3372
|
const verdicts = candidates.map((c) => {
|
|
3079
3373
|
if (!c.disabledFn) return void 0;
|
|
3080
3374
|
const fields = gateFields?.(c.envelope);
|
|
3081
|
-
|
|
3375
|
+
let input = rows;
|
|
3376
|
+
if (fields && rows.some((row) => exceedsFields(row, fields))) {
|
|
3377
|
+
const paths = splitPaths(fields);
|
|
3378
|
+
input = rows.map((row) => projectRow$1(row, paths));
|
|
3379
|
+
}
|
|
3082
3380
|
return judgeRows(c.envelope.info.name, c.disabledFn, input);
|
|
3083
3381
|
});
|
|
3084
3382
|
const masks = outOfScope && candidates.map((c) => outOfScope.get(c.envelope.info.name));
|
|
@@ -3102,7 +3400,11 @@ function augmentRowsWithActions(args) {
|
|
|
3102
3400
|
}
|
|
3103
3401
|
if (resolvedProjection !== null) {
|
|
3104
3402
|
const stripFields = computeStripFields(candidates, resolvedProjection);
|
|
3105
|
-
if (stripFields !== null) for (
|
|
3403
|
+
if (stripFields !== null) for (let i = 0; i < rows.length; i++) {
|
|
3404
|
+
const row = rows[i];
|
|
3405
|
+
if (isPlainObject(row)) rows[i] = omitKeys(row, stripFields);
|
|
3406
|
+
else for (const key of stripFields) delete row[key];
|
|
3407
|
+
}
|
|
3106
3408
|
}
|
|
3107
3409
|
return rows;
|
|
3108
3410
|
}
|
|
@@ -3126,7 +3428,7 @@ const dbActionArgsResolvedKey = key("atscript_db_action_args_resolved");
|
|
|
3126
3428
|
const markHandlerStartPipe = definePipeFn((value, metas, level) => {
|
|
3127
3429
|
const total = metas.methodMeta?.params?.length ?? 0;
|
|
3128
3430
|
if (level !== "PARAM" || !metas.methodMeta?.atscript_db_action || metas.index === void 0) return value;
|
|
3129
|
-
const ctx = current();
|
|
3431
|
+
const ctx = current$1();
|
|
3130
3432
|
let seen = ctx.hasOwn(dbActionArgsResolvedKey) ? ctx.getOwn(dbActionArgsResolvedKey) : void 0;
|
|
3131
3433
|
if (!seen) {
|
|
3132
3434
|
seen = /* @__PURE__ */ new Set();
|
|
@@ -3140,7 +3442,7 @@ const markHandlerStartPipe = definePipeFn((value, metas, level) => {
|
|
|
3140
3442
|
* `true` once the current action event's handler started (every `'rows'`
|
|
3141
3443
|
* action has an argument, so its start is always observed).
|
|
3142
3444
|
*/
|
|
3143
|
-
function actionHandlerStarted(ctx = current()) {
|
|
3445
|
+
function actionHandlerStarted(ctx = current$1()) {
|
|
3144
3446
|
return ctx.hasOwn(dbActionHandlerStartedKey);
|
|
3145
3447
|
}
|
|
3146
3448
|
//#endregion
|
|
@@ -3472,7 +3774,7 @@ const dbActionAbortedKey = key("atscript_db_action_aborted");
|
|
|
3472
3774
|
*
|
|
3473
3775
|
* @since 0.1.147
|
|
3474
3776
|
*/
|
|
3475
|
-
function useDbActionTarget(ctx = current()) {
|
|
3777
|
+
function useDbActionTarget(ctx = current$1()) {
|
|
3476
3778
|
if (!ctx.has(dbActionTargetKey)) throw new Error("[moost-db actions] useDbActionTarget(): no action target in this event — it is available in the handler of a 'rows' level @DbAction");
|
|
3477
3779
|
return ctx.get(dbActionTargetKey);
|
|
3478
3780
|
}
|
|
@@ -4183,7 +4485,7 @@ function buildDecorationIndex(controller, meta, source) {
|
|
|
4183
4485
|
requires,
|
|
4184
4486
|
visibleOn,
|
|
4185
4487
|
leavesOf,
|
|
4186
|
-
memo: {
|
|
4488
|
+
memo: {}
|
|
4187
4489
|
};
|
|
4188
4490
|
}
|
|
4189
4491
|
//#endregion
|
|
@@ -4374,45 +4676,46 @@ var DecorationPlanner = class {
|
|
|
4374
4676
|
* `fields[key]` entry (from its virtual capability-index entry); the others
|
|
4375
4677
|
* are pruned, and `decorations` is dropped when none is left. Runs after
|
|
4376
4678
|
* `applyMetaOverlay` — an overlay never sees the decoration `fields`
|
|
4377
|
-
* entries. Memoized per input
|
|
4679
|
+
* entries. Memoized per input and per the request's decoration visibility.
|
|
4378
4680
|
*/
|
|
4379
4681
|
meta(meta) {
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
const
|
|
4383
|
-
const hit = memoize ? memo.meta.get(meta) : void 0;
|
|
4384
|
-
if (hit) return hit;
|
|
4682
|
+
const decorations = meta.decorations;
|
|
4683
|
+
if (!decorations) return meta;
|
|
4684
|
+
const { keySet } = this.index;
|
|
4385
4685
|
const capabilities = this.host.capabilities();
|
|
4386
|
-
const props =
|
|
4387
|
-
const
|
|
4388
|
-
const
|
|
4389
|
-
|
|
4390
|
-
const
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
out = rest;
|
|
4403
|
-
} else out = {
|
|
4404
|
-
...meta,
|
|
4405
|
-
fields,
|
|
4406
|
-
decorations: {
|
|
4407
|
-
...meta.decorations,
|
|
4408
|
-
type: {
|
|
4409
|
-
...meta.decorations.type,
|
|
4410
|
-
props: kept
|
|
4411
|
-
}
|
|
4686
|
+
const props = decorations.type.props ?? {};
|
|
4687
|
+
const entries = Object.entries(props);
|
|
4688
|
+
const visible = entries.map(([key]) => keySet.has(key) && this.visible(key));
|
|
4689
|
+
return metaVariant(this.host.metaVariants, meta, `p:${identityId(capabilities)}:${bits(visible)}`, () => {
|
|
4690
|
+
const kept = {};
|
|
4691
|
+
const fields = { ...meta.fields };
|
|
4692
|
+
for (let i = 0; i < entries.length; i++) {
|
|
4693
|
+
const [key, prop] = entries[i];
|
|
4694
|
+
const cap = visible[i] ? capabilities.decorationCap(key) : void 0;
|
|
4695
|
+
if (!cap) continue;
|
|
4696
|
+
kept[key] = prop;
|
|
4697
|
+
fields[key] = {
|
|
4698
|
+
sortable: cap.sortable,
|
|
4699
|
+
filterable: cap.filterable,
|
|
4700
|
+
decoration: true
|
|
4701
|
+
};
|
|
4412
4702
|
}
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4703
|
+
if (Object.keys(kept).length === 0) {
|
|
4704
|
+
const { decorations: _dropped, ...rest } = meta;
|
|
4705
|
+
return rest;
|
|
4706
|
+
}
|
|
4707
|
+
return {
|
|
4708
|
+
...meta,
|
|
4709
|
+
fields,
|
|
4710
|
+
decorations: {
|
|
4711
|
+
...decorations,
|
|
4712
|
+
type: {
|
|
4713
|
+
...decorations.type,
|
|
4714
|
+
props: kept
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
};
|
|
4718
|
+
});
|
|
4416
4719
|
}
|
|
4417
4720
|
/** The declared interface serialized for `/meta.decorations`, once per class and app scope. */
|
|
4418
4721
|
serialized(scope, serialize) {
|
|
@@ -4424,18 +4727,31 @@ var DecorationPlanner = class {
|
|
|
4424
4727
|
return memo.serialized;
|
|
4425
4728
|
}
|
|
4426
4729
|
};
|
|
4427
|
-
/**
|
|
4730
|
+
/**
|
|
4731
|
+
* Removes what a read must not carry — the unserved declared keys and the
|
|
4732
|
+
* hook-only paths — from `rows`, in place in the array: a row losing a
|
|
4733
|
+
* top-level key is replaced by a rebuilt copy (`omitKeys`); nested paths are
|
|
4734
|
+
* deleted inside it.
|
|
4735
|
+
*/
|
|
4428
4736
|
function stripDecorations(rows, read) {
|
|
4429
4737
|
const { dropKeys, dropPaths, keepPaths, selectedPaths } = read;
|
|
4430
4738
|
if (dropKeys.length === 0 && dropPaths.length === 0) return;
|
|
4431
4739
|
const owned = (prefix) => selectedPaths !== void 0 && selectedPaths.some((sel) => prefix.every((part, i) => sel[i] === part));
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4740
|
+
const topLevel = new Set(dropKeys);
|
|
4741
|
+
const nested = [];
|
|
4742
|
+
dropPaths.forEach((parts, i) => {
|
|
4743
|
+
if (parts.length === 1 && (keepPaths[i] ?? []).length === 0) topLevel.add(parts[0]);
|
|
4744
|
+
else nested.push(i);
|
|
4745
|
+
});
|
|
4746
|
+
for (let r = 0; r < rows.length; r++) {
|
|
4747
|
+
let row = rows[r];
|
|
4748
|
+
if (isPlainObject(row)) row = rows[r] = omitKeys(row, topLevel);
|
|
4749
|
+
else for (const key of dropKeys) delete row[key];
|
|
4750
|
+
for (const i of nested) {
|
|
4435
4751
|
const keep = keepPaths[i] ?? [];
|
|
4436
|
-
if (keep.length === 0) deleteDescending(row,
|
|
4437
|
-
else pruneExcept(row,
|
|
4438
|
-
}
|
|
4752
|
+
if (keep.length === 0) deleteDescending(row, dropPaths[i], 0, selectedPaths !== void 0, owned);
|
|
4753
|
+
else pruneExcept(row, dropPaths[i], 0, keep);
|
|
4754
|
+
}
|
|
4439
4755
|
}
|
|
4440
4756
|
}
|
|
4441
4757
|
/** An object without keys, or an array (of any nesting) holding nothing but such values (what a strip left of a parent). */
|
|
@@ -4558,6 +4874,15 @@ function routedController(ctx) {
|
|
|
4558
4874
|
return controllerOf(c);
|
|
4559
4875
|
}
|
|
4560
4876
|
}
|
|
4877
|
+
/**
|
|
4878
|
+
* `collectQueryPaths`' aggregate flag for a parsed query: `true` when its
|
|
4879
|
+
* `$select` carries computed entries (aggregates, calendar buckets), else
|
|
4880
|
+
* `undefined` (aggregate mode iff `$groupBy`).
|
|
4881
|
+
*/
|
|
4882
|
+
function computedMode(parsed) {
|
|
4883
|
+
const select = parsed.controls?.$select;
|
|
4884
|
+
return Array.isArray(select) && select.some((item) => typeof item !== "string") || void 0;
|
|
4885
|
+
}
|
|
4561
4886
|
/** The 400 of a filter / sort on a `@db.writeOnly` field. */
|
|
4562
4887
|
function writeOnlyError(path, op) {
|
|
4563
4888
|
const verdict = writeOnlyVerdict(path, op);
|
|
@@ -4598,13 +4923,34 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
4598
4923
|
* would keep advertising (and gating) the pre-sync answer (since 0.1.132).
|
|
4599
4924
|
*/
|
|
4600
4925
|
get capabilities() {
|
|
4926
|
+
const readable = this.readable;
|
|
4927
|
+
const geo = readable.isGeoSearchable();
|
|
4928
|
+
const units = readable.calendarBucketUnits();
|
|
4929
|
+
const fns = readable.aggregateFns();
|
|
4930
|
+
const expr = readable.supportsAggregateExpressions();
|
|
4601
4931
|
const current = this._capabilities;
|
|
4602
|
-
|
|
4603
|
-
|
|
4932
|
+
const seen = this._capabilityInputs;
|
|
4933
|
+
if (current && seen && seen.geo === geo && seen.units === units && seen.unitsSize === units.size && seen.fns === fns && seen.fnsSize === fns.size && seen.expr === expr) return current;
|
|
4934
|
+
const inputs = {
|
|
4935
|
+
geo,
|
|
4936
|
+
units,
|
|
4937
|
+
unitsSize: units.size,
|
|
4938
|
+
fns,
|
|
4939
|
+
fnsSize: fns.size,
|
|
4940
|
+
expr
|
|
4941
|
+
};
|
|
4942
|
+
if (current && current.signature === FieldCapabilityIndex.adapterSignature(readable)) {
|
|
4943
|
+
this._capabilityInputs = inputs;
|
|
4944
|
+
return current;
|
|
4945
|
+
}
|
|
4946
|
+
const index = new FieldCapabilityIndex(readable, this._writeOnlySet, this._decorations?.visibleOn);
|
|
4604
4947
|
this._capabilities = index;
|
|
4948
|
+
this._capabilityInputs = inputs;
|
|
4605
4949
|
return index;
|
|
4606
4950
|
}
|
|
4607
4951
|
_capabilities;
|
|
4952
|
+
/** The adapter answers {@link capabilities} last confirmed its index against (identity fast path). */
|
|
4953
|
+
_capabilityInputs;
|
|
4608
4954
|
/** The client relational-predicate gate (since 0.1.147), built on first use — see {@link _relationGate}. */
|
|
4609
4955
|
_relGate;
|
|
4610
4956
|
/**
|
|
@@ -4637,6 +4983,12 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
4637
4983
|
_derivedSource;
|
|
4638
4984
|
/** `@db.writeOnly` paths of `$with` target readables, collected once per target. */
|
|
4639
4985
|
_targetWriteOnly = /* @__PURE__ */ new WeakMap();
|
|
4986
|
+
/** The forced exclusion `$select` per sealed set (see {@link _sealSelect}). */
|
|
4987
|
+
_exclusionBySealed = /* @__PURE__ */ new WeakMap();
|
|
4988
|
+
/** The objects of {@link _exclusionBySealed}. */
|
|
4989
|
+
_forcedExclusions = /* @__PURE__ */ new WeakSet();
|
|
4990
|
+
/** {@link widenPreferredIdProjection} of each forced exclusion. */
|
|
4991
|
+
_widenedExclusions = /* @__PURE__ */ new WeakMap();
|
|
4640
4992
|
/** Own leaf paths per readable (bound + `$with` targets), see {@link _leavesOf}. */
|
|
4641
4993
|
_targetLeaves = /* @__PURE__ */ new WeakMap();
|
|
4642
4994
|
_indexFieldPathsCache;
|
|
@@ -4722,7 +5074,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
4722
5074
|
this._idOpts = scoped ? { isFieldVisible: isVisible } : void 0;
|
|
4723
5075
|
this._planner = this._decorations && new DecorationPlanner(this._decorations, {
|
|
4724
5076
|
isVisible,
|
|
4725
|
-
|
|
5077
|
+
metaVariants: this._metaVariants,
|
|
4726
5078
|
preferred: this._preferredIdSet,
|
|
4727
5079
|
capabilities: () => this.capabilities,
|
|
4728
5080
|
firstVisibleField: () => this._invertibleFields.find((path) => isVisible(path))
|
|
@@ -4868,8 +5220,8 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
4868
5220
|
checkCapabilities(parsed) {
|
|
4869
5221
|
const capabilities = this.capabilities;
|
|
4870
5222
|
const isVisible = this.fieldVisibility.isVisible;
|
|
4871
|
-
const
|
|
4872
|
-
const refs = collectQueryPaths$1(parsed,
|
|
5223
|
+
const shared = this._sharedRefs;
|
|
5224
|
+
const refs = shared?.parsed === parsed ? shared.refs : collectQueryPaths$1(parsed, computedMode(parsed));
|
|
4873
5225
|
if (refs.unsupportedOperator !== void 0) return badRequest(refs.unsupportedOperator, unsupportedOperatorMessage(refs.unsupportedOperator));
|
|
4874
5226
|
const relState = { nodes: 0 };
|
|
4875
5227
|
for (const ref of refs.filter) {
|
|
@@ -4896,6 +5248,21 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
4896
5248
|
if (having) return badRequest(having.path, having.message);
|
|
4897
5249
|
return this.checkGates(parsed) ?? this._checkIndexGate(parsed.controls ?? {});
|
|
4898
5250
|
}
|
|
5251
|
+
/** {@link checkCapabilities} reusing `refs` (the walk it makes) when the method is not overridden. */
|
|
5252
|
+
_checkCapabilitiesWith(parsed, refs) {
|
|
5253
|
+
if (this.checkCapabilities !== _AsDbReadableController.prototype.checkCapabilities) return this.checkCapabilities(parsed);
|
|
5254
|
+
this._sharedRefs = {
|
|
5255
|
+
parsed,
|
|
5256
|
+
refs
|
|
5257
|
+
};
|
|
5258
|
+
try {
|
|
5259
|
+
return this.checkCapabilities(parsed);
|
|
5260
|
+
} finally {
|
|
5261
|
+
this._sharedRefs = void 0;
|
|
5262
|
+
}
|
|
5263
|
+
}
|
|
5264
|
+
/** The walk of the `parsed` in flight, handed to {@link checkCapabilities} (sync, cleared after). */
|
|
5265
|
+
_sharedRefs;
|
|
4899
5266
|
/**
|
|
4900
5267
|
* The core's shared normalizer of `$select` computed entries
|
|
4901
5268
|
* (`normalizeComputedSelect`) as a 400 with the core's wording and `path`
|
|
@@ -5234,15 +5601,22 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
5234
5601
|
_applyIndexVisibility(meta) {
|
|
5235
5602
|
if (!this._indexGateActive) return meta;
|
|
5236
5603
|
const entries = this.indexFieldPaths();
|
|
5604
|
+
const indexVisible = entries.map((e) => this._indexVisible(e));
|
|
5605
|
+
const isSearchable = this.readable.isSearchable();
|
|
5606
|
+
const fallbackVisible = isSearchable ? [] : this._searchFallbackFields.map((f) => this.fieldVisibility.isVisible(f));
|
|
5607
|
+
return metaVariant(this._metaVariants, meta, `i:${bits(indexVisible)}:${isSearchable ? 1 : 0}:${bits(fallbackVisible)}`, () => this._narrowIndexVisibility(meta, entries, indexVisible, isSearchable, fallbackVisible));
|
|
5608
|
+
}
|
|
5609
|
+
/** {@link _applyIndexVisibility} for the given answers. */
|
|
5610
|
+
_narrowIndexVisibility(meta, entries, indexVisible, isSearchable, fallbackVisible) {
|
|
5237
5611
|
const visibleDefault = (type) => {
|
|
5238
|
-
const
|
|
5239
|
-
return
|
|
5612
|
+
const at = entries.findIndex((e) => e.type === type && e.isDefault);
|
|
5613
|
+
return at >= 0 && indexVisible[at];
|
|
5240
5614
|
};
|
|
5241
5615
|
const has = (type) => entries.some((e) => e.type === type);
|
|
5242
|
-
const anyVisible = (type) => entries.some((e) => e.type === type &&
|
|
5243
|
-
const hidden = new Set(entries.filter((e) => e.type !== "geo" && !
|
|
5244
|
-
const searchable =
|
|
5245
|
-
const textUsable = searchable ||
|
|
5616
|
+
const anyVisible = (type) => entries.some((e, i) => e.type === type && indexVisible[i]);
|
|
5617
|
+
const hidden = new Set(entries.filter((e, i) => e.type !== "geo" && !indexVisible[i]).map((e) => e.name));
|
|
5618
|
+
const searchable = isSearchable ? visibleDefault("text") : fallbackVisible.some(Boolean);
|
|
5619
|
+
const textUsable = searchable || isSearchable && anyVisible("text");
|
|
5246
5620
|
const vectorUsable = anyVisible("vector");
|
|
5247
5621
|
const unusable = /* @__PURE__ */ new Set();
|
|
5248
5622
|
if (has("text") && !anyVisible("text")) unusable.add("index");
|
|
@@ -5476,7 +5850,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
5476
5850
|
const transformed = await this.transformProjection(plan ? plan.select : controls.$select);
|
|
5477
5851
|
const sealed = this._sealControls(controls, transformed);
|
|
5478
5852
|
const finish = () => {
|
|
5479
|
-
const select = this.
|
|
5853
|
+
const select = this._widenSealed(sealed.$select);
|
|
5480
5854
|
if (select instanceof HttpError) return select;
|
|
5481
5855
|
let kept;
|
|
5482
5856
|
const keptPaths = () => kept === void 0 ? kept = this._resolveProjectionForAugmenter(select) : kept;
|
|
@@ -5491,6 +5865,23 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
5491
5865
|
finish
|
|
5492
5866
|
};
|
|
5493
5867
|
}
|
|
5868
|
+
/**
|
|
5869
|
+
* {@link widenPreferredIdProjection} of a sealed `$select` — memoized for the
|
|
5870
|
+
* forced exclusion of a read without `$select` (one object per sealed set,
|
|
5871
|
+
* see {@link _sealSelect}): its widening depends on the controller alone.
|
|
5872
|
+
*/
|
|
5873
|
+
_widenSealed(select) {
|
|
5874
|
+
if (select === void 0 || !this._forcedExclusions.has(select)) return this.widenPreferredIdProjection(select);
|
|
5875
|
+
let widened = this._widenedExclusions.get(select);
|
|
5876
|
+
if (widened === void 0) {
|
|
5877
|
+
const out = this.widenPreferredIdProjection(select);
|
|
5878
|
+
if (out instanceof HttpError) return out;
|
|
5879
|
+
if (out !== void 0 && out !== select) freezeShared(out);
|
|
5880
|
+
this._widenedExclusions.set(select, out);
|
|
5881
|
+
widened = out;
|
|
5882
|
+
}
|
|
5883
|
+
return widened;
|
|
5884
|
+
}
|
|
5494
5885
|
widenPreferredIdProjection(projection) {
|
|
5495
5886
|
const widened = this.widenQuantityRefProjection(projection);
|
|
5496
5887
|
if (widened instanceof HttpError) return widened;
|
|
@@ -5818,7 +6209,15 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
5818
6209
|
const plain = (q) => this.readable.findMany(q);
|
|
5819
6210
|
return byIds(plain, ids, void 0, fields);
|
|
5820
6211
|
}
|
|
5821
|
-
|
|
6212
|
+
const pathsByShape = /* @__PURE__ */ new Map();
|
|
6213
|
+
const pathsOf = (id) => {
|
|
6214
|
+
const keys = Object.keys(id);
|
|
6215
|
+
const shape = keys.join("");
|
|
6216
|
+
let paths = pathsByShape.get(shape);
|
|
6217
|
+
if (!paths) pathsByShape.set(shape, paths = splitPaths(new Set([...fields, ...keys])));
|
|
6218
|
+
return paths;
|
|
6219
|
+
};
|
|
6220
|
+
return Promise.resolve(alignRowsToIds(rows, ids).map((row, i) => row ? projectRow$1(row, pathsOf(ids[i])) : void 0));
|
|
5822
6221
|
}
|
|
5823
6222
|
};
|
|
5824
6223
|
}
|
|
@@ -5968,7 +6367,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
5968
6367
|
async resolveQuery(q, opts = {}) {
|
|
5969
6368
|
let caller;
|
|
5970
6369
|
try {
|
|
5971
|
-
caller = current();
|
|
6370
|
+
caller = current$1();
|
|
5972
6371
|
} catch {
|
|
5973
6372
|
throw new Error("[moost-db] resolveQuery must be awaited inside an event handler");
|
|
5974
6373
|
}
|
|
@@ -6001,7 +6400,8 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
6001
6400
|
sortGate: ids.length > 0 ? void 0 : order,
|
|
6002
6401
|
routeParams: sameRoute ? void 0 : {}
|
|
6003
6402
|
});
|
|
6004
|
-
|
|
6403
|
+
const paths = splitPaths(sealedSelect);
|
|
6404
|
+
return rows.map((row) => projectRow$1(row, paths));
|
|
6005
6405
|
}
|
|
6006
6406
|
/**
|
|
6007
6407
|
* Runs `fn` as a READ of this controller (since 0.1.147): in a child of
|
|
@@ -6013,7 +6413,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
6013
6413
|
*/
|
|
6014
6414
|
_asRead(controls, filter, fn, routeParams) {
|
|
6015
6415
|
return runAsController(this, "query", async () => {
|
|
6016
|
-
if (routeParams) current().set(routeParamsKey, routeParams);
|
|
6416
|
+
if (routeParams) current$1().set(routeParamsKey, routeParams);
|
|
6017
6417
|
if (typeof this.prepareRequest === "function") await this.prepareRequest(readRequestContext("query", controls, filter));
|
|
6018
6418
|
return fn();
|
|
6019
6419
|
});
|
|
@@ -6045,8 +6445,18 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
6045
6445
|
_sealSelect(select, writeOnly, readable) {
|
|
6046
6446
|
if (writeOnly.size === 0) return select;
|
|
6047
6447
|
const exclusion = () => {
|
|
6048
|
-
const
|
|
6049
|
-
|
|
6448
|
+
const shared = writeOnly === this._writeOnlySet || writeOnly === this._targetWriteOnly.get(readable);
|
|
6449
|
+
let out = shared ? this._exclusionBySealed.get(writeOnly) : void 0;
|
|
6450
|
+
if (out === void 0) {
|
|
6451
|
+
const built = {};
|
|
6452
|
+
for (const f of writeOnly) built[f] = 0;
|
|
6453
|
+
out = built;
|
|
6454
|
+
if (shared) {
|
|
6455
|
+
freezeShared(out);
|
|
6456
|
+
this._exclusionBySealed.set(writeOnly, out);
|
|
6457
|
+
this._forcedExclusions.add(out);
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6050
6460
|
return out;
|
|
6051
6461
|
};
|
|
6052
6462
|
if (select === void 0) return exclusion();
|
|
@@ -6318,10 +6728,11 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
6318
6728
|
const [overlaid, delegated] = await Promise.all([own, this._hasDelegations ? this._delegatedInfos() : []]);
|
|
6319
6729
|
const meta = this._planner ? this._planner.meta(overlaid) : overlaid;
|
|
6320
6730
|
const visible = this._applyIndexVisibility(meta);
|
|
6321
|
-
|
|
6731
|
+
if (delegated.length === 0) return visible;
|
|
6732
|
+
return metaVariant(this._metaVariants, visible, `d:${delegated.map((info) => identityId(info)).join(",")}`, () => ({
|
|
6322
6733
|
...visible,
|
|
6323
6734
|
actions: [...visible.actions, ...delegated]
|
|
6324
|
-
}
|
|
6735
|
+
}));
|
|
6325
6736
|
})();
|
|
6326
6737
|
}
|
|
6327
6738
|
/**
|
|
@@ -6587,8 +6998,10 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
6587
6998
|
if (groupBy?.length && controls.$vector !== void 0) return new HttpError(400, "Cannot combine $vector and $groupBy in the same query");
|
|
6588
6999
|
const error = this.validateParsed(parsed, "query");
|
|
6589
7000
|
if (error) return error;
|
|
7001
|
+
let gateRefs;
|
|
6590
7002
|
if (groupBy?.length && this._writeOnlySet.size > 0) {
|
|
6591
|
-
|
|
7003
|
+
gateRefs = collectQueryPaths$1(parsed, computedMode(parsed));
|
|
7004
|
+
const refs = gateRefs.aggregateMode ? gateRefs : collectQueryPaths$1(parsed, true);
|
|
6592
7005
|
const sealed = [
|
|
6593
7006
|
...refs.groupBy,
|
|
6594
7007
|
...refs.aggregate,
|
|
@@ -6598,7 +7011,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
6598
7011
|
].find((path) => this._writeOnlySet.has(path) && this.fieldVisibility.isVisible(path));
|
|
6599
7012
|
if (sealed) return new HttpError(400, `Field "${sealed}" is @db.writeOnly and cannot be aggregated`);
|
|
6600
7013
|
}
|
|
6601
|
-
const gateError = this.checkCapabilities(parsed);
|
|
7014
|
+
const gateError = gateRefs ? this._checkCapabilitiesWith(parsed, gateRefs) : this.checkCapabilities(parsed);
|
|
6602
7015
|
if (gateError) return gateError;
|
|
6603
7016
|
const clientFilter = await this._relationOverlay(parsed);
|
|
6604
7017
|
if (groupBy?.length) {
|
|
@@ -6790,14 +7203,15 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
6790
7203
|
};
|
|
6791
7204
|
const item = await this.returnOne(this._findRow(resolvedId, overlay, readControls));
|
|
6792
7205
|
if (item instanceof HttpError) return item;
|
|
6793
|
-
const
|
|
7206
|
+
const rows = [item];
|
|
7207
|
+
const pending = this._finishRows(rows, prep, {
|
|
6794
7208
|
endpoint: "one",
|
|
6795
7209
|
projection: projected.select,
|
|
6796
7210
|
controls,
|
|
6797
7211
|
decorations: projected.read?.served ?? NO_DECORATIONS
|
|
6798
7212
|
}, projected.read);
|
|
6799
7213
|
if (pending) await pending;
|
|
6800
|
-
return
|
|
7214
|
+
return rows[0];
|
|
6801
7215
|
}
|
|
6802
7216
|
/**
|
|
6803
7217
|
* **GET /meta/actions/:id** — the row-level actions the caller may run on
|
|
@@ -6916,7 +7330,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
6916
7330
|
throw new HttpError(404, `Unknown action "${name}"`);
|
|
6917
7331
|
}
|
|
6918
7332
|
const limits = delegation.queryTargets.get(name);
|
|
6919
|
-
const env = await useBody(current()).parseBody() ?? {};
|
|
7333
|
+
const env = await useBody(current$1()).parseBody() ?? {};
|
|
6920
7334
|
if (typeof env !== "object" || Array.isArray(env)) throw targetInvalid(name, "Action body must be an object of shape { query, input? }");
|
|
6921
7335
|
if (env.ids !== void 0) throw targetInvalid(name, "This route takes a `query` — post `ids` to the action's own route");
|
|
6922
7336
|
if (env.query === void 0) throw targetInvalid(name, "`query` is required");
|
|
@@ -7066,7 +7480,9 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
7066
7480
|
for (const row of rows) if (row) present.push(row);
|
|
7067
7481
|
const verdicts = envelopes.map((e, i) => {
|
|
7068
7482
|
const disabled = getCandidate(e)?.disabledFn;
|
|
7069
|
-
|
|
7483
|
+
if (!disabled || present.length === 0) return void 0;
|
|
7484
|
+
const paths = splitPaths(fieldsOf[i]);
|
|
7485
|
+
return judgeRows(e.info.name, disabled, present.map((row) => projectRow$1(row, paths)));
|
|
7070
7486
|
});
|
|
7071
7487
|
let p = 0;
|
|
7072
7488
|
return rows.map((row, r) => {
|
|
@@ -7874,22 +8290,29 @@ let AsJsonValueHelpController = class AsJsonValueHelpController extends AsValueH
|
|
|
7874
8290
|
}
|
|
7875
8291
|
async query(controls) {
|
|
7876
8292
|
let rows = this.rows;
|
|
7877
|
-
if (controls.filter && Object.keys(controls.filter).length > 0) {
|
|
7878
|
-
const predicate = buildMemoryPredicate(controls.filter);
|
|
7879
|
-
rows = rows.filter((row) => predicate(row));
|
|
7880
|
-
}
|
|
7881
8293
|
const search = controls.controls.$search;
|
|
7882
8294
|
if (search) {
|
|
7883
8295
|
const needle = search.toLowerCase();
|
|
7884
|
-
const
|
|
7885
|
-
rows = rows.filter((row) => {
|
|
7886
|
-
for (const field of
|
|
8296
|
+
const columns = this.searchableFields.filter((field) => this.hasField(field)).map((field) => [field, this._lowercased(field)]);
|
|
8297
|
+
rows = rows.filter((row, i) => {
|
|
8298
|
+
for (const [field, column] of columns) {
|
|
7887
8299
|
const v = row[field];
|
|
7888
|
-
if (typeof v
|
|
8300
|
+
if (typeof v !== "string") continue;
|
|
8301
|
+
let lower = column.lower[i];
|
|
8302
|
+
if (lower === void 0 || column.src[i] !== v) {
|
|
8303
|
+
lower = v.toLowerCase();
|
|
8304
|
+
column.src[i] = v;
|
|
8305
|
+
column.lower[i] = lower;
|
|
8306
|
+
}
|
|
8307
|
+
if (lower.includes(needle)) return true;
|
|
7889
8308
|
}
|
|
7890
8309
|
return false;
|
|
7891
8310
|
});
|
|
7892
8311
|
}
|
|
8312
|
+
if (controls.filter && Object.keys(controls.filter).length > 0) {
|
|
8313
|
+
const predicate = buildMemoryPredicate(controls.filter);
|
|
8314
|
+
rows = rows.filter((row) => predicate(row));
|
|
8315
|
+
}
|
|
7893
8316
|
const sort = this.normalizeSort(controls.controls.$sort);
|
|
7894
8317
|
if (sort) rows = sortRows(rows, sort);
|
|
7895
8318
|
const total = rows.length;
|
|
@@ -7902,6 +8325,25 @@ let AsJsonValueHelpController = class AsJsonValueHelpController extends AsValueH
|
|
|
7902
8325
|
count: total
|
|
7903
8326
|
};
|
|
7904
8327
|
}
|
|
8328
|
+
/**
|
|
8329
|
+
* `field`'s lowercased values by position in {@link rows} (the `$search`
|
|
8330
|
+
* haystack). An entry is used only while the row still holds the value it
|
|
8331
|
+
* was lowercased from (`src`), so rows edited in place stay correct.
|
|
8332
|
+
*/
|
|
8333
|
+
_lowercased(field) {
|
|
8334
|
+
let haystack = this._haystack;
|
|
8335
|
+
if (haystack?.rows !== this.rows) haystack = this._haystack = {
|
|
8336
|
+
rows: this.rows,
|
|
8337
|
+
byField: /* @__PURE__ */ new Map()
|
|
8338
|
+
};
|
|
8339
|
+
let column = haystack.byField.get(field);
|
|
8340
|
+
if (!column) haystack.byField.set(field, column = {
|
|
8341
|
+
src: [],
|
|
8342
|
+
lower: []
|
|
8343
|
+
});
|
|
8344
|
+
return column;
|
|
8345
|
+
}
|
|
8346
|
+
_haystack;
|
|
7905
8347
|
async getOne(id) {
|
|
7906
8348
|
return this._pkIndex?.get(String(id)) ?? null;
|
|
7907
8349
|
}
|
|
@@ -8052,7 +8494,7 @@ var ActionDisabledError = class extends HttpError {
|
|
|
8052
8494
|
//#endregion
|
|
8053
8495
|
//#region src/actions/gate-interceptor.ts
|
|
8054
8496
|
function injectBoundTable(fallback) {
|
|
8055
|
-
const ctx = current();
|
|
8497
|
+
const ctx = current$1();
|
|
8056
8498
|
if (ctx.has(boundTableKey)) return;
|
|
8057
8499
|
const t = controllerTable(ctx) ?? fallback;
|
|
8058
8500
|
if (t != null) ctx.set(boundTableKey, t);
|
|
@@ -8076,7 +8518,7 @@ async function queryTargetReply(ctx, reply) {
|
|
|
8076
8518
|
function buildGateInterceptor(opts) {
|
|
8077
8519
|
const { action, level, disabled, onDisabledRows, table } = opts;
|
|
8078
8520
|
return defineBeforeInterceptor(async (reply) => {
|
|
8079
|
-
const ctx = current();
|
|
8521
|
+
const ctx = current$1();
|
|
8080
8522
|
await awaitActionPrepared(ctx);
|
|
8081
8523
|
injectBoundTable(table);
|
|
8082
8524
|
await ctx.get(dbActionOverlaySlot);
|
|
@@ -8177,7 +8619,7 @@ async function needsScopeLoad(ctx, level) {
|
|
|
8177
8619
|
function buildThinInterceptor(opts) {
|
|
8178
8620
|
const { table, scope } = opts;
|
|
8179
8621
|
return defineBeforeInterceptor(async (reply) => {
|
|
8180
|
-
const ctx = current();
|
|
8622
|
+
const ctx = current$1();
|
|
8181
8623
|
await awaitActionPrepared(ctx);
|
|
8182
8624
|
injectBoundTable(table);
|
|
8183
8625
|
if (!scope) return;
|
|
@@ -8206,7 +8648,7 @@ function buildTargetInterceptor(opts) {
|
|
|
8206
8648
|
const { action, disabled, table } = opts;
|
|
8207
8649
|
return defineInterceptor({
|
|
8208
8650
|
async before(reply) {
|
|
8209
|
-
const ctx = current();
|
|
8651
|
+
const ctx = current$1();
|
|
8210
8652
|
await awaitActionPrepared(ctx);
|
|
8211
8653
|
injectBoundTable(table);
|
|
8212
8654
|
const { target, dryRun } = await setStreamedTarget(ctx, action, disabled);
|
|
@@ -8214,7 +8656,7 @@ function buildTargetInterceptor(opts) {
|
|
|
8214
8656
|
else if (target.kind === "query" && target.matched === 0) reply(target.summary());
|
|
8215
8657
|
},
|
|
8216
8658
|
error(error, reply) {
|
|
8217
|
-
const summary = abortStreamedTarget(current(), error);
|
|
8659
|
+
const summary = abortStreamedTarget(current$1(), error);
|
|
8218
8660
|
if (summary) reply(summary);
|
|
8219
8661
|
}
|
|
8220
8662
|
}, ACTION_GATE_PRIORITY);
|
|
@@ -8308,7 +8750,7 @@ function DbActionDefault() {
|
|
|
8308
8750
|
*/
|
|
8309
8751
|
function createIdParamDecorator(kind) {
|
|
8310
8752
|
const resolverName = kind === "id" ? "dbActionId" : "dbActionIds";
|
|
8311
|
-
const resolver = kind === "id" ? async () => current().get(dbActionIdSlot) : async () => current().get(dbActionIdsSlot);
|
|
8753
|
+
const resolver = kind === "id" ? async () => current$1().get(dbActionIdSlot) : async () => current$1().get(dbActionIdsSlot);
|
|
8312
8754
|
return ApplyDecorators(getAtscriptDbMate().decorate("atscript_db_action_param", kind), Resolve(resolver, resolverName));
|
|
8313
8755
|
}
|
|
8314
8756
|
//#endregion
|
|
@@ -8364,7 +8806,7 @@ function DbActionIDs() {
|
|
|
8364
8806
|
//#endregion
|
|
8365
8807
|
//#region src/actions/db-action-row.decorator.ts
|
|
8366
8808
|
function createRowParamDecorator(metaKey, slot, resolverName) {
|
|
8367
|
-
return ApplyDecorators(getAtscriptDbMate().decorate(metaKey, true), Resolve(async () => current().get(slot), resolverName));
|
|
8809
|
+
return ApplyDecorators(getAtscriptDbMate().decorate(metaKey, true), Resolve(async () => current$1().get(slot), resolverName));
|
|
8368
8810
|
}
|
|
8369
8811
|
/**
|
|
8370
8812
|
* Parameter decorator that injects the row whose identifier was supplied in
|
|
@@ -8517,7 +8959,7 @@ function InputForm(formType, validatorOpts) {
|
|
|
8517
8959
|
atscript_type: resolved
|
|
8518
8960
|
};
|
|
8519
8961
|
}), Resolve(async () => {
|
|
8520
|
-
const input = await current().get(dbActionInputSlot) ?? {};
|
|
8962
|
+
const input = await current$1().get(dbActionInputSlot) ?? {};
|
|
8521
8963
|
if (resolved) resolved.validator(validatorOpts).validate(input);
|
|
8522
8964
|
return input;
|
|
8523
8965
|
}, "dbActionInputForm"));
|
|
@@ -8690,4 +9132,4 @@ const VALUE_HELP_CRUD_HANDLERS = Object.freeze({
|
|
|
8690
9132
|
one: ["runGetOne", "runGetOneComposite"]
|
|
8691
9133
|
});
|
|
8692
9134
|
//#endregion
|
|
8693
|
-
export { ActionDisabledError, ActionTargetError, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DB_CRUD_HANDLERS, DEFAULT_DB_SPACE, DbAction, DbActionDefault, DbActionID, DbActionIDs, DbActionRow, DbActionRows, DbActionTarget, DbActions, DbActionsFrom, DbDecorations, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, REL_FILTER_CLIENT_MAX_DEPTH, REL_FILTER_CLIENT_MAX_NODES, ReadableController, TABLE_DEF, TableController, UseValidationErrorTransform, VALUE_HELP_CRUD_HANDLERS, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, closeDbSpaces, collectQueryPaths, dbActionBodySlot, dbActionInputSlot, discoverActions, discoverRowLevelActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, getDbEndpoint, hasActionDelegations, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, unknownRelationError, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, useDbActionTarget, validationErrorTransform };
|
|
9135
|
+
export { ActionDisabledError, ActionTargetError, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DB_CRUD_HANDLERS, DEFAULT_DB_SPACE, DbAction, DbActionDefault, DbActionID, DbActionIDs, DbActionRow, DbActionRows, DbActionTarget, DbActions, DbActionsFrom, DbDecorations, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, REL_FILTER_CLIENT_MAX_DEPTH, REL_FILTER_CLIENT_MAX_NODES, ReadableController, TABLE_DEF, TableController, UseValidationErrorTransform, VALUE_HELP_CRUD_HANDLERS, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, closeDbSpaces, collectQueryPaths, dbActionBodySlot, dbActionInputSlot, discoverActions, discoverRowLevelActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, getDbEndpoint, hasActionDelegations, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, stableMeta, unknownRelationError, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, useDbActionTarget, validationErrorTransform };
|