@atscript/moost-db 0.1.135 → 0.1.137
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +95 -29
- package/dist/index.d.cts +93 -13
- package/dist/index.d.mts +93 -13
- package/dist/index.mjs +96 -30
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -251,7 +251,8 @@ const rowLevelActionsCache = /* @__PURE__ */ new WeakMap();
|
|
|
251
251
|
/**
|
|
252
252
|
* Per-controller registry of form names → compiled `.as` classes, populated
|
|
253
253
|
* during {@link discoverActions} when a method param carries
|
|
254
|
-
* `atscript_db_action_input_form
|
|
254
|
+
* `atscript_db_action_input_form` or a class-level entry passes a type as
|
|
255
|
+
* `inputForm`. Backs `GET /meta/form/:name`.
|
|
255
256
|
*
|
|
256
257
|
* Same name + same type ref across multiple actions is fine (forms can be
|
|
257
258
|
* reused). Same name + *different* type refs is an ambiguity — discovery
|
|
@@ -395,7 +396,7 @@ function collectClassActions(ctor, logger, out, seen) {
|
|
|
395
396
|
logger.warn(`${WARN_PREFIX} duplicate action name "${name}" within controller — dropping the second declaration`);
|
|
396
397
|
continue;
|
|
397
398
|
}
|
|
398
|
-
const built = buildClassEntry(name, entry, logger);
|
|
399
|
+
const built = buildClassEntry(ctor, name, entry, logger);
|
|
399
400
|
if (built) {
|
|
400
401
|
seen.add(name);
|
|
401
402
|
out.push({
|
|
@@ -405,7 +406,7 @@ function collectClassActions(ctor, logger, out, seen) {
|
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
408
|
}
|
|
408
|
-
function buildClassEntry(name, entry, logger) {
|
|
409
|
+
function buildClassEntry(ctor, name, entry, logger) {
|
|
409
410
|
const level = entry.level;
|
|
410
411
|
if (!level) {
|
|
411
412
|
logger.warn(`${WARN_PREFIX} class-level action "${name}" requires a level — dropping. Use @DbTableActions/@DbRowActions/@DbRowsActions or set "level" explicitly.`);
|
|
@@ -443,6 +444,8 @@ function buildClassEntry(name, entry, logger) {
|
|
|
443
444
|
logger.warn(`${WARN_PREFIX} class-level action "${name}" has unknown processor "${String(processor)}" — dropping`);
|
|
444
445
|
return null;
|
|
445
446
|
}
|
|
447
|
+
const form = resolveClassInputForm(name, entry, processor, logger);
|
|
448
|
+
if (form === null) return null;
|
|
446
449
|
const info = {
|
|
447
450
|
name,
|
|
448
451
|
label: entry.label,
|
|
@@ -450,9 +453,45 @@ function buildClassEntry(name, entry, logger) {
|
|
|
450
453
|
processor,
|
|
451
454
|
value
|
|
452
455
|
};
|
|
456
|
+
if (form) {
|
|
457
|
+
if (form.type && !registerFormType(ctor, {
|
|
458
|
+
type: form.type,
|
|
459
|
+
name: form.name
|
|
460
|
+
}, name, logger)) return null;
|
|
461
|
+
info.inputForm = form.name;
|
|
462
|
+
if (form.url !== void 0) info.formUrl = form.url;
|
|
463
|
+
}
|
|
453
464
|
emitInfo(info, entry);
|
|
454
465
|
return info;
|
|
455
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Resolves a class-level entry's `inputForm`. Returns `undefined` when the
|
|
469
|
+
* entry declares none, `null` when it must be dropped (already warned — the
|
|
470
|
+
* runtime check for JS callers and `processor: "navigate"`), else the form to
|
|
471
|
+
* emit: `type` when this controller serves it (`/meta/form/:name`), `url`
|
|
472
|
+
* when it lives elsewhere.
|
|
473
|
+
*/
|
|
474
|
+
function resolveClassInputForm(name, entry, processor, logger) {
|
|
475
|
+
const inputForm = entry.inputForm;
|
|
476
|
+
if (inputForm === void 0) return void 0;
|
|
477
|
+
const drop = (reason) => {
|
|
478
|
+
logger.warn(`${WARN_PREFIX} class-level action "${name}" — ${reason} — dropping`);
|
|
479
|
+
return null;
|
|
480
|
+
};
|
|
481
|
+
if (processor === "navigate") return drop("processor \"navigate\" cannot take an `inputForm`");
|
|
482
|
+
const { name: formName, url } = inputForm ?? {};
|
|
483
|
+
if (typeof formName === "string" && formName !== "") {
|
|
484
|
+
if ((0, _atscript_typescript_utils.isAnnotatedType)(inputForm)) return {
|
|
485
|
+
name: formName,
|
|
486
|
+
type: inputForm
|
|
487
|
+
};
|
|
488
|
+
if (typeof url === "string" && url !== "") return {
|
|
489
|
+
name: formName,
|
|
490
|
+
url
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
return drop("`inputForm` must be a compiled .as interface or `{ name, url }` (non-empty strings)");
|
|
494
|
+
}
|
|
456
495
|
function applyDefaultPerLevel(envelopes, logger) {
|
|
457
496
|
const winners = /* @__PURE__ */ new Map();
|
|
458
497
|
for (const { info } of envelopes) {
|
|
@@ -977,6 +1016,11 @@ function computeStripFields(candidates, resolvedProjection) {
|
|
|
977
1016
|
}
|
|
978
1017
|
return strip;
|
|
979
1018
|
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Sets `$actions` on every row and strips the columns fetched only for an
|
|
1021
|
+
* action's `requiredFields` — IN PLACE; returns the same array, typed as
|
|
1022
|
+
* augmented.
|
|
1023
|
+
*/
|
|
980
1024
|
function augmentRowsWithActions(args) {
|
|
981
1025
|
const { envelopes, rows, resolvedProjection } = args;
|
|
982
1026
|
const candidates = collectCandidates(envelopes);
|
|
@@ -1224,16 +1268,18 @@ var FieldCapabilityIndex = class FieldCapabilityIndex {
|
|
|
1224
1268
|
physicalNames;
|
|
1225
1269
|
/** Calendar-bucket units the adapter groups by, in `BUCKET_UNITS` order (`/meta.bucketUnits`). */
|
|
1226
1270
|
bucketUnits;
|
|
1271
|
+
/** Aggregate functions the adapter renders, in canonical `ALL_AGGREGATE_FNS` order (`/meta.aggregateFns`). */
|
|
1272
|
+
aggregateFns;
|
|
1227
1273
|
/** The adapter-level capabilities this index was built against — see {@link adapterSignature}. */
|
|
1228
1274
|
signature;
|
|
1229
1275
|
/**
|
|
1230
1276
|
* The adapter-level capabilities that can change after construction (geo
|
|
1231
|
-
* support, calendar-bucket units): an index whose
|
|
1232
|
-
* differs from this is stale. Any new adapter-level input
|
|
1233
|
-
* must be added here.
|
|
1277
|
+
* support, calendar-bucket units, aggregate functions): an index whose
|
|
1278
|
+
* {@link signature} differs from this is stale. Any new adapter-level input
|
|
1279
|
+
* the index reads must be added here.
|
|
1234
1280
|
*/
|
|
1235
1281
|
static adapterSignature(source) {
|
|
1236
|
-
return `${source.isGeoSearchable()}|${[...source.calendarBucketUnits()].join(",")}`;
|
|
1282
|
+
return `${source.isGeoSearchable()}|${[...source.calendarBucketUnits()].join(",")}|${[...source.aggregateFns()].join(",")}`;
|
|
1237
1283
|
}
|
|
1238
1284
|
_entries = /* @__PURE__ */ new Map();
|
|
1239
1285
|
/** Nested-object parents (never listed, always selectable) → their listed leaves. */
|
|
@@ -1256,6 +1302,8 @@ var FieldCapabilityIndex = class FieldCapabilityIndex {
|
|
|
1256
1302
|
this.signature = FieldCapabilityIndex.adapterSignature(source);
|
|
1257
1303
|
const units = source.calendarBucketUnits();
|
|
1258
1304
|
this.bucketUnits = _uniqu_core.BUCKET_UNITS.filter((unit) => units.has(unit));
|
|
1305
|
+
const fns = source.aggregateFns();
|
|
1306
|
+
this.aggregateFns = [..._atscript_db.ALL_AGGREGATE_FNS].filter((fn) => fns.has(fn));
|
|
1259
1307
|
const physicalNames = /* @__PURE__ */ new Set();
|
|
1260
1308
|
const jsonValueParents = /* @__PURE__ */ new Set();
|
|
1261
1309
|
for (const fd of source.fieldDescriptors) {
|
|
@@ -1552,6 +1600,8 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
1552
1600
|
_idSources = /* @__PURE__ */ new Map();
|
|
1553
1601
|
_preferredIdSet;
|
|
1554
1602
|
_overlayIsNoOp;
|
|
1603
|
+
/** `true` when a subclass implements {@link decorateRows} (the override switches the hook on). */
|
|
1604
|
+
_decorates;
|
|
1555
1605
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
1556
1606
|
_quantityRefByPath;
|
|
1557
1607
|
/** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
|
|
@@ -1575,6 +1625,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
1575
1625
|
this._quantityRefByPath = this._collectQuantityRefs();
|
|
1576
1626
|
const defaultOverlay = AsReadableController.prototype.applyMetaOverlay;
|
|
1577
1627
|
this._overlayIsNoOp = this.applyMetaOverlay === defaultOverlay;
|
|
1628
|
+
this._decorates = typeof this.decorateRows === "function";
|
|
1578
1629
|
this._idOpts = this.hasField === _AsDbReadableController.prototype.hasField ? void 0 : { isFieldVisible: this._exists };
|
|
1579
1630
|
}
|
|
1580
1631
|
/**
|
|
@@ -1681,7 +1732,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
1681
1732
|
}
|
|
1682
1733
|
/**
|
|
1683
1734
|
* The core's shared normalizer of `$select` computed entries
|
|
1684
|
-
* (`
|
|
1735
|
+
* (`normalizeComputedSelect`) as a 400 with the core's wording and `path`
|
|
1685
1736
|
* (`$select` / `$groupBy`): entry shapes, calendar-bucket unit / zone /
|
|
1686
1737
|
* week start / alias, "grouped queries only", "must also appear in
|
|
1687
1738
|
* $groupBy", alias collisions with this table's fields. Runs once per
|
|
@@ -1693,7 +1744,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
1693
1744
|
checkComputedSelect(controls) {
|
|
1694
1745
|
const capabilities = this.capabilities;
|
|
1695
1746
|
try {
|
|
1696
|
-
(0, _atscript_db.
|
|
1747
|
+
(0, _atscript_db.normalizeComputedSelect)(controls, {
|
|
1697
1748
|
flatMap: this.readable.flatMap,
|
|
1698
1749
|
physicalNames: capabilities.physicalNames,
|
|
1699
1750
|
navFields: capabilities.navFields
|
|
@@ -2050,13 +2101,27 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2050
2101
|
return { kind: "plain" };
|
|
2051
2102
|
}
|
|
2052
2103
|
/**
|
|
2053
|
-
*
|
|
2104
|
+
* Finishes a read's top-level rows in place: `$actions` augmentation (when
|
|
2105
|
+
* the request asked for it — `prep`), then {@link decorateRows} when a
|
|
2106
|
+
* subclass implements it. Returns the hook's result — `undefined`, with no
|
|
2107
|
+
* promise or microtask, when there is no hook or it is synchronous.
|
|
2108
|
+
*/
|
|
2109
|
+
_finishRows(rows, prep, ctx) {
|
|
2110
|
+
if (prep) augmentRowsWithActions({
|
|
2111
|
+
envelopes: prep.envelopes,
|
|
2112
|
+
rows,
|
|
2113
|
+
resolvedProjection: prep.resolvedProjection
|
|
2114
|
+
});
|
|
2115
|
+
return this._decorates ? this.decorateRows(rows, ctx) : void 0;
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Shared `query` / `pages` / `geo` pipeline: prepare actions augmentation + read
|
|
2054
2119
|
* strategy in parallel, pre-widen $select for `requiredFields`, run
|
|
2055
2120
|
* `exec`, and augment `result.data` with `$actions` when the request set
|
|
2056
|
-
* `$actions=true
|
|
2057
|
-
* family (count vs no-count).
|
|
2121
|
+
* `$actions=true`, then run {@link decorateRows}. Caller dispatches the
|
|
2122
|
+
* strategy to its read-method family (count vs no-count).
|
|
2058
2123
|
*/
|
|
2059
|
-
async _runReadWithActions(queryObj, controls, select, exec) {
|
|
2124
|
+
async _runReadWithActions(endpoint, queryObj, controls, select, exec) {
|
|
2060
2125
|
const [prep, strategy] = await Promise.all([this._prepareAugmentation(controls, select), this._resolveReadStrategy(controls)]);
|
|
2061
2126
|
const result = await exec(prep?.widenedSelect ? {
|
|
2062
2127
|
...queryObj,
|
|
@@ -2065,12 +2130,12 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2065
2130
|
$select: prep.widenedSelect
|
|
2066
2131
|
}
|
|
2067
2132
|
} : queryObj, strategy);
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
resolvedProjection: prep.resolvedProjection
|
|
2133
|
+
const pending = this._finishRows(result.data, prep, {
|
|
2134
|
+
endpoint,
|
|
2135
|
+
projection: select,
|
|
2136
|
+
controls
|
|
2073
2137
|
});
|
|
2138
|
+
if (pending) await pending;
|
|
2074
2139
|
return result;
|
|
2075
2140
|
}
|
|
2076
2141
|
/**
|
|
@@ -2142,7 +2207,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2142
2207
|
$threshold: threshold
|
|
2143
2208
|
}
|
|
2144
2209
|
};
|
|
2145
|
-
return (await this._runReadWithActions(queryObj, controls, select, async (q, strategy) => {
|
|
2210
|
+
return (await this._runReadWithActions("query", queryObj, controls, select, async (q, strategy) => {
|
|
2146
2211
|
switch (strategy.kind) {
|
|
2147
2212
|
case "vector": return { data: await (strategy.vectorField ? this.readable.vectorSearch(strategy.vectorField, strategy.vector, q) : this.readable.vectorSearch(strategy.vector, q)) };
|
|
2148
2213
|
case "search": return { data: await this.readable.search(strategy.term, q, strategy.index) };
|
|
@@ -2180,7 +2245,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2180
2245
|
$threshold: threshold
|
|
2181
2246
|
}
|
|
2182
2247
|
};
|
|
2183
|
-
const result = await this._runReadWithActions(query, controls, select, async (q, strategy) => {
|
|
2248
|
+
const result = await this._runReadWithActions("pages", query, controls, select, async (q, strategy) => {
|
|
2184
2249
|
switch (strategy.kind) {
|
|
2185
2250
|
case "vector": return strategy.vectorField ? this.readable.vectorSearchWithCount(strategy.vectorField, strategy.vector, q) : this.readable.vectorSearchWithCount(strategy.vector, q);
|
|
2186
2251
|
case "search": return this.readable.searchWithCount(strategy.term, q, strategy.index);
|
|
@@ -2245,7 +2310,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2245
2310
|
}
|
|
2246
2311
|
};
|
|
2247
2312
|
if (paginated) {
|
|
2248
|
-
const result = await this._runReadWithActions(queryObj, controls, select, async (q) => indexName ? this.readable.geoSearchWithCount(indexName, point, q) : this.readable.geoSearchWithCount(point, q));
|
|
2313
|
+
const result = await this._runReadWithActions("geo", queryObj, controls, select, async (q) => indexName ? this.readable.geoSearchWithCount(indexName, point, q) : this.readable.geoSearchWithCount(point, q));
|
|
2249
2314
|
return {
|
|
2250
2315
|
data: result.data,
|
|
2251
2316
|
page,
|
|
@@ -2254,7 +2319,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2254
2319
|
count: result.count
|
|
2255
2320
|
};
|
|
2256
2321
|
}
|
|
2257
|
-
return (await this._runReadWithActions(queryObj, controls, select, async (q) => ({ data: await (indexName ? this.readable.geoSearch(indexName, point, q) : this.readable.geoSearch(point, q)) }))).data;
|
|
2322
|
+
return (await this._runReadWithActions("geo", queryObj, controls, select, async (q) => ({ data: await (indexName ? this.readable.geoSearch(indexName, point, q) : this.readable.geoSearch(point, q)) }))).data;
|
|
2258
2323
|
}
|
|
2259
2324
|
/** Parses the `$center` control: `"lng,lat"` string (or tuple) → `[number, number]`. */
|
|
2260
2325
|
_parseGeoCenter(raw) {
|
|
@@ -2329,13 +2394,13 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2329
2394
|
}
|
|
2330
2395
|
const item = await this.returnOne(Promise.resolve(row));
|
|
2331
2396
|
if (item instanceof _moostjs_event_http.HttpError) return item;
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
resolvedProjection: prep.resolvedProjection
|
|
2397
|
+
const pending = this._finishRows([item], prep, {
|
|
2398
|
+
endpoint: "one",
|
|
2399
|
+
projection: select,
|
|
2400
|
+
controls: parsedControls
|
|
2337
2401
|
});
|
|
2338
|
-
|
|
2402
|
+
if (pending) await pending;
|
|
2403
|
+
return item;
|
|
2339
2404
|
}
|
|
2340
2405
|
/**
|
|
2341
2406
|
* **GET /meta** — returns table/view metadata for UI.
|
|
@@ -2383,7 +2448,8 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2383
2448
|
actions: this.buildActions(),
|
|
2384
2449
|
crud: this.buildCrud(),
|
|
2385
2450
|
versionColumn: this.readable.versionColumn,
|
|
2386
|
-
...capabilities.bucketUnits.length > 0 && { bucketUnits: [...capabilities.bucketUnits] }
|
|
2451
|
+
...capabilities.bucketUnits.length > 0 && { bucketUnits: [...capabilities.bucketUnits] },
|
|
2452
|
+
aggregateFns: [...capabilities.aggregateFns]
|
|
2387
2453
|
};
|
|
2388
2454
|
}
|
|
2389
2455
|
buildCrud() {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as resolveDbSpace, n as clearDbSpaces, r as provideDbSpace, t as DEFAULT_DB_SPACE } from "./db-space-registry-CWpYwZ4R.cjs";
|
|
2
2
|
import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializedAnnotatedType, TValidatorOptions, Validator } from "@atscript/typescript/utils";
|
|
3
|
-
import { AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TCrudOp, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbFieldMeta, TDbRemoveGuardContext, TDbRemoveGuardContext as TDbRemoveGuardContext$1, TDbWriteAction, TDbWriteAction as TDbWriteAction$1, TDbWriteGuardContext, TDbWriteGuardContext as TDbWriteGuardContext$1, TFilterPredicate, TIdResolveOptions, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
|
|
3
|
+
import { AggregateFn, AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TCrudOp, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbFieldMeta, TDbRemoveGuardContext, TDbRemoveGuardContext as TDbRemoveGuardContext$1, TDbWriteAction, TDbWriteAction as TDbWriteAction$1, TDbWriteGuardContext, TDbWriteGuardContext as TDbWriteGuardContext$1, TFilterPredicate, TIdResolveOptions, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
|
|
4
4
|
import { HttpError } from "@moostjs/event-http";
|
|
5
5
|
import { Mate, Moost, TConsoleBase, TMateParamMeta, TMoostMetadata } from "moost";
|
|
6
6
|
import { parseUrl } from "@uniqu/url";
|
|
@@ -241,7 +241,7 @@ interface TCapabilityVerdict {
|
|
|
241
241
|
message: string;
|
|
242
242
|
}
|
|
243
243
|
/** The readable members the index reads. */
|
|
244
|
-
type TCapabilityReadable = Pick<AtscriptDbReadable, "type" | "fieldDescriptors" | "flatMap" | "navFields" | "relations" | "ignoredFields" | "canFilterField" | "canSortField" | "isGeoSearchable" | "calendarBucketUnits" | "dimensions" | "measures">;
|
|
244
|
+
type TCapabilityReadable = Pick<AtscriptDbReadable, "type" | "fieldDescriptors" | "flatMap" | "navFields" | "relations" | "ignoredFields" | "canFilterField" | "canSortField" | "isGeoSearchable" | "calendarBucketUnits" | "aggregateFns" | "dimensions" | "measures">;
|
|
245
245
|
/**
|
|
246
246
|
* Capability index of one readable.
|
|
247
247
|
*
|
|
@@ -273,15 +273,17 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
|
273
273
|
readonly physicalNames: ReadonlySet<string>;
|
|
274
274
|
/** Calendar-bucket units the adapter groups by, in `BUCKET_UNITS` order (`/meta.bucketUnits`). */
|
|
275
275
|
readonly bucketUnits: readonly BucketUnit[];
|
|
276
|
+
/** Aggregate functions the adapter renders, in canonical `ALL_AGGREGATE_FNS` order (`/meta.aggregateFns`). */
|
|
277
|
+
readonly aggregateFns: readonly AggregateFn[];
|
|
276
278
|
/** The adapter-level capabilities this index was built against — see {@link adapterSignature}. */
|
|
277
279
|
readonly signature: string;
|
|
278
280
|
/**
|
|
279
281
|
* The adapter-level capabilities that can change after construction (geo
|
|
280
|
-
* support, calendar-bucket units): an index whose
|
|
281
|
-
* differs from this is stale. Any new adapter-level input
|
|
282
|
-
* must be added here.
|
|
282
|
+
* support, calendar-bucket units, aggregate functions): an index whose
|
|
283
|
+
* {@link signature} differs from this is stale. Any new adapter-level input
|
|
284
|
+
* the index reads must be added here.
|
|
283
285
|
*/
|
|
284
|
-
static adapterSignature(source: Pick<TCapabilityReadable, "isGeoSearchable" | "calendarBucketUnits">): string;
|
|
286
|
+
static adapterSignature(source: Pick<TCapabilityReadable, "isGeoSearchable" | "calendarBucketUnits" | "aggregateFns">): string;
|
|
285
287
|
private readonly _entries;
|
|
286
288
|
/** Nested-object parents (never listed, always selectable) → their listed leaves. */
|
|
287
289
|
private readonly _objectParents;
|
|
@@ -324,12 +326,34 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
|
324
326
|
}
|
|
325
327
|
//#endregion
|
|
326
328
|
//#region src/as-db-readable.controller.d.ts
|
|
329
|
+
/** Read endpoint a {@link AsDbReadableController.decorateRows} call serves. */
|
|
330
|
+
type TDbDecorateEndpoint = "query" | "pages" | "geo" | "one";
|
|
331
|
+
/**
|
|
332
|
+
* Context passed to {@link AsDbReadableController.decorateRows}.
|
|
333
|
+
*
|
|
334
|
+
* @since 0.1.136
|
|
335
|
+
*/
|
|
336
|
+
interface TDbDecorateContext {
|
|
337
|
+
/** Endpoint that produced the rows. `/one` and `/one/:id` both report `"one"`. */
|
|
338
|
+
endpoint: TDbDecorateEndpoint;
|
|
339
|
+
/**
|
|
340
|
+
* The effective `$select` the endpoint read with — after
|
|
341
|
+
* `transformProjection`, the `@db.writeOnly` seal and preferred-id
|
|
342
|
+
* widening (`undefined` = no projection). Columns added only to feed an
|
|
343
|
+
* action's `requiredFields` are stripped again before the hook runs.
|
|
344
|
+
*/
|
|
345
|
+
projection: UniqueryControls["$select"] | undefined;
|
|
346
|
+
/** The request's parsed controls (`$select`, `$with`, `$actions`, …). Read-only by convention. */
|
|
347
|
+
controls: Record<string, unknown>;
|
|
348
|
+
}
|
|
327
349
|
/**
|
|
328
350
|
* Read-only database controller for Moost that works with any `AtscriptDbReadable`
|
|
329
351
|
* (tables or views). Provides query, pages, getOne, and meta endpoints.
|
|
330
352
|
*
|
|
331
353
|
* For write operations (insert, replace, update, delete), use {@link AsDbController}.
|
|
332
|
-
*
|
|
354
|
+
* Views bind to this same class — `@ViewController(view)` (an alias of
|
|
355
|
+
* `@ReadableController`) or a constructor-passed view; there is no separate
|
|
356
|
+
* view controller.
|
|
333
357
|
*/
|
|
334
358
|
declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsReadableController<T, DataType> {
|
|
335
359
|
/** Reference to the underlying readable (table or view). */
|
|
@@ -370,6 +394,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
370
394
|
private readonly _idSources;
|
|
371
395
|
private readonly _preferredIdSet;
|
|
372
396
|
private readonly _overlayIsNoOp;
|
|
397
|
+
/** `true` when a subclass implements {@link decorateRows} (the override switches the hook on). */
|
|
398
|
+
private readonly _decorates;
|
|
373
399
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
374
400
|
private readonly _quantityRefByPath;
|
|
375
401
|
/** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
|
|
@@ -435,7 +461,7 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
435
461
|
}): HttpError | undefined;
|
|
436
462
|
/**
|
|
437
463
|
* The core's shared normalizer of `$select` computed entries
|
|
438
|
-
* (`
|
|
464
|
+
* (`normalizeComputedSelect`) as a 400 with the core's wording and `path`
|
|
439
465
|
* (`$select` / `$groupBy`): entry shapes, calendar-bucket unit / zone /
|
|
440
466
|
* week start / alias, "grouped queries only", "must also appear in
|
|
441
467
|
* $groupBy", alias collisions with this table's fields. Runs once per
|
|
@@ -551,11 +577,45 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
551
577
|
private _aggregateControls;
|
|
552
578
|
private _resolveReadStrategy;
|
|
553
579
|
/**
|
|
554
|
-
*
|
|
580
|
+
* Post-read row decoration hook. Not implemented by
|
|
581
|
+
* default — defining it in a subclass switches it on. Runs once per
|
|
582
|
+
* response on `/query`, `/pages`, `/geo` and `/one` (`/one/:id` and the
|
|
583
|
+
* composite form), after `$actions` augmentation, with the final top-level
|
|
584
|
+
* rows. Mutate the rows in place; the return value is ignored. May be async.
|
|
585
|
+
*
|
|
586
|
+
* Not called for `$count`, `$groupBy` aggregates, nested `$with` rows
|
|
587
|
+
* (reach them through the parent row), a `/one` 404, or value-help
|
|
588
|
+
* controllers.
|
|
589
|
+
*
|
|
590
|
+
* Convention (not enforced): name decoration keys with a `$` prefix, like
|
|
591
|
+
* `$actions` and `$distance`, so they can never collide with a field name.
|
|
592
|
+
* Do not overwrite `$actions`. Columns the
|
|
593
|
+
* hook needs but the client did not select must be added in
|
|
594
|
+
* {@link transformProjection} — they are then part of the response.
|
|
595
|
+
*
|
|
596
|
+
* ```ts
|
|
597
|
+
* protected async decorateRows(rows: Record<string, unknown>[], ctx: TDbDecorateContext) {
|
|
598
|
+
* const unread = await countUnread(rows.map((r) => r.id))
|
|
599
|
+
* for (const row of rows) row.$unread = unread.get(row.id) ?? 0
|
|
600
|
+
* }
|
|
601
|
+
* ```
|
|
602
|
+
*
|
|
603
|
+
* @since 0.1.136
|
|
604
|
+
*/
|
|
605
|
+
protected decorateRows?(rows: Record<string, unknown>[], ctx: TDbDecorateContext): void | Promise<void>;
|
|
606
|
+
/**
|
|
607
|
+
* Finishes a read's top-level rows in place: `$actions` augmentation (when
|
|
608
|
+
* the request asked for it — `prep`), then {@link decorateRows} when a
|
|
609
|
+
* subclass implements it. Returns the hook's result — `undefined`, with no
|
|
610
|
+
* promise or microtask, when there is no hook or it is synchronous.
|
|
611
|
+
*/
|
|
612
|
+
private _finishRows;
|
|
613
|
+
/**
|
|
614
|
+
* Shared `query` / `pages` / `geo` pipeline: prepare actions augmentation + read
|
|
555
615
|
* strategy in parallel, pre-widen $select for `requiredFields`, run
|
|
556
616
|
* `exec`, and augment `result.data` with `$actions` when the request set
|
|
557
|
-
* `$actions=true
|
|
558
|
-
* family (count vs no-count).
|
|
617
|
+
* `$actions=true`, then run {@link decorateRows}. Caller dispatches the
|
|
618
|
+
* strategy to its read-method family (count vs no-count).
|
|
559
619
|
*/
|
|
560
620
|
private _runReadWithActions;
|
|
561
621
|
/**
|
|
@@ -1006,7 +1066,7 @@ interface LooseGate {
|
|
|
1006
1066
|
onDisabledRows?: TOnDisabledRows;
|
|
1007
1067
|
}
|
|
1008
1068
|
type GateOpts<TRow, R extends readonly FlatKey<TRow>[]> = unknown extends TRow ? LooseGate : NoGate | WithGate<TRow, R>;
|
|
1009
|
-
interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value" | "disabled">> {
|
|
1069
|
+
interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value" | "disabled" | "formUrl">> {
|
|
1010
1070
|
/**
|
|
1011
1071
|
* Bound table reference. REQUIRED on non-`AsDbReadableController` classes
|
|
1012
1072
|
* when `disabled` is set OR a `@DbActionRow*` parameter is declared.
|
|
@@ -1035,6 +1095,25 @@ interface DbActionsEntryCommonBase {
|
|
|
1035
1095
|
promptText?: string | [string, string];
|
|
1036
1096
|
/** Mirrors {@link TDbActionInfo.shortcut} — single-character UI hint. */
|
|
1037
1097
|
shortcut?: string;
|
|
1098
|
+
/**
|
|
1099
|
+
* Input form the UI collects before invoking the action:
|
|
1100
|
+
*
|
|
1101
|
+
* - a compiled `.as` interface — registered on THIS controller and served
|
|
1102
|
+
* by its own `GET /meta/form/:name`; the wire carries `inputForm: Type.name`.
|
|
1103
|
+
* - `{ name, url }` — a form served elsewhere: `name` goes on the wire as
|
|
1104
|
+
* `inputForm`, `url` (server-absolute path of the serialized schema, e.g.
|
|
1105
|
+
* `"/api/shipping/meta/form/ShipForm"`) as {@link TDbActionInfo.formUrl}.
|
|
1106
|
+
*
|
|
1107
|
+
* Not allowed with `processor: 'navigate'`. Class-level entries only
|
|
1108
|
+
* describe the action — validating `input` is the target handler's job
|
|
1109
|
+
* (e.g. its own `@InputForm(Type)` param).
|
|
1110
|
+
*
|
|
1111
|
+
* @since 0.1.136
|
|
1112
|
+
*/
|
|
1113
|
+
inputForm?: TAtscriptAnnotatedType | {
|
|
1114
|
+
name: string;
|
|
1115
|
+
url: string;
|
|
1116
|
+
};
|
|
1038
1117
|
}
|
|
1039
1118
|
type DbActionsEntryWithGate<TRow, R extends readonly FlatKey<TRow>[]> = DbActionsEntryCommonBase & GateOpts<TRow, R>;
|
|
1040
1119
|
/**
|
|
@@ -1048,6 +1127,7 @@ type DbActionsEntryWithGate<TRow, R extends readonly FlatKey<TRow>[]> = DbAction
|
|
|
1048
1127
|
type TDbActionsEntry<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = (DbActionsEntryWithGate<TRow, R> & {
|
|
1049
1128
|
processor: "navigate";
|
|
1050
1129
|
value: string;
|
|
1130
|
+
inputForm?: never;
|
|
1051
1131
|
}) | (DbActionsEntryWithGate<TRow, R> & {
|
|
1052
1132
|
processor: "custom";
|
|
1053
1133
|
value?: never;
|
|
@@ -1721,4 +1801,4 @@ declare function resolveTerminalRef(def: TAtscriptAnnotatedType): TTerminalRef |
|
|
|
1721
1801
|
*/
|
|
1722
1802
|
declare function applyTerminalRefs(serialized: TSerializedAnnotatedType, runtime: TAtscriptAnnotatedType, options: TSerializeOptions): TSerializedAnnotatedType;
|
|
1723
1803
|
//#endregion
|
|
1724
|
-
export { ActionDisabledError, type ActionDisabledErrorBody, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, type AtscriptDbMate, type AtscriptDbMeta, type AtscriptDbParamsMeta, DEFAULT_DB_SPACE, DbAction, DbActionDefault, type DbActionEnvelope, DbActionID, DbActionIDs, type DbActionOpts, DbActionRow, DbActionRows, DbActions, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, type IdValidationSource, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, ReadableController, TABLE_DEF, TAssertExposedOptions, type TCapabilityReadable, type TCapabilityVerdict, TControllerBindingOptions, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionInputFormMeta, type TDbActionIntent, type TDbActionLevel, type TDbActionMeta, type TDbActionParamKind, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, type TDbClassActionMeta, type TDbRemoveGuardContext, type TDbWriteAction, type TDbWriteGuardContext, type TFieldCapability, type THttpErrorEntry, type TQueryPathOp, type TQueryPathRefs, TReadableBinding, type TReadableBindingMeta, type TTerminalRef, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, collectQueryPaths, dbActionBodySlot, dbActionInputSlot, discoverActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, validationErrorTransform };
|
|
1804
|
+
export { ActionDisabledError, type ActionDisabledErrorBody, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, type AtscriptDbMate, type AtscriptDbMeta, type AtscriptDbParamsMeta, DEFAULT_DB_SPACE, DbAction, DbActionDefault, type DbActionEnvelope, DbActionID, DbActionIDs, type DbActionOpts, DbActionRow, DbActionRows, DbActions, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, type IdValidationSource, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, ReadableController, TABLE_DEF, TAssertExposedOptions, type TCapabilityReadable, type TCapabilityVerdict, TControllerBindingOptions, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionInputFormMeta, type TDbActionIntent, type TDbActionLevel, type TDbActionMeta, type TDbActionParamKind, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, type TDbClassActionMeta, TDbDecorateContext, TDbDecorateEndpoint, type TDbRemoveGuardContext, type TDbWriteAction, type TDbWriteGuardContext, type TFieldCapability, type THttpErrorEntry, type TQueryPathOp, type TQueryPathRefs, TReadableBinding, type TReadableBindingMeta, type TTerminalRef, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, collectQueryPaths, dbActionBodySlot, dbActionInputSlot, discoverActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, validationErrorTransform };
|
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializ
|
|
|
3
3
|
import { HttpError } from "@moostjs/event-http";
|
|
4
4
|
import { Mate, Moost, TConsoleBase, TMateParamMeta, TMoostMetadata } from "moost";
|
|
5
5
|
import { parseUrl } from "@uniqu/url";
|
|
6
|
-
import { AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TCrudOp, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbFieldMeta, TDbRemoveGuardContext, TDbRemoveGuardContext as TDbRemoveGuardContext$1, TDbWriteAction, TDbWriteAction as TDbWriteAction$1, TDbWriteGuardContext, TDbWriteGuardContext as TDbWriteGuardContext$1, TFilterPredicate, TIdResolveOptions, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
|
|
6
|
+
import { AggregateFn, AtscriptDbReadable, AtscriptDbTable, BucketUnit, FilterExpr, FlatOf, TCrudOp, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbFieldMeta, TDbRemoveGuardContext, TDbRemoveGuardContext as TDbRemoveGuardContext$1, TDbWriteAction, TDbWriteAction as TDbWriteAction$1, TDbWriteGuardContext, TDbWriteGuardContext as TDbWriteGuardContext$1, TFilterPredicate, TIdResolveOptions, TIdentification, TMetaResponse, TQueryPathOp, TQueryPathOp as TQueryPathOp$1, TQueryPathRefs, TQueryPathSource, Uniquery, UniqueryControls, collectQueryPaths } from "@atscript/db";
|
|
7
7
|
//#region src/as-readable.controller.d.ts
|
|
8
8
|
/**
|
|
9
9
|
* Abstract base class for read-only HTTP controllers over an Atscript interface.
|
|
@@ -241,7 +241,7 @@ interface TCapabilityVerdict {
|
|
|
241
241
|
message: string;
|
|
242
242
|
}
|
|
243
243
|
/** The readable members the index reads. */
|
|
244
|
-
type TCapabilityReadable = Pick<AtscriptDbReadable, "type" | "fieldDescriptors" | "flatMap" | "navFields" | "relations" | "ignoredFields" | "canFilterField" | "canSortField" | "isGeoSearchable" | "calendarBucketUnits" | "dimensions" | "measures">;
|
|
244
|
+
type TCapabilityReadable = Pick<AtscriptDbReadable, "type" | "fieldDescriptors" | "flatMap" | "navFields" | "relations" | "ignoredFields" | "canFilterField" | "canSortField" | "isGeoSearchable" | "calendarBucketUnits" | "aggregateFns" | "dimensions" | "measures">;
|
|
245
245
|
/**
|
|
246
246
|
* Capability index of one readable.
|
|
247
247
|
*
|
|
@@ -273,15 +273,17 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
|
273
273
|
readonly physicalNames: ReadonlySet<string>;
|
|
274
274
|
/** Calendar-bucket units the adapter groups by, in `BUCKET_UNITS` order (`/meta.bucketUnits`). */
|
|
275
275
|
readonly bucketUnits: readonly BucketUnit[];
|
|
276
|
+
/** Aggregate functions the adapter renders, in canonical `ALL_AGGREGATE_FNS` order (`/meta.aggregateFns`). */
|
|
277
|
+
readonly aggregateFns: readonly AggregateFn[];
|
|
276
278
|
/** The adapter-level capabilities this index was built against — see {@link adapterSignature}. */
|
|
277
279
|
readonly signature: string;
|
|
278
280
|
/**
|
|
279
281
|
* The adapter-level capabilities that can change after construction (geo
|
|
280
|
-
* support, calendar-bucket units): an index whose
|
|
281
|
-
* differs from this is stale. Any new adapter-level input
|
|
282
|
-
* must be added here.
|
|
282
|
+
* support, calendar-bucket units, aggregate functions): an index whose
|
|
283
|
+
* {@link signature} differs from this is stale. Any new adapter-level input
|
|
284
|
+
* the index reads must be added here.
|
|
283
285
|
*/
|
|
284
|
-
static adapterSignature(source: Pick<TCapabilityReadable, "isGeoSearchable" | "calendarBucketUnits">): string;
|
|
286
|
+
static adapterSignature(source: Pick<TCapabilityReadable, "isGeoSearchable" | "calendarBucketUnits" | "aggregateFns">): string;
|
|
285
287
|
private readonly _entries;
|
|
286
288
|
/** Nested-object parents (never listed, always selectable) → their listed leaves. */
|
|
287
289
|
private readonly _objectParents;
|
|
@@ -324,12 +326,34 @@ declare class FieldCapabilityIndex implements TQueryPathSource {
|
|
|
324
326
|
}
|
|
325
327
|
//#endregion
|
|
326
328
|
//#region src/as-db-readable.controller.d.ts
|
|
329
|
+
/** Read endpoint a {@link AsDbReadableController.decorateRows} call serves. */
|
|
330
|
+
type TDbDecorateEndpoint = "query" | "pages" | "geo" | "one";
|
|
331
|
+
/**
|
|
332
|
+
* Context passed to {@link AsDbReadableController.decorateRows}.
|
|
333
|
+
*
|
|
334
|
+
* @since 0.1.136
|
|
335
|
+
*/
|
|
336
|
+
interface TDbDecorateContext {
|
|
337
|
+
/** Endpoint that produced the rows. `/one` and `/one/:id` both report `"one"`. */
|
|
338
|
+
endpoint: TDbDecorateEndpoint;
|
|
339
|
+
/**
|
|
340
|
+
* The effective `$select` the endpoint read with — after
|
|
341
|
+
* `transformProjection`, the `@db.writeOnly` seal and preferred-id
|
|
342
|
+
* widening (`undefined` = no projection). Columns added only to feed an
|
|
343
|
+
* action's `requiredFields` are stripped again before the hook runs.
|
|
344
|
+
*/
|
|
345
|
+
projection: UniqueryControls["$select"] | undefined;
|
|
346
|
+
/** The request's parsed controls (`$select`, `$with`, `$actions`, …). Read-only by convention. */
|
|
347
|
+
controls: Record<string, unknown>;
|
|
348
|
+
}
|
|
327
349
|
/**
|
|
328
350
|
* Read-only database controller for Moost that works with any `AtscriptDbReadable`
|
|
329
351
|
* (tables or views). Provides query, pages, getOne, and meta endpoints.
|
|
330
352
|
*
|
|
331
353
|
* For write operations (insert, replace, update, delete), use {@link AsDbController}.
|
|
332
|
-
*
|
|
354
|
+
* Views bind to this same class — `@ViewController(view)` (an alias of
|
|
355
|
+
* `@ReadableController`) or a constructor-passed view; there is no separate
|
|
356
|
+
* view controller.
|
|
333
357
|
*/
|
|
334
358
|
declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsReadableController<T, DataType> {
|
|
335
359
|
/** Reference to the underlying readable (table or view). */
|
|
@@ -370,6 +394,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
370
394
|
private readonly _idSources;
|
|
371
395
|
private readonly _preferredIdSet;
|
|
372
396
|
private readonly _overlayIsNoOp;
|
|
397
|
+
/** `true` when a subclass implements {@link decorateRows} (the override switches the hook on). */
|
|
398
|
+
private readonly _decorates;
|
|
373
399
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
374
400
|
private readonly _quantityRefByPath;
|
|
375
401
|
/** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
|
|
@@ -435,7 +461,7 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
435
461
|
}): HttpError | undefined;
|
|
436
462
|
/**
|
|
437
463
|
* The core's shared normalizer of `$select` computed entries
|
|
438
|
-
* (`
|
|
464
|
+
* (`normalizeComputedSelect`) as a 400 with the core's wording and `path`
|
|
439
465
|
* (`$select` / `$groupBy`): entry shapes, calendar-bucket unit / zone /
|
|
440
466
|
* week start / alias, "grouped queries only", "must also appear in
|
|
441
467
|
* $groupBy", alias collisions with this table's fields. Runs once per
|
|
@@ -551,11 +577,45 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
551
577
|
private _aggregateControls;
|
|
552
578
|
private _resolveReadStrategy;
|
|
553
579
|
/**
|
|
554
|
-
*
|
|
580
|
+
* Post-read row decoration hook. Not implemented by
|
|
581
|
+
* default — defining it in a subclass switches it on. Runs once per
|
|
582
|
+
* response on `/query`, `/pages`, `/geo` and `/one` (`/one/:id` and the
|
|
583
|
+
* composite form), after `$actions` augmentation, with the final top-level
|
|
584
|
+
* rows. Mutate the rows in place; the return value is ignored. May be async.
|
|
585
|
+
*
|
|
586
|
+
* Not called for `$count`, `$groupBy` aggregates, nested `$with` rows
|
|
587
|
+
* (reach them through the parent row), a `/one` 404, or value-help
|
|
588
|
+
* controllers.
|
|
589
|
+
*
|
|
590
|
+
* Convention (not enforced): name decoration keys with a `$` prefix, like
|
|
591
|
+
* `$actions` and `$distance`, so they can never collide with a field name.
|
|
592
|
+
* Do not overwrite `$actions`. Columns the
|
|
593
|
+
* hook needs but the client did not select must be added in
|
|
594
|
+
* {@link transformProjection} — they are then part of the response.
|
|
595
|
+
*
|
|
596
|
+
* ```ts
|
|
597
|
+
* protected async decorateRows(rows: Record<string, unknown>[], ctx: TDbDecorateContext) {
|
|
598
|
+
* const unread = await countUnread(rows.map((r) => r.id))
|
|
599
|
+
* for (const row of rows) row.$unread = unread.get(row.id) ?? 0
|
|
600
|
+
* }
|
|
601
|
+
* ```
|
|
602
|
+
*
|
|
603
|
+
* @since 0.1.136
|
|
604
|
+
*/
|
|
605
|
+
protected decorateRows?(rows: Record<string, unknown>[], ctx: TDbDecorateContext): void | Promise<void>;
|
|
606
|
+
/**
|
|
607
|
+
* Finishes a read's top-level rows in place: `$actions` augmentation (when
|
|
608
|
+
* the request asked for it — `prep`), then {@link decorateRows} when a
|
|
609
|
+
* subclass implements it. Returns the hook's result — `undefined`, with no
|
|
610
|
+
* promise or microtask, when there is no hook or it is synchronous.
|
|
611
|
+
*/
|
|
612
|
+
private _finishRows;
|
|
613
|
+
/**
|
|
614
|
+
* Shared `query` / `pages` / `geo` pipeline: prepare actions augmentation + read
|
|
555
615
|
* strategy in parallel, pre-widen $select for `requiredFields`, run
|
|
556
616
|
* `exec`, and augment `result.data` with `$actions` when the request set
|
|
557
|
-
* `$actions=true
|
|
558
|
-
* family (count vs no-count).
|
|
617
|
+
* `$actions=true`, then run {@link decorateRows}. Caller dispatches the
|
|
618
|
+
* strategy to its read-method family (count vs no-count).
|
|
559
619
|
*/
|
|
560
620
|
private _runReadWithActions;
|
|
561
621
|
/**
|
|
@@ -1006,7 +1066,7 @@ interface LooseGate {
|
|
|
1006
1066
|
onDisabledRows?: TOnDisabledRows;
|
|
1007
1067
|
}
|
|
1008
1068
|
type GateOpts<TRow, R extends readonly FlatKey<TRow>[]> = unknown extends TRow ? LooseGate : NoGate | WithGate<TRow, R>;
|
|
1009
|
-
interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value" | "disabled">> {
|
|
1069
|
+
interface BaseActionOpts extends Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value" | "disabled" | "formUrl">> {
|
|
1010
1070
|
/**
|
|
1011
1071
|
* Bound table reference. REQUIRED on non-`AsDbReadableController` classes
|
|
1012
1072
|
* when `disabled` is set OR a `@DbActionRow*` parameter is declared.
|
|
@@ -1035,6 +1095,25 @@ interface DbActionsEntryCommonBase {
|
|
|
1035
1095
|
promptText?: string | [string, string];
|
|
1036
1096
|
/** Mirrors {@link TDbActionInfo.shortcut} — single-character UI hint. */
|
|
1037
1097
|
shortcut?: string;
|
|
1098
|
+
/**
|
|
1099
|
+
* Input form the UI collects before invoking the action:
|
|
1100
|
+
*
|
|
1101
|
+
* - a compiled `.as` interface — registered on THIS controller and served
|
|
1102
|
+
* by its own `GET /meta/form/:name`; the wire carries `inputForm: Type.name`.
|
|
1103
|
+
* - `{ name, url }` — a form served elsewhere: `name` goes on the wire as
|
|
1104
|
+
* `inputForm`, `url` (server-absolute path of the serialized schema, e.g.
|
|
1105
|
+
* `"/api/shipping/meta/form/ShipForm"`) as {@link TDbActionInfo.formUrl}.
|
|
1106
|
+
*
|
|
1107
|
+
* Not allowed with `processor: 'navigate'`. Class-level entries only
|
|
1108
|
+
* describe the action — validating `input` is the target handler's job
|
|
1109
|
+
* (e.g. its own `@InputForm(Type)` param).
|
|
1110
|
+
*
|
|
1111
|
+
* @since 0.1.136
|
|
1112
|
+
*/
|
|
1113
|
+
inputForm?: TAtscriptAnnotatedType | {
|
|
1114
|
+
name: string;
|
|
1115
|
+
url: string;
|
|
1116
|
+
};
|
|
1038
1117
|
}
|
|
1039
1118
|
type DbActionsEntryWithGate<TRow, R extends readonly FlatKey<TRow>[]> = DbActionsEntryCommonBase & GateOpts<TRow, R>;
|
|
1040
1119
|
/**
|
|
@@ -1048,6 +1127,7 @@ type DbActionsEntryWithGate<TRow, R extends readonly FlatKey<TRow>[]> = DbAction
|
|
|
1048
1127
|
type TDbActionsEntry<TRow = unknown, R extends readonly FlatKey<TRow>[] = []> = (DbActionsEntryWithGate<TRow, R> & {
|
|
1049
1128
|
processor: "navigate";
|
|
1050
1129
|
value: string;
|
|
1130
|
+
inputForm?: never;
|
|
1051
1131
|
}) | (DbActionsEntryWithGate<TRow, R> & {
|
|
1052
1132
|
processor: "custom";
|
|
1053
1133
|
value?: never;
|
|
@@ -1721,4 +1801,4 @@ declare function resolveTerminalRef(def: TAtscriptAnnotatedType): TTerminalRef |
|
|
|
1721
1801
|
*/
|
|
1722
1802
|
declare function applyTerminalRefs(serialized: TSerializedAnnotatedType, runtime: TAtscriptAnnotatedType, options: TSerializeOptions): TSerializedAnnotatedType;
|
|
1723
1803
|
//#endregion
|
|
1724
|
-
export { ActionDisabledError, type ActionDisabledErrorBody, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, type AtscriptDbMate, type AtscriptDbMeta, type AtscriptDbParamsMeta, DEFAULT_DB_SPACE, DbAction, DbActionDefault, type DbActionEnvelope, DbActionID, DbActionIDs, type DbActionOpts, DbActionRow, DbActionRows, DbActions, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, type IdValidationSource, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, ReadableController, TABLE_DEF, TAssertExposedOptions, type TCapabilityReadable, type TCapabilityVerdict, TControllerBindingOptions, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionInputFormMeta, type TDbActionIntent, type TDbActionLevel, type TDbActionMeta, type TDbActionParamKind, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, type TDbClassActionMeta, type TDbRemoveGuardContext, type TDbWriteAction, type TDbWriteGuardContext, type TFieldCapability, type THttpErrorEntry, type TQueryPathOp, type TQueryPathRefs, TReadableBinding, type TReadableBindingMeta, type TTerminalRef, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, collectQueryPaths, dbActionBodySlot, dbActionInputSlot, discoverActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, validationErrorTransform };
|
|
1804
|
+
export { ActionDisabledError, type ActionDisabledErrorBody, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, type AtscriptDbMate, type AtscriptDbMeta, type AtscriptDbParamsMeta, DEFAULT_DB_SPACE, DbAction, DbActionDefault, type DbActionEnvelope, DbActionID, DbActionIDs, type DbActionOpts, DbActionRow, DbActionRows, DbActions, DbRowActions, DbRowsActions, DbTableActions, FieldCapabilityIndex, type IdValidationSource, InputForm, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, ReadableController, TABLE_DEF, TAssertExposedOptions, type TCapabilityReadable, type TCapabilityVerdict, TControllerBindingOptions, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionInputFormMeta, type TDbActionIntent, type TDbActionLevel, type TDbActionMeta, type TDbActionParamKind, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, type TDbClassActionMeta, TDbDecorateContext, TDbDecorateEndpoint, type TDbRemoveGuardContext, type TDbWriteAction, type TDbWriteGuardContext, type TFieldCapability, type THttpErrorEntry, type TQueryPathOp, type TQueryPathRefs, TReadableBinding, type TReadableBindingMeta, type TTerminalRef, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, applyTerminalRefs, assertExposed, badRequest, clearDbSpaces, collectQueryPaths, dbActionBodySlot, dbActionInputSlot, discoverActions, errorEnvelope, findReadableBinding, getAtscriptDbMate, getControllerFormType, perRow, provideDbSpace, resolveBoundReadable, resolveDbSpace, resolveProp, resolveTerminalRef, useDbActionId, useDbActionIds, useDbActionInput, useDbActionRow, useDbActionRows, validationErrorTransform };
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { ValidatorError, defineAnnotatedType, isAnnotatedType, serializeAnnotate
|
|
|
3
3
|
import { Body, Delete, Get, HttpError, Patch, Post, Put, Query, Url } from "@moostjs/event-http";
|
|
4
4
|
import { ApplyDecorators, Controller, Inherit, Inject, Intercept, Moost, Optional, Param, Provide, Resolve, TInterceptorPriority, defineBeforeInterceptor, defineInterceptor, getMoostMate, useControllerContext } from "moost";
|
|
5
5
|
import { parseUrl } from "@uniqu/url";
|
|
6
|
-
import { ADAPTER_FILTER_REASON, DbError, ENCRYPTED_REASON, acceptedOperatorsHint, bucketSourceVerdict, canFilterLeaf, checkHavingKeys, classifyQueryPath, collectQueryPaths, collectQueryPaths as collectQueryPaths$1, findAncestorInSet, isJsonValueField, isPlainObject, narrowerFilterOps,
|
|
6
|
+
import { ADAPTER_FILTER_REASON, ALL_AGGREGATE_FNS, DbError, ENCRYPTED_REASON, acceptedOperatorsHint, bucketSourceVerdict, canFilterLeaf, checkHavingKeys, classifyQueryPath, collectQueryPaths, collectQueryPaths as collectQueryPaths$1, findAncestorInSet, isJsonValueField, isPlainObject, narrowerFilterOps, normalizeComputedSelect, reconcileCas, unsupportedOperatorMessage } from "@atscript/db";
|
|
7
7
|
import { BUCKET_UNITS } from "@uniqu/core";
|
|
8
8
|
import { buildMemoryPredicate, projectRow, sortRows } from "@atscript/db-memory";
|
|
9
9
|
import { cached, current, defineWook, key } from "@wooksjs/event-core";
|
|
@@ -250,7 +250,8 @@ const rowLevelActionsCache = /* @__PURE__ */ new WeakMap();
|
|
|
250
250
|
/**
|
|
251
251
|
* Per-controller registry of form names → compiled `.as` classes, populated
|
|
252
252
|
* during {@link discoverActions} when a method param carries
|
|
253
|
-
* `atscript_db_action_input_form
|
|
253
|
+
* `atscript_db_action_input_form` or a class-level entry passes a type as
|
|
254
|
+
* `inputForm`. Backs `GET /meta/form/:name`.
|
|
254
255
|
*
|
|
255
256
|
* Same name + same type ref across multiple actions is fine (forms can be
|
|
256
257
|
* reused). Same name + *different* type refs is an ambiguity — discovery
|
|
@@ -394,7 +395,7 @@ function collectClassActions(ctor, logger, out, seen) {
|
|
|
394
395
|
logger.warn(`${WARN_PREFIX} duplicate action name "${name}" within controller — dropping the second declaration`);
|
|
395
396
|
continue;
|
|
396
397
|
}
|
|
397
|
-
const built = buildClassEntry(name, entry, logger);
|
|
398
|
+
const built = buildClassEntry(ctor, name, entry, logger);
|
|
398
399
|
if (built) {
|
|
399
400
|
seen.add(name);
|
|
400
401
|
out.push({
|
|
@@ -404,7 +405,7 @@ function collectClassActions(ctor, logger, out, seen) {
|
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
|
-
function buildClassEntry(name, entry, logger) {
|
|
408
|
+
function buildClassEntry(ctor, name, entry, logger) {
|
|
408
409
|
const level = entry.level;
|
|
409
410
|
if (!level) {
|
|
410
411
|
logger.warn(`${WARN_PREFIX} class-level action "${name}" requires a level — dropping. Use @DbTableActions/@DbRowActions/@DbRowsActions or set "level" explicitly.`);
|
|
@@ -442,6 +443,8 @@ function buildClassEntry(name, entry, logger) {
|
|
|
442
443
|
logger.warn(`${WARN_PREFIX} class-level action "${name}" has unknown processor "${String(processor)}" — dropping`);
|
|
443
444
|
return null;
|
|
444
445
|
}
|
|
446
|
+
const form = resolveClassInputForm(name, entry, processor, logger);
|
|
447
|
+
if (form === null) return null;
|
|
445
448
|
const info = {
|
|
446
449
|
name,
|
|
447
450
|
label: entry.label,
|
|
@@ -449,9 +452,45 @@ function buildClassEntry(name, entry, logger) {
|
|
|
449
452
|
processor,
|
|
450
453
|
value
|
|
451
454
|
};
|
|
455
|
+
if (form) {
|
|
456
|
+
if (form.type && !registerFormType(ctor, {
|
|
457
|
+
type: form.type,
|
|
458
|
+
name: form.name
|
|
459
|
+
}, name, logger)) return null;
|
|
460
|
+
info.inputForm = form.name;
|
|
461
|
+
if (form.url !== void 0) info.formUrl = form.url;
|
|
462
|
+
}
|
|
452
463
|
emitInfo(info, entry);
|
|
453
464
|
return info;
|
|
454
465
|
}
|
|
466
|
+
/**
|
|
467
|
+
* Resolves a class-level entry's `inputForm`. Returns `undefined` when the
|
|
468
|
+
* entry declares none, `null` when it must be dropped (already warned — the
|
|
469
|
+
* runtime check for JS callers and `processor: "navigate"`), else the form to
|
|
470
|
+
* emit: `type` when this controller serves it (`/meta/form/:name`), `url`
|
|
471
|
+
* when it lives elsewhere.
|
|
472
|
+
*/
|
|
473
|
+
function resolveClassInputForm(name, entry, processor, logger) {
|
|
474
|
+
const inputForm = entry.inputForm;
|
|
475
|
+
if (inputForm === void 0) return void 0;
|
|
476
|
+
const drop = (reason) => {
|
|
477
|
+
logger.warn(`${WARN_PREFIX} class-level action "${name}" — ${reason} — dropping`);
|
|
478
|
+
return null;
|
|
479
|
+
};
|
|
480
|
+
if (processor === "navigate") return drop("processor \"navigate\" cannot take an `inputForm`");
|
|
481
|
+
const { name: formName, url } = inputForm ?? {};
|
|
482
|
+
if (typeof formName === "string" && formName !== "") {
|
|
483
|
+
if (isAnnotatedType(inputForm)) return {
|
|
484
|
+
name: formName,
|
|
485
|
+
type: inputForm
|
|
486
|
+
};
|
|
487
|
+
if (typeof url === "string" && url !== "") return {
|
|
488
|
+
name: formName,
|
|
489
|
+
url
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
return drop("`inputForm` must be a compiled .as interface or `{ name, url }` (non-empty strings)");
|
|
493
|
+
}
|
|
455
494
|
function applyDefaultPerLevel(envelopes, logger) {
|
|
456
495
|
const winners = /* @__PURE__ */ new Map();
|
|
457
496
|
for (const { info } of envelopes) {
|
|
@@ -976,6 +1015,11 @@ function computeStripFields(candidates, resolvedProjection) {
|
|
|
976
1015
|
}
|
|
977
1016
|
return strip;
|
|
978
1017
|
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Sets `$actions` on every row and strips the columns fetched only for an
|
|
1020
|
+
* action's `requiredFields` — IN PLACE; returns the same array, typed as
|
|
1021
|
+
* augmented.
|
|
1022
|
+
*/
|
|
979
1023
|
function augmentRowsWithActions(args) {
|
|
980
1024
|
const { envelopes, rows, resolvedProjection } = args;
|
|
981
1025
|
const candidates = collectCandidates(envelopes);
|
|
@@ -1223,16 +1267,18 @@ var FieldCapabilityIndex = class FieldCapabilityIndex {
|
|
|
1223
1267
|
physicalNames;
|
|
1224
1268
|
/** Calendar-bucket units the adapter groups by, in `BUCKET_UNITS` order (`/meta.bucketUnits`). */
|
|
1225
1269
|
bucketUnits;
|
|
1270
|
+
/** Aggregate functions the adapter renders, in canonical `ALL_AGGREGATE_FNS` order (`/meta.aggregateFns`). */
|
|
1271
|
+
aggregateFns;
|
|
1226
1272
|
/** The adapter-level capabilities this index was built against — see {@link adapterSignature}. */
|
|
1227
1273
|
signature;
|
|
1228
1274
|
/**
|
|
1229
1275
|
* The adapter-level capabilities that can change after construction (geo
|
|
1230
|
-
* support, calendar-bucket units): an index whose
|
|
1231
|
-
* differs from this is stale. Any new adapter-level input
|
|
1232
|
-
* must be added here.
|
|
1276
|
+
* support, calendar-bucket units, aggregate functions): an index whose
|
|
1277
|
+
* {@link signature} differs from this is stale. Any new adapter-level input
|
|
1278
|
+
* the index reads must be added here.
|
|
1233
1279
|
*/
|
|
1234
1280
|
static adapterSignature(source) {
|
|
1235
|
-
return `${source.isGeoSearchable()}|${[...source.calendarBucketUnits()].join(",")}`;
|
|
1281
|
+
return `${source.isGeoSearchable()}|${[...source.calendarBucketUnits()].join(",")}|${[...source.aggregateFns()].join(",")}`;
|
|
1236
1282
|
}
|
|
1237
1283
|
_entries = /* @__PURE__ */ new Map();
|
|
1238
1284
|
/** Nested-object parents (never listed, always selectable) → their listed leaves. */
|
|
@@ -1255,6 +1301,8 @@ var FieldCapabilityIndex = class FieldCapabilityIndex {
|
|
|
1255
1301
|
this.signature = FieldCapabilityIndex.adapterSignature(source);
|
|
1256
1302
|
const units = source.calendarBucketUnits();
|
|
1257
1303
|
this.bucketUnits = BUCKET_UNITS.filter((unit) => units.has(unit));
|
|
1304
|
+
const fns = source.aggregateFns();
|
|
1305
|
+
this.aggregateFns = [...ALL_AGGREGATE_FNS].filter((fn) => fns.has(fn));
|
|
1258
1306
|
const physicalNames = /* @__PURE__ */ new Set();
|
|
1259
1307
|
const jsonValueParents = /* @__PURE__ */ new Set();
|
|
1260
1308
|
for (const fd of source.fieldDescriptors) {
|
|
@@ -1551,6 +1599,8 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
1551
1599
|
_idSources = /* @__PURE__ */ new Map();
|
|
1552
1600
|
_preferredIdSet;
|
|
1553
1601
|
_overlayIsNoOp;
|
|
1602
|
+
/** `true` when a subclass implements {@link decorateRows} (the override switches the hook on). */
|
|
1603
|
+
_decorates;
|
|
1554
1604
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
1555
1605
|
_quantityRefByPath;
|
|
1556
1606
|
/** `@db.column.searchable` paths — the `$search` fallback when the adapter has no native search. */
|
|
@@ -1574,6 +1624,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
1574
1624
|
this._quantityRefByPath = this._collectQuantityRefs();
|
|
1575
1625
|
const defaultOverlay = AsReadableController.prototype.applyMetaOverlay;
|
|
1576
1626
|
this._overlayIsNoOp = this.applyMetaOverlay === defaultOverlay;
|
|
1627
|
+
this._decorates = typeof this.decorateRows === "function";
|
|
1577
1628
|
this._idOpts = this.hasField === _AsDbReadableController.prototype.hasField ? void 0 : { isFieldVisible: this._exists };
|
|
1578
1629
|
}
|
|
1579
1630
|
/**
|
|
@@ -1680,7 +1731,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
1680
1731
|
}
|
|
1681
1732
|
/**
|
|
1682
1733
|
* The core's shared normalizer of `$select` computed entries
|
|
1683
|
-
* (`
|
|
1734
|
+
* (`normalizeComputedSelect`) as a 400 with the core's wording and `path`
|
|
1684
1735
|
* (`$select` / `$groupBy`): entry shapes, calendar-bucket unit / zone /
|
|
1685
1736
|
* week start / alias, "grouped queries only", "must also appear in
|
|
1686
1737
|
* $groupBy", alias collisions with this table's fields. Runs once per
|
|
@@ -1692,7 +1743,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
1692
1743
|
checkComputedSelect(controls) {
|
|
1693
1744
|
const capabilities = this.capabilities;
|
|
1694
1745
|
try {
|
|
1695
|
-
|
|
1746
|
+
normalizeComputedSelect(controls, {
|
|
1696
1747
|
flatMap: this.readable.flatMap,
|
|
1697
1748
|
physicalNames: capabilities.physicalNames,
|
|
1698
1749
|
navFields: capabilities.navFields
|
|
@@ -2049,13 +2100,27 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2049
2100
|
return { kind: "plain" };
|
|
2050
2101
|
}
|
|
2051
2102
|
/**
|
|
2052
|
-
*
|
|
2103
|
+
* Finishes a read's top-level rows in place: `$actions` augmentation (when
|
|
2104
|
+
* the request asked for it — `prep`), then {@link decorateRows} when a
|
|
2105
|
+
* subclass implements it. Returns the hook's result — `undefined`, with no
|
|
2106
|
+
* promise or microtask, when there is no hook or it is synchronous.
|
|
2107
|
+
*/
|
|
2108
|
+
_finishRows(rows, prep, ctx) {
|
|
2109
|
+
if (prep) augmentRowsWithActions({
|
|
2110
|
+
envelopes: prep.envelopes,
|
|
2111
|
+
rows,
|
|
2112
|
+
resolvedProjection: prep.resolvedProjection
|
|
2113
|
+
});
|
|
2114
|
+
return this._decorates ? this.decorateRows(rows, ctx) : void 0;
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Shared `query` / `pages` / `geo` pipeline: prepare actions augmentation + read
|
|
2053
2118
|
* strategy in parallel, pre-widen $select for `requiredFields`, run
|
|
2054
2119
|
* `exec`, and augment `result.data` with `$actions` when the request set
|
|
2055
|
-
* `$actions=true
|
|
2056
|
-
* family (count vs no-count).
|
|
2120
|
+
* `$actions=true`, then run {@link decorateRows}. Caller dispatches the
|
|
2121
|
+
* strategy to its read-method family (count vs no-count).
|
|
2057
2122
|
*/
|
|
2058
|
-
async _runReadWithActions(queryObj, controls, select, exec) {
|
|
2123
|
+
async _runReadWithActions(endpoint, queryObj, controls, select, exec) {
|
|
2059
2124
|
const [prep, strategy] = await Promise.all([this._prepareAugmentation(controls, select), this._resolveReadStrategy(controls)]);
|
|
2060
2125
|
const result = await exec(prep?.widenedSelect ? {
|
|
2061
2126
|
...queryObj,
|
|
@@ -2064,12 +2129,12 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2064
2129
|
$select: prep.widenedSelect
|
|
2065
2130
|
}
|
|
2066
2131
|
} : queryObj, strategy);
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
resolvedProjection: prep.resolvedProjection
|
|
2132
|
+
const pending = this._finishRows(result.data, prep, {
|
|
2133
|
+
endpoint,
|
|
2134
|
+
projection: select,
|
|
2135
|
+
controls
|
|
2072
2136
|
});
|
|
2137
|
+
if (pending) await pending;
|
|
2073
2138
|
return result;
|
|
2074
2139
|
}
|
|
2075
2140
|
/**
|
|
@@ -2141,7 +2206,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2141
2206
|
$threshold: threshold
|
|
2142
2207
|
}
|
|
2143
2208
|
};
|
|
2144
|
-
return (await this._runReadWithActions(queryObj, controls, select, async (q, strategy) => {
|
|
2209
|
+
return (await this._runReadWithActions("query", queryObj, controls, select, async (q, strategy) => {
|
|
2145
2210
|
switch (strategy.kind) {
|
|
2146
2211
|
case "vector": return { data: await (strategy.vectorField ? this.readable.vectorSearch(strategy.vectorField, strategy.vector, q) : this.readable.vectorSearch(strategy.vector, q)) };
|
|
2147
2212
|
case "search": return { data: await this.readable.search(strategy.term, q, strategy.index) };
|
|
@@ -2179,7 +2244,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2179
2244
|
$threshold: threshold
|
|
2180
2245
|
}
|
|
2181
2246
|
};
|
|
2182
|
-
const result = await this._runReadWithActions(query, controls, select, async (q, strategy) => {
|
|
2247
|
+
const result = await this._runReadWithActions("pages", query, controls, select, async (q, strategy) => {
|
|
2183
2248
|
switch (strategy.kind) {
|
|
2184
2249
|
case "vector": return strategy.vectorField ? this.readable.vectorSearchWithCount(strategy.vectorField, strategy.vector, q) : this.readable.vectorSearchWithCount(strategy.vector, q);
|
|
2185
2250
|
case "search": return this.readable.searchWithCount(strategy.term, q, strategy.index);
|
|
@@ -2244,7 +2309,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2244
2309
|
}
|
|
2245
2310
|
};
|
|
2246
2311
|
if (paginated) {
|
|
2247
|
-
const result = await this._runReadWithActions(queryObj, controls, select, async (q) => indexName ? this.readable.geoSearchWithCount(indexName, point, q) : this.readable.geoSearchWithCount(point, q));
|
|
2312
|
+
const result = await this._runReadWithActions("geo", queryObj, controls, select, async (q) => indexName ? this.readable.geoSearchWithCount(indexName, point, q) : this.readable.geoSearchWithCount(point, q));
|
|
2248
2313
|
return {
|
|
2249
2314
|
data: result.data,
|
|
2250
2315
|
page,
|
|
@@ -2253,7 +2318,7 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2253
2318
|
count: result.count
|
|
2254
2319
|
};
|
|
2255
2320
|
}
|
|
2256
|
-
return (await this._runReadWithActions(queryObj, controls, select, async (q) => ({ data: await (indexName ? this.readable.geoSearch(indexName, point, q) : this.readable.geoSearch(point, q)) }))).data;
|
|
2321
|
+
return (await this._runReadWithActions("geo", queryObj, controls, select, async (q) => ({ data: await (indexName ? this.readable.geoSearch(indexName, point, q) : this.readable.geoSearch(point, q)) }))).data;
|
|
2257
2322
|
}
|
|
2258
2323
|
/** Parses the `$center` control: `"lng,lat"` string (or tuple) → `[number, number]`. */
|
|
2259
2324
|
_parseGeoCenter(raw) {
|
|
@@ -2328,13 +2393,13 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2328
2393
|
}
|
|
2329
2394
|
const item = await this.returnOne(Promise.resolve(row));
|
|
2330
2395
|
if (item instanceof HttpError) return item;
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
resolvedProjection: prep.resolvedProjection
|
|
2396
|
+
const pending = this._finishRows([item], prep, {
|
|
2397
|
+
endpoint: "one",
|
|
2398
|
+
projection: select,
|
|
2399
|
+
controls: parsedControls
|
|
2336
2400
|
});
|
|
2337
|
-
|
|
2401
|
+
if (pending) await pending;
|
|
2402
|
+
return item;
|
|
2338
2403
|
}
|
|
2339
2404
|
/**
|
|
2340
2405
|
* **GET /meta** — returns table/view metadata for UI.
|
|
@@ -2382,7 +2447,8 @@ let AsDbReadableController = _AsDbReadableController = class AsDbReadableControl
|
|
|
2382
2447
|
actions: this.buildActions(),
|
|
2383
2448
|
crud: this.buildCrud(),
|
|
2384
2449
|
versionColumn: this.readable.versionColumn,
|
|
2385
|
-
...capabilities.bucketUnits.length > 0 && { bucketUnits: [...capabilities.bucketUnits] }
|
|
2450
|
+
...capabilities.bucketUnits.length > 0 && { bucketUnits: [...capabilities.bucketUnits] },
|
|
2451
|
+
aggregateFns: [...capabilities.aggregateFns]
|
|
2386
2452
|
};
|
|
2387
2453
|
}
|
|
2388
2454
|
buildCrud() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/moost-db",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.137",
|
|
4
4
|
"description": "Generic database controller for Moost with Atscript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@uniqu/url": "^0.1.11",
|
|
47
|
-
"@atscript/db-memory": "^0.1.
|
|
47
|
+
"@atscript/db-memory": "^0.1.137"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@atscript/core": "^0.1.92",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@wooksjs/event-core": "^0.7.23",
|
|
65
65
|
"@wooksjs/http-body": "^0.7.23",
|
|
66
66
|
"moost": "^0.6.37",
|
|
67
|
-
"@atscript/db": "^0.1.
|
|
67
|
+
"@atscript/db": "^0.1.137"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"postinstall": "asc -f dts",
|