@atscript/db-client 0.1.146 → 0.1.147
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 +147 -3
- package/dist/index.d.cts +87 -5
- package/dist/index.d.mts +87 -5
- package/dist/index.mjs +141 -5
- package/dist/{validator-810-rhnQ.d.cts → validator-DVha6LOQ.d.cts} +23 -1
- package/dist/{validator-CIm1ZCS5.d.mts → validator-xkEq4h0X.d.mts} +23 -1
- package/dist/validator.d.cts +1 -1
- package/dist/validator.d.mts +1 -1
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -65,6 +65,29 @@ var ActionDisabledError = class extends ClientError {
|
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
/**
|
|
68
|
+
* Typed marker thrown by `Client` when a query-targeted action request is
|
|
69
|
+
* refused (body `name === 'ActionTargetError'`): `TARGET_INVALID` (400),
|
|
70
|
+
* `TARGET_TOO_LARGE` (400, `cap`) or `TARGET_CHANGED` (409, `matched` — the
|
|
71
|
+
* rows the query matches now; re-confirm and retry with that count).
|
|
72
|
+
*
|
|
73
|
+
* @since 0.1.147
|
|
74
|
+
*/
|
|
75
|
+
var ActionTargetError = class extends ClientError {
|
|
76
|
+
name = "ActionTargetError";
|
|
77
|
+
get code() {
|
|
78
|
+
return this.body.code;
|
|
79
|
+
}
|
|
80
|
+
get action() {
|
|
81
|
+
return this.body.action;
|
|
82
|
+
}
|
|
83
|
+
get matched() {
|
|
84
|
+
return this.body.matched;
|
|
85
|
+
}
|
|
86
|
+
get cap() {
|
|
87
|
+
return this.body.cap;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
68
91
|
* Typed marker thrown by `Client._send` when the server response body's
|
|
69
92
|
* `kind === 'version_mismatch'`. The transport / status / base body are
|
|
70
93
|
* identical to a generic `ClientError`; this subclass adds a typed
|
|
@@ -186,7 +209,8 @@ var Client = class {
|
|
|
186
209
|
*
|
|
187
210
|
* `$select` may carry calendar buckets (`{ $bucket, $field, $tz?, $weekStart?, $as? }`);
|
|
188
211
|
* a `$groupBy` entry must be a dimension or a bucket alias (`ValidGroupBy`), and a
|
|
189
|
-
* bucket's value is typed as its `YYYY-MM-DD`
|
|
212
|
+
* bucket's value is typed as its `YYYY-MM-DD` (hour: `YYYY-MM-DDTHH:00`) label
|
|
213
|
+
* (`| null` for an optional source).
|
|
190
214
|
* Gap-fill between labels with `nextBucketLabel` (re-exported here).
|
|
191
215
|
*/
|
|
192
216
|
async aggregate(query) {
|
|
@@ -323,6 +347,13 @@ var Client = class {
|
|
|
323
347
|
* `{ ids?, input? }` — `ids` carries `id` (object or array per level),
|
|
324
348
|
* `input` carries the form payload.
|
|
325
349
|
*
|
|
350
|
+
* **Delegated actions** (since 0.1.147). An action another controller owns
|
|
351
|
+
* (`owner`, e.g. a view listing its source table's actions) may carry
|
|
352
|
+
* `idMap`: each `id` is then a row (or identifier) of THIS controller and
|
|
353
|
+
* is mapped to the owner's identification — `{ [ownerField]:
|
|
354
|
+
* row[path] }` — before it is sent to `value` (the owner's route). A
|
|
355
|
+
* missing path throws `TypeError`. See {@link actionIdentifier}.
|
|
356
|
+
*
|
|
326
357
|
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
327
358
|
* server returns whatever the handler emits (commonly
|
|
328
359
|
* `{ message?: string, ... }`); the client cannot validate.
|
|
@@ -332,15 +363,65 @@ var Client = class {
|
|
|
332
363
|
const action = meta.actions.find((a) => a.name === name);
|
|
333
364
|
if (!action) throw new ActionNotFoundError(name);
|
|
334
365
|
if (action.processor === "custom") throw new ActionUnsupportedError(name, "custom", `Action "${name}" has processor "custom" — applications must dispatch custom actions themselves; the client cannot.`);
|
|
366
|
+
const mapped = action.idMap ? mapDelegatedIds(action, id) : id;
|
|
335
367
|
if (action.processor === "navigate") {
|
|
336
|
-
const
|
|
368
|
+
const order = action.idMap ? Object.keys(action.idMap) : meta.preferredId;
|
|
369
|
+
const url = this._interpolateNavigateUrl(action, mapped, order);
|
|
337
370
|
await this._dispatchNavigate(action, url);
|
|
338
371
|
return;
|
|
339
372
|
}
|
|
340
|
-
const body = this._buildActionBody(action,
|
|
373
|
+
const body = this._buildActionBody(action, mapped, input);
|
|
341
374
|
return this._postAction(action, body);
|
|
342
375
|
}
|
|
343
376
|
/**
|
|
377
|
+
* Runs a `'rows'` action on every row matching a query (since 0.1.147) —
|
|
378
|
+
* the action's `/meta` entry must carry `queryTarget`. POSTs
|
|
379
|
+
* `{ query: { q, exclude?, expectCount?, maxRows? }, input? }` to
|
|
380
|
+
* `queryTarget.url` (a delegated action: the view resolves the rows and
|
|
381
|
+
* runs the owner's action in batches) or `value`. `q` is the `/query`
|
|
382
|
+
* string of `target.filter` / `search` / `index`.
|
|
383
|
+
*
|
|
384
|
+
* The server answers what the handler returns — for a delegated action
|
|
385
|
+
* (and a handler returning `target.summary()`) a
|
|
386
|
+
* {@link TDbActionTargetSummary}. Refusals arrive as
|
|
387
|
+
* {@link ActionTargetError}: `TARGET_TOO_LARGE` (`cap`), `TARGET_CHANGED`
|
|
388
|
+
* (`matched` differs from `expectCount`), `TARGET_INVALID`. Throws
|
|
389
|
+
* {@link ActionUnsupportedError} when the action takes no query target.
|
|
390
|
+
*
|
|
391
|
+
* @since 0.1.147
|
|
392
|
+
*/
|
|
393
|
+
async actionOnQuery(name, target, input) {
|
|
394
|
+
const action = await this._queryTargetAction(name);
|
|
395
|
+
const body = { query: queryTargetBody(target) };
|
|
396
|
+
if (input !== void 0) body.input = input;
|
|
397
|
+
return this._postQueryTarget(action, body);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* How many rows {@link actionOnQuery} would target right now (a dry run:
|
|
401
|
+
* the handler does not run) — pass the answer as `expectCount` to make the
|
|
402
|
+
* real run fail with `TARGET_CHANGED` if the set changed in between.
|
|
403
|
+
*
|
|
404
|
+
* @since 0.1.147
|
|
405
|
+
*/
|
|
406
|
+
async countActionTarget(name, target) {
|
|
407
|
+
const action = await this._queryTargetAction(name);
|
|
408
|
+
const query = {
|
|
409
|
+
...queryTargetBody(target),
|
|
410
|
+
dryRun: true
|
|
411
|
+
};
|
|
412
|
+
return this._postQueryTarget(action, { query });
|
|
413
|
+
}
|
|
414
|
+
async _queryTargetAction(name) {
|
|
415
|
+
const action = (await this.meta()).actions.find((a) => a.name === name);
|
|
416
|
+
if (!action) throw new ActionNotFoundError(name);
|
|
417
|
+
if (!action.queryTarget || action.processor !== "backend") throw new ActionUnsupportedError(name, action.processor, `Action "${name}" does not accept a query target (no \`queryTarget\` in /meta).`);
|
|
418
|
+
return action;
|
|
419
|
+
}
|
|
420
|
+
_postQueryTarget(action, body) {
|
|
421
|
+
const path = action.queryTarget?.url ?? action.value;
|
|
422
|
+
return this._requestUrl("POST", `${this._baseUrl}${path}`, body, true);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
344
425
|
* `GET /meta/actions/:id` or `GET /meta/actions?k1=v1&k2=v2` — the
|
|
345
426
|
* row-level actions the caller may run on one row right now, and the
|
|
346
427
|
* reasons of those disabled with one: `{ actions, disabledReasons? }`.
|
|
@@ -519,6 +600,7 @@ var Client = class {
|
|
|
519
600
|
};
|
|
520
601
|
}
|
|
521
602
|
if (errorBody.name === "ActionDisabledError") throw new ActionDisabledError(res.status, errorBody);
|
|
603
|
+
if (errorBody.name === "ActionTargetError") throw new ActionTargetError(res.status, errorBody);
|
|
522
604
|
if (errorBody.kind === "version_mismatch") throw new VersionMismatchError(res.status, errorBody);
|
|
523
605
|
throw new ClientError(res.status, errorBody);
|
|
524
606
|
}
|
|
@@ -540,6 +622,60 @@ function describeCause(cause) {
|
|
|
540
622
|
if (cause instanceof Error) return cause.message || cause.name;
|
|
541
623
|
return String(cause);
|
|
542
624
|
}
|
|
625
|
+
/** The value at a dot `path` of `row`. */
|
|
626
|
+
function valueAt(row, path) {
|
|
627
|
+
if (!path.includes(".") || Object.hasOwn(row, path)) return row[path];
|
|
628
|
+
let v = row;
|
|
629
|
+
for (const part of path.split(".")) v = v?.[part];
|
|
630
|
+
return v;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* The identifier `action` takes for one row (or identifier) of the
|
|
634
|
+
* controller whose `/meta` listed it (since 0.1.147):
|
|
635
|
+
*
|
|
636
|
+
* - a delegated action with `idMap` → the owner's identification,
|
|
637
|
+
* `{ [ownerField]: rowOrId[path] }` (dot paths allowed); a missing value
|
|
638
|
+
* throws `TypeError` naming the action and the path;
|
|
639
|
+
* - any other action → `rowOrId`'s `preferredId` fields when it carries them
|
|
640
|
+
* all, else `rowOrId` itself (already an identifier).
|
|
641
|
+
*
|
|
642
|
+
* UIs build `ids` from loaded rows with it; `Client.action()` applies the
|
|
643
|
+
* `idMap` mapping itself.
|
|
644
|
+
*
|
|
645
|
+
* @since 0.1.147
|
|
646
|
+
*/
|
|
647
|
+
function actionIdentifier(action, rowOrId, preferredId) {
|
|
648
|
+
if (action.idMap) {
|
|
649
|
+
const out = {};
|
|
650
|
+
for (const [field, path] of Object.entries(action.idMap)) {
|
|
651
|
+
const value = valueAt(rowOrId, path);
|
|
652
|
+
if (value === void 0 || value === null) throw new TypeError(`client.action("${action.name}"): the identifier has no "${path}" — needed for the owner's "${field}".`);
|
|
653
|
+
out[field] = value;
|
|
654
|
+
}
|
|
655
|
+
return out;
|
|
656
|
+
}
|
|
657
|
+
if (preferredId.length > 0 && preferredId.every((f) => rowOrId[f] !== void 0)) return Object.fromEntries(preferredId.map((f) => [f, rowOrId[f]]));
|
|
658
|
+
return rowOrId;
|
|
659
|
+
}
|
|
660
|
+
/** `action()`'s `id` argument through a delegated action's `idMap` (shape errors are left to the body builder). */
|
|
661
|
+
function mapDelegatedIds(action, id) {
|
|
662
|
+
const map = (one) => one !== null && typeof one === "object" && !Array.isArray(one) ? actionIdentifier(action, one, []) : one;
|
|
663
|
+
return Array.isArray(id) ? id.map(map) : map(id);
|
|
664
|
+
}
|
|
665
|
+
/** The wire `query` of a {@link TDbQueryTarget}. */
|
|
666
|
+
function queryTargetBody(target) {
|
|
667
|
+
const controls = {};
|
|
668
|
+
if (target.search !== void 0) controls.$search = target.search;
|
|
669
|
+
if (target.index !== void 0) controls.$index = target.index;
|
|
670
|
+
const out = { q: (0, _uniqu_url_builder.buildUrl)({
|
|
671
|
+
filter: target.filter ?? {},
|
|
672
|
+
controls
|
|
673
|
+
}) };
|
|
674
|
+
if (target.exclude?.length) out.exclude = target.exclude;
|
|
675
|
+
if (target.expectCount !== void 0) out.expectCount = target.expectCount;
|
|
676
|
+
if (target.maxRows !== void 0) out.maxRows = target.maxRows;
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
543
679
|
/**
|
|
544
680
|
* Render a single identifier field for substitution into a navigate-URL
|
|
545
681
|
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
@@ -578,11 +714,19 @@ function describeShape(value) {
|
|
|
578
714
|
//#endregion
|
|
579
715
|
exports.ActionDisabledError = ActionDisabledError;
|
|
580
716
|
exports.ActionNotFoundError = ActionNotFoundError;
|
|
717
|
+
exports.ActionTargetError = ActionTargetError;
|
|
581
718
|
exports.ActionUnsupportedError = ActionUnsupportedError;
|
|
582
719
|
exports.Client = Client;
|
|
583
720
|
exports.ClientError = ClientError;
|
|
584
721
|
exports.TransportError = TransportError;
|
|
585
722
|
exports.VersionMismatchError = VersionMismatchError;
|
|
723
|
+
exports.actionIdentifier = actionIdentifier;
|
|
724
|
+
Object.defineProperty(exports, "bucketSeries", {
|
|
725
|
+
enumerable: true,
|
|
726
|
+
get: function() {
|
|
727
|
+
return _uniqu_core.bucketSeries;
|
|
728
|
+
}
|
|
729
|
+
});
|
|
586
730
|
Object.defineProperty(exports, "bucketStartInstant", {
|
|
587
731
|
enumerable: true,
|
|
588
732
|
get: function() {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { A as TDbDeleteResult, C as OwnOf, D as RowOf, E as RelationInfo, F as
|
|
2
|
-
import { AggregateExpr, AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1, ValidGroupBy as ValidGroupBy$1, bucketStartInstant, nextBucketLabel } from "@uniqu/core";
|
|
1
|
+
import { A as TDbDeleteResult, C as OwnOf, D as RowOf, E as RelationInfo, F as TypedWithRelation, I as Uniquery, L as UniqueryControls, M as TDbInsertResult, N as TDbQueryTarget, O as SearchIndexInfo, P as TDbUpdateResult, R as ValidGroupBy, S as NavOf, T as PatchOf, _ as DbRow, a as ValidatorMode, b as IdOf, c as AggregateResult, d as BucketUnit, f as CalendarBucketLabel, g as DbPatch, h as DataOf, j as TDbInsertManyResult, k as ServerError, l as AtscriptClientShape, m as ClientResponse, n as ClientValidator, p as ClientOptions, r as ClientValidatorOptions, s as AggregateQuery, t as ClientValidationError, u as BucketExpr, v as FieldMeta, w as PageResult, x as MetaResponse, y as FilterExpr, z as WeekStart } from "./validator-DVha6LOQ.cjs";
|
|
2
|
+
import { AggregateExpr, AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, BucketSeriesOptions, NextBucketOptions, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1, ValidGroupBy as ValidGroupBy$1, bucketSeries, bucketStartInstant, nextBucketLabel } from "@uniqu/core";
|
|
3
3
|
import { TAtscriptAnnotatedType, TSerializedAnnotatedType } from "@atscript/typescript/utils";
|
|
4
|
-
import { TCrudOp, TCrudPermissions, TDbActionInfo, TDbActionIntent, TDbActionLevel, TDbActionProcessor, TDbAvailableActions, TDbAvailableActions as TDbAvailableActions$1, TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1 } from "@atscript/db";
|
|
4
|
+
import { TCrudOp, TCrudPermissions, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionLevel, TDbActionProcessor, TDbActionTargetSummary, TDbActionTargetSummary as TDbActionTargetSummary$1, TDbAvailableActions, TDbAvailableActions as TDbAvailableActions$1, TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1 } from "@atscript/db";
|
|
5
5
|
|
|
6
6
|
//#region src/client.d.ts
|
|
7
7
|
type Own<T> = OwnOf<T>;
|
|
@@ -59,7 +59,8 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
59
59
|
*
|
|
60
60
|
* `$select` may carry calendar buckets (`{ $bucket, $field, $tz?, $weekStart?, $as? }`);
|
|
61
61
|
* a `$groupBy` entry must be a dimension or a bucket alias (`ValidGroupBy`), and a
|
|
62
|
-
* bucket's value is typed as its `YYYY-MM-DD`
|
|
62
|
+
* bucket's value is typed as its `YYYY-MM-DD` (hour: `YYYY-MM-DDTHH:00`) label
|
|
63
|
+
* (`| null` for an optional source).
|
|
63
64
|
* Gap-fill between labels with `nextBucketLabel` (re-exported here).
|
|
64
65
|
*/
|
|
65
66
|
aggregate<const Q extends AggregateQuery$1<Own<T>>>(query: Q & ValidGroupBy$1<Own<T>, Q>): Promise<Q["controls"]["$select"] extends readonly (string | AggregateExpr | BucketExpr$1)[] ? AggregateResult$1<Own<T>, Q["controls"]["$select"]>[] : Record<string, unknown>[]>;
|
|
@@ -166,11 +167,48 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
166
167
|
* `{ ids?, input? }` — `ids` carries `id` (object or array per level),
|
|
167
168
|
* `input` carries the form payload.
|
|
168
169
|
*
|
|
170
|
+
* **Delegated actions** (since 0.1.147). An action another controller owns
|
|
171
|
+
* (`owner`, e.g. a view listing its source table's actions) may carry
|
|
172
|
+
* `idMap`: each `id` is then a row (or identifier) of THIS controller and
|
|
173
|
+
* is mapped to the owner's identification — `{ [ownerField]:
|
|
174
|
+
* row[path] }` — before it is sent to `value` (the owner's route). A
|
|
175
|
+
* missing path throws `TypeError`. See {@link actionIdentifier}.
|
|
176
|
+
*
|
|
169
177
|
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
170
178
|
* server returns whatever the handler emits (commonly
|
|
171
179
|
* `{ message?: string, ... }`); the client cannot validate.
|
|
172
180
|
*/
|
|
173
181
|
action<R = unknown>(name: string, id?: Partial<Own<T>> | Partial<Own<T>>[], input?: unknown): Promise<R>;
|
|
182
|
+
/**
|
|
183
|
+
* Runs a `'rows'` action on every row matching a query (since 0.1.147) —
|
|
184
|
+
* the action's `/meta` entry must carry `queryTarget`. POSTs
|
|
185
|
+
* `{ query: { q, exclude?, expectCount?, maxRows? }, input? }` to
|
|
186
|
+
* `queryTarget.url` (a delegated action: the view resolves the rows and
|
|
187
|
+
* runs the owner's action in batches) or `value`. `q` is the `/query`
|
|
188
|
+
* string of `target.filter` / `search` / `index`.
|
|
189
|
+
*
|
|
190
|
+
* The server answers what the handler returns — for a delegated action
|
|
191
|
+
* (and a handler returning `target.summary()`) a
|
|
192
|
+
* {@link TDbActionTargetSummary}. Refusals arrive as
|
|
193
|
+
* {@link ActionTargetError}: `TARGET_TOO_LARGE` (`cap`), `TARGET_CHANGED`
|
|
194
|
+
* (`matched` differs from `expectCount`), `TARGET_INVALID`. Throws
|
|
195
|
+
* {@link ActionUnsupportedError} when the action takes no query target.
|
|
196
|
+
*
|
|
197
|
+
* @since 0.1.147
|
|
198
|
+
*/
|
|
199
|
+
actionOnQuery<R = TDbActionTargetSummary$1>(name: string, target: TDbQueryTarget<T>, input?: unknown): Promise<R>;
|
|
200
|
+
/**
|
|
201
|
+
* How many rows {@link actionOnQuery} would target right now (a dry run:
|
|
202
|
+
* the handler does not run) — pass the answer as `expectCount` to make the
|
|
203
|
+
* real run fail with `TARGET_CHANGED` if the set changed in between.
|
|
204
|
+
*
|
|
205
|
+
* @since 0.1.147
|
|
206
|
+
*/
|
|
207
|
+
countActionTarget(name: string, target: TDbQueryTarget<T>): Promise<{
|
|
208
|
+
matched: number;
|
|
209
|
+
}>;
|
|
210
|
+
private _queryTargetAction;
|
|
211
|
+
private _postQueryTarget;
|
|
174
212
|
/**
|
|
175
213
|
* `GET /meta/actions/:id` or `GET /meta/actions?k1=v1&k2=v2` — the
|
|
176
214
|
* row-level actions the caller may run on one row right now, and the
|
|
@@ -224,6 +262,22 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
224
262
|
private _requestUrl;
|
|
225
263
|
private _send;
|
|
226
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* The identifier `action` takes for one row (or identifier) of the
|
|
267
|
+
* controller whose `/meta` listed it (since 0.1.147):
|
|
268
|
+
*
|
|
269
|
+
* - a delegated action with `idMap` → the owner's identification,
|
|
270
|
+
* `{ [ownerField]: rowOrId[path] }` (dot paths allowed); a missing value
|
|
271
|
+
* throws `TypeError` naming the action and the path;
|
|
272
|
+
* - any other action → `rowOrId`'s `preferredId` fields when it carries them
|
|
273
|
+
* all, else `rowOrId` itself (already an identifier).
|
|
274
|
+
*
|
|
275
|
+
* UIs build `ids` from loaded rows with it; `Client.action()` applies the
|
|
276
|
+
* `idMap` mapping itself.
|
|
277
|
+
*
|
|
278
|
+
* @since 0.1.147
|
|
279
|
+
*/
|
|
280
|
+
declare function actionIdentifier(action: Pick<TDbActionInfo$1, "name" | "idMap">, rowOrId: Record<string, unknown>, preferredId: readonly string[]): Record<string, unknown>;
|
|
227
281
|
/**
|
|
228
282
|
* Render a single identifier field for substitution into a navigate-URL
|
|
229
283
|
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
@@ -318,6 +372,34 @@ declare class ActionDisabledError extends ClientError {
|
|
|
318
372
|
*/
|
|
319
373
|
get reasons(): (string | null)[] | undefined;
|
|
320
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Wire-body shape for `ActionTargetError` responses (since 0.1.147): a
|
|
377
|
+
* query target the server refused — `code` says why.
|
|
378
|
+
*/
|
|
379
|
+
interface ActionTargetErrorBody extends ServerError {
|
|
380
|
+
name: "ActionTargetError";
|
|
381
|
+
code: "TARGET_INVALID" | "TARGET_TOO_LARGE" | "TARGET_CHANGED";
|
|
382
|
+
action: string;
|
|
383
|
+
/** `TARGET_CHANGED`: the current match count. */
|
|
384
|
+
matched?: number;
|
|
385
|
+
/** `TARGET_TOO_LARGE`: the most rows one request may target. */
|
|
386
|
+
cap?: number;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Typed marker thrown by `Client` when a query-targeted action request is
|
|
390
|
+
* refused (body `name === 'ActionTargetError'`): `TARGET_INVALID` (400),
|
|
391
|
+
* `TARGET_TOO_LARGE` (400, `cap`) or `TARGET_CHANGED` (409, `matched` — the
|
|
392
|
+
* rows the query matches now; re-confirm and retry with that count).
|
|
393
|
+
*
|
|
394
|
+
* @since 0.1.147
|
|
395
|
+
*/
|
|
396
|
+
declare class ActionTargetError extends ClientError {
|
|
397
|
+
name: string;
|
|
398
|
+
get code(): ActionTargetErrorBody["code"];
|
|
399
|
+
get action(): string;
|
|
400
|
+
get matched(): number | undefined;
|
|
401
|
+
get cap(): number | undefined;
|
|
402
|
+
}
|
|
321
403
|
/**
|
|
322
404
|
* Wire-body shape for 409 OCC `version_mismatch` responses. Extends the base
|
|
323
405
|
* `ServerError` envelope with a `kind` discriminator and the row's current
|
|
@@ -382,4 +464,4 @@ declare class ActionUnsupportedError extends Error {
|
|
|
382
464
|
constructor(action: string, processor: string, message: string);
|
|
383
465
|
}
|
|
384
466
|
//#endregion
|
|
385
|
-
export { ActionDisabledError, type ActionDisabledErrorBody, ActionNotFoundError, ActionUnsupportedError, type AggregateQuery, type AggregateResult, type AtscriptClientShape, type BucketExpr, type BucketUnit, type CalendarBucketLabel, Client, ClientError, type ClientOptions, type ClientResponse, type ClientValidationError, type ClientValidator, type ClientValidatorOptions, type DataOf, type DbPatch, type DbRow, type FieldMeta, type FilterExpr, type IdOf, type MetaResponse, type NavOf, type OwnOf, type PageResult, type PatchOf, type RelationInfo, type RowOf, type SearchIndexInfo, type ServerError, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbAvailableActions, type TDbDeleteResult, type TDbInsertManyResult, type TDbInsertResult, type TDbUpdateResult, type TSerializedAnnotatedType, TransportError, type TypedWithRelation, type Uniquery, type UniqueryControls, type ValidGroupBy, type ValidatorMode, VersionMismatchError, type VersionMismatchErrorBody, type WeekStart, bucketStartInstant, encodeNavigateId, formatIdentifier, formatIdentifierField, nextBucketLabel };
|
|
467
|
+
export { ActionDisabledError, type ActionDisabledErrorBody, ActionNotFoundError, ActionTargetError, type ActionTargetErrorBody, ActionUnsupportedError, type AggregateQuery, type AggregateResult, type AtscriptClientShape, type BucketExpr, type BucketSeriesOptions, type BucketUnit, type CalendarBucketLabel, Client, ClientError, type ClientOptions, type ClientResponse, type ClientValidationError, type ClientValidator, type ClientValidatorOptions, type DataOf, type DbPatch, type DbRow, type FieldMeta, type FilterExpr, type IdOf, type MetaResponse, type NavOf, type NextBucketOptions, type OwnOf, type PageResult, type PatchOf, type RelationInfo, type RowOf, type SearchIndexInfo, type ServerError, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbActionTargetSummary, type TDbAvailableActions, type TDbDeleteResult, type TDbInsertManyResult, type TDbInsertResult, type TDbQueryTarget, type TDbUpdateResult, type TSerializedAnnotatedType, TransportError, type TypedWithRelation, type Uniquery, type UniqueryControls, type ValidGroupBy, type ValidatorMode, VersionMismatchError, type VersionMismatchErrorBody, type WeekStart, actionIdentifier, bucketSeries, bucketStartInstant, encodeNavigateId, formatIdentifier, formatIdentifierField, nextBucketLabel };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { A as TDbDeleteResult, C as OwnOf, D as RowOf, E as RelationInfo, F as
|
|
1
|
+
import { A as TDbDeleteResult, C as OwnOf, D as RowOf, E as RelationInfo, F as TypedWithRelation, I as Uniquery, L as UniqueryControls, M as TDbInsertResult, N as TDbQueryTarget, O as SearchIndexInfo, P as TDbUpdateResult, R as ValidGroupBy, S as NavOf, T as PatchOf, _ as DbRow, a as ValidatorMode, b as IdOf, c as AggregateResult, d as BucketUnit, f as CalendarBucketLabel, g as DbPatch, h as DataOf, j as TDbInsertManyResult, k as ServerError, l as AtscriptClientShape, m as ClientResponse, n as ClientValidator, p as ClientOptions, r as ClientValidatorOptions, s as AggregateQuery, t as ClientValidationError, u as BucketExpr, v as FieldMeta, w as PageResult, x as MetaResponse, y as FilterExpr, z as WeekStart } from "./validator-xkEq4h0X.mjs";
|
|
2
2
|
import { TAtscriptAnnotatedType, TSerializedAnnotatedType } from "@atscript/typescript/utils";
|
|
3
|
-
import { AggregateExpr, AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1, ValidGroupBy as ValidGroupBy$1, bucketStartInstant, nextBucketLabel } from "@uniqu/core";
|
|
4
|
-
import { TCrudOp, TCrudPermissions, TDbActionInfo, TDbActionIntent, TDbActionLevel, TDbActionProcessor, TDbAvailableActions, TDbAvailableActions as TDbAvailableActions$1, TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1 } from "@atscript/db";
|
|
3
|
+
import { AggregateExpr, AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, BucketSeriesOptions, NextBucketOptions, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1, ValidGroupBy as ValidGroupBy$1, bucketSeries, bucketStartInstant, nextBucketLabel } from "@uniqu/core";
|
|
4
|
+
import { TCrudOp, TCrudPermissions, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionLevel, TDbActionProcessor, TDbActionTargetSummary, TDbActionTargetSummary as TDbActionTargetSummary$1, TDbAvailableActions, TDbAvailableActions as TDbAvailableActions$1, TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1 } from "@atscript/db";
|
|
5
5
|
|
|
6
6
|
//#region src/client.d.ts
|
|
7
7
|
type Own<T> = OwnOf<T>;
|
|
@@ -59,7 +59,8 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
59
59
|
*
|
|
60
60
|
* `$select` may carry calendar buckets (`{ $bucket, $field, $tz?, $weekStart?, $as? }`);
|
|
61
61
|
* a `$groupBy` entry must be a dimension or a bucket alias (`ValidGroupBy`), and a
|
|
62
|
-
* bucket's value is typed as its `YYYY-MM-DD`
|
|
62
|
+
* bucket's value is typed as its `YYYY-MM-DD` (hour: `YYYY-MM-DDTHH:00`) label
|
|
63
|
+
* (`| null` for an optional source).
|
|
63
64
|
* Gap-fill between labels with `nextBucketLabel` (re-exported here).
|
|
64
65
|
*/
|
|
65
66
|
aggregate<const Q extends AggregateQuery$1<Own<T>>>(query: Q & ValidGroupBy$1<Own<T>, Q>): Promise<Q["controls"]["$select"] extends readonly (string | AggregateExpr | BucketExpr$1)[] ? AggregateResult$1<Own<T>, Q["controls"]["$select"]>[] : Record<string, unknown>[]>;
|
|
@@ -166,11 +167,48 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
166
167
|
* `{ ids?, input? }` — `ids` carries `id` (object or array per level),
|
|
167
168
|
* `input` carries the form payload.
|
|
168
169
|
*
|
|
170
|
+
* **Delegated actions** (since 0.1.147). An action another controller owns
|
|
171
|
+
* (`owner`, e.g. a view listing its source table's actions) may carry
|
|
172
|
+
* `idMap`: each `id` is then a row (or identifier) of THIS controller and
|
|
173
|
+
* is mapped to the owner's identification — `{ [ownerField]:
|
|
174
|
+
* row[path] }` — before it is sent to `value` (the owner's route). A
|
|
175
|
+
* missing path throws `TypeError`. See {@link actionIdentifier}.
|
|
176
|
+
*
|
|
169
177
|
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
170
178
|
* server returns whatever the handler emits (commonly
|
|
171
179
|
* `{ message?: string, ... }`); the client cannot validate.
|
|
172
180
|
*/
|
|
173
181
|
action<R = unknown>(name: string, id?: Partial<Own<T>> | Partial<Own<T>>[], input?: unknown): Promise<R>;
|
|
182
|
+
/**
|
|
183
|
+
* Runs a `'rows'` action on every row matching a query (since 0.1.147) —
|
|
184
|
+
* the action's `/meta` entry must carry `queryTarget`. POSTs
|
|
185
|
+
* `{ query: { q, exclude?, expectCount?, maxRows? }, input? }` to
|
|
186
|
+
* `queryTarget.url` (a delegated action: the view resolves the rows and
|
|
187
|
+
* runs the owner's action in batches) or `value`. `q` is the `/query`
|
|
188
|
+
* string of `target.filter` / `search` / `index`.
|
|
189
|
+
*
|
|
190
|
+
* The server answers what the handler returns — for a delegated action
|
|
191
|
+
* (and a handler returning `target.summary()`) a
|
|
192
|
+
* {@link TDbActionTargetSummary}. Refusals arrive as
|
|
193
|
+
* {@link ActionTargetError}: `TARGET_TOO_LARGE` (`cap`), `TARGET_CHANGED`
|
|
194
|
+
* (`matched` differs from `expectCount`), `TARGET_INVALID`. Throws
|
|
195
|
+
* {@link ActionUnsupportedError} when the action takes no query target.
|
|
196
|
+
*
|
|
197
|
+
* @since 0.1.147
|
|
198
|
+
*/
|
|
199
|
+
actionOnQuery<R = TDbActionTargetSummary$1>(name: string, target: TDbQueryTarget<T>, input?: unknown): Promise<R>;
|
|
200
|
+
/**
|
|
201
|
+
* How many rows {@link actionOnQuery} would target right now (a dry run:
|
|
202
|
+
* the handler does not run) — pass the answer as `expectCount` to make the
|
|
203
|
+
* real run fail with `TARGET_CHANGED` if the set changed in between.
|
|
204
|
+
*
|
|
205
|
+
* @since 0.1.147
|
|
206
|
+
*/
|
|
207
|
+
countActionTarget(name: string, target: TDbQueryTarget<T>): Promise<{
|
|
208
|
+
matched: number;
|
|
209
|
+
}>;
|
|
210
|
+
private _queryTargetAction;
|
|
211
|
+
private _postQueryTarget;
|
|
174
212
|
/**
|
|
175
213
|
* `GET /meta/actions/:id` or `GET /meta/actions?k1=v1&k2=v2` — the
|
|
176
214
|
* row-level actions the caller may run on one row right now, and the
|
|
@@ -224,6 +262,22 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
224
262
|
private _requestUrl;
|
|
225
263
|
private _send;
|
|
226
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* The identifier `action` takes for one row (or identifier) of the
|
|
267
|
+
* controller whose `/meta` listed it (since 0.1.147):
|
|
268
|
+
*
|
|
269
|
+
* - a delegated action with `idMap` → the owner's identification,
|
|
270
|
+
* `{ [ownerField]: rowOrId[path] }` (dot paths allowed); a missing value
|
|
271
|
+
* throws `TypeError` naming the action and the path;
|
|
272
|
+
* - any other action → `rowOrId`'s `preferredId` fields when it carries them
|
|
273
|
+
* all, else `rowOrId` itself (already an identifier).
|
|
274
|
+
*
|
|
275
|
+
* UIs build `ids` from loaded rows with it; `Client.action()` applies the
|
|
276
|
+
* `idMap` mapping itself.
|
|
277
|
+
*
|
|
278
|
+
* @since 0.1.147
|
|
279
|
+
*/
|
|
280
|
+
declare function actionIdentifier(action: Pick<TDbActionInfo$1, "name" | "idMap">, rowOrId: Record<string, unknown>, preferredId: readonly string[]): Record<string, unknown>;
|
|
227
281
|
/**
|
|
228
282
|
* Render a single identifier field for substitution into a navigate-URL
|
|
229
283
|
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
@@ -318,6 +372,34 @@ declare class ActionDisabledError extends ClientError {
|
|
|
318
372
|
*/
|
|
319
373
|
get reasons(): (string | null)[] | undefined;
|
|
320
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Wire-body shape for `ActionTargetError` responses (since 0.1.147): a
|
|
377
|
+
* query target the server refused — `code` says why.
|
|
378
|
+
*/
|
|
379
|
+
interface ActionTargetErrorBody extends ServerError {
|
|
380
|
+
name: "ActionTargetError";
|
|
381
|
+
code: "TARGET_INVALID" | "TARGET_TOO_LARGE" | "TARGET_CHANGED";
|
|
382
|
+
action: string;
|
|
383
|
+
/** `TARGET_CHANGED`: the current match count. */
|
|
384
|
+
matched?: number;
|
|
385
|
+
/** `TARGET_TOO_LARGE`: the most rows one request may target. */
|
|
386
|
+
cap?: number;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Typed marker thrown by `Client` when a query-targeted action request is
|
|
390
|
+
* refused (body `name === 'ActionTargetError'`): `TARGET_INVALID` (400),
|
|
391
|
+
* `TARGET_TOO_LARGE` (400, `cap`) or `TARGET_CHANGED` (409, `matched` — the
|
|
392
|
+
* rows the query matches now; re-confirm and retry with that count).
|
|
393
|
+
*
|
|
394
|
+
* @since 0.1.147
|
|
395
|
+
*/
|
|
396
|
+
declare class ActionTargetError extends ClientError {
|
|
397
|
+
name: string;
|
|
398
|
+
get code(): ActionTargetErrorBody["code"];
|
|
399
|
+
get action(): string;
|
|
400
|
+
get matched(): number | undefined;
|
|
401
|
+
get cap(): number | undefined;
|
|
402
|
+
}
|
|
321
403
|
/**
|
|
322
404
|
* Wire-body shape for 409 OCC `version_mismatch` responses. Extends the base
|
|
323
405
|
* `ServerError` envelope with a `kind` discriminator and the row's current
|
|
@@ -382,4 +464,4 @@ declare class ActionUnsupportedError extends Error {
|
|
|
382
464
|
constructor(action: string, processor: string, message: string);
|
|
383
465
|
}
|
|
384
466
|
//#endregion
|
|
385
|
-
export { ActionDisabledError, type ActionDisabledErrorBody, ActionNotFoundError, ActionUnsupportedError, type AggregateQuery, type AggregateResult, type AtscriptClientShape, type BucketExpr, type BucketUnit, type CalendarBucketLabel, Client, ClientError, type ClientOptions, type ClientResponse, type ClientValidationError, type ClientValidator, type ClientValidatorOptions, type DataOf, type DbPatch, type DbRow, type FieldMeta, type FilterExpr, type IdOf, type MetaResponse, type NavOf, type OwnOf, type PageResult, type PatchOf, type RelationInfo, type RowOf, type SearchIndexInfo, type ServerError, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbAvailableActions, type TDbDeleteResult, type TDbInsertManyResult, type TDbInsertResult, type TDbUpdateResult, type TSerializedAnnotatedType, TransportError, type TypedWithRelation, type Uniquery, type UniqueryControls, type ValidGroupBy, type ValidatorMode, VersionMismatchError, type VersionMismatchErrorBody, type WeekStart, bucketStartInstant, encodeNavigateId, formatIdentifier, formatIdentifierField, nextBucketLabel };
|
|
467
|
+
export { ActionDisabledError, type ActionDisabledErrorBody, ActionNotFoundError, ActionTargetError, type ActionTargetErrorBody, ActionUnsupportedError, type AggregateQuery, type AggregateResult, type AtscriptClientShape, type BucketExpr, type BucketSeriesOptions, type BucketUnit, type CalendarBucketLabel, Client, ClientError, type ClientOptions, type ClientResponse, type ClientValidationError, type ClientValidator, type ClientValidatorOptions, type DataOf, type DbPatch, type DbRow, type FieldMeta, type FilterExpr, type IdOf, type MetaResponse, type NavOf, type NextBucketOptions, type OwnOf, type PageResult, type PatchOf, type RelationInfo, type RowOf, type SearchIndexInfo, type ServerError, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbActionTargetSummary, type TDbAvailableActions, type TDbDeleteResult, type TDbInsertManyResult, type TDbInsertResult, type TDbQueryTarget, type TDbUpdateResult, type TSerializedAnnotatedType, TransportError, type TypedWithRelation, type Uniquery, type UniqueryControls, type ValidGroupBy, type ValidatorMode, VersionMismatchError, type VersionMismatchErrorBody, type WeekStart, actionIdentifier, bucketSeries, bucketStartInstant, encodeNavigateId, formatIdentifier, formatIdentifierField, nextBucketLabel };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildUrl } from "@uniqu/url/builder";
|
|
2
2
|
import { deserializeAnnotatedType } from "@atscript/typescript/utils";
|
|
3
|
-
import { bucketStartInstant, nextBucketLabel } from "@uniqu/core";
|
|
3
|
+
import { bucketSeries, bucketStartInstant, nextBucketLabel } from "@uniqu/core";
|
|
4
4
|
//#region src/client-error.ts
|
|
5
5
|
/**
|
|
6
6
|
* Error thrown by `Client` when the server responds with a non-2xx status code.
|
|
@@ -64,6 +64,29 @@ var ActionDisabledError = class extends ClientError {
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
/**
|
|
67
|
+
* Typed marker thrown by `Client` when a query-targeted action request is
|
|
68
|
+
* refused (body `name === 'ActionTargetError'`): `TARGET_INVALID` (400),
|
|
69
|
+
* `TARGET_TOO_LARGE` (400, `cap`) or `TARGET_CHANGED` (409, `matched` — the
|
|
70
|
+
* rows the query matches now; re-confirm and retry with that count).
|
|
71
|
+
*
|
|
72
|
+
* @since 0.1.147
|
|
73
|
+
*/
|
|
74
|
+
var ActionTargetError = class extends ClientError {
|
|
75
|
+
name = "ActionTargetError";
|
|
76
|
+
get code() {
|
|
77
|
+
return this.body.code;
|
|
78
|
+
}
|
|
79
|
+
get action() {
|
|
80
|
+
return this.body.action;
|
|
81
|
+
}
|
|
82
|
+
get matched() {
|
|
83
|
+
return this.body.matched;
|
|
84
|
+
}
|
|
85
|
+
get cap() {
|
|
86
|
+
return this.body.cap;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
67
90
|
* Typed marker thrown by `Client._send` when the server response body's
|
|
68
91
|
* `kind === 'version_mismatch'`. The transport / status / base body are
|
|
69
92
|
* identical to a generic `ClientError`; this subclass adds a typed
|
|
@@ -185,7 +208,8 @@ var Client = class {
|
|
|
185
208
|
*
|
|
186
209
|
* `$select` may carry calendar buckets (`{ $bucket, $field, $tz?, $weekStart?, $as? }`);
|
|
187
210
|
* a `$groupBy` entry must be a dimension or a bucket alias (`ValidGroupBy`), and a
|
|
188
|
-
* bucket's value is typed as its `YYYY-MM-DD`
|
|
211
|
+
* bucket's value is typed as its `YYYY-MM-DD` (hour: `YYYY-MM-DDTHH:00`) label
|
|
212
|
+
* (`| null` for an optional source).
|
|
189
213
|
* Gap-fill between labels with `nextBucketLabel` (re-exported here).
|
|
190
214
|
*/
|
|
191
215
|
async aggregate(query) {
|
|
@@ -322,6 +346,13 @@ var Client = class {
|
|
|
322
346
|
* `{ ids?, input? }` — `ids` carries `id` (object or array per level),
|
|
323
347
|
* `input` carries the form payload.
|
|
324
348
|
*
|
|
349
|
+
* **Delegated actions** (since 0.1.147). An action another controller owns
|
|
350
|
+
* (`owner`, e.g. a view listing its source table's actions) may carry
|
|
351
|
+
* `idMap`: each `id` is then a row (or identifier) of THIS controller and
|
|
352
|
+
* is mapped to the owner's identification — `{ [ownerField]:
|
|
353
|
+
* row[path] }` — before it is sent to `value` (the owner's route). A
|
|
354
|
+
* missing path throws `TypeError`. See {@link actionIdentifier}.
|
|
355
|
+
*
|
|
325
356
|
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
326
357
|
* server returns whatever the handler emits (commonly
|
|
327
358
|
* `{ message?: string, ... }`); the client cannot validate.
|
|
@@ -331,15 +362,65 @@ var Client = class {
|
|
|
331
362
|
const action = meta.actions.find((a) => a.name === name);
|
|
332
363
|
if (!action) throw new ActionNotFoundError(name);
|
|
333
364
|
if (action.processor === "custom") throw new ActionUnsupportedError(name, "custom", `Action "${name}" has processor "custom" — applications must dispatch custom actions themselves; the client cannot.`);
|
|
365
|
+
const mapped = action.idMap ? mapDelegatedIds(action, id) : id;
|
|
334
366
|
if (action.processor === "navigate") {
|
|
335
|
-
const
|
|
367
|
+
const order = action.idMap ? Object.keys(action.idMap) : meta.preferredId;
|
|
368
|
+
const url = this._interpolateNavigateUrl(action, mapped, order);
|
|
336
369
|
await this._dispatchNavigate(action, url);
|
|
337
370
|
return;
|
|
338
371
|
}
|
|
339
|
-
const body = this._buildActionBody(action,
|
|
372
|
+
const body = this._buildActionBody(action, mapped, input);
|
|
340
373
|
return this._postAction(action, body);
|
|
341
374
|
}
|
|
342
375
|
/**
|
|
376
|
+
* Runs a `'rows'` action on every row matching a query (since 0.1.147) —
|
|
377
|
+
* the action's `/meta` entry must carry `queryTarget`. POSTs
|
|
378
|
+
* `{ query: { q, exclude?, expectCount?, maxRows? }, input? }` to
|
|
379
|
+
* `queryTarget.url` (a delegated action: the view resolves the rows and
|
|
380
|
+
* runs the owner's action in batches) or `value`. `q` is the `/query`
|
|
381
|
+
* string of `target.filter` / `search` / `index`.
|
|
382
|
+
*
|
|
383
|
+
* The server answers what the handler returns — for a delegated action
|
|
384
|
+
* (and a handler returning `target.summary()`) a
|
|
385
|
+
* {@link TDbActionTargetSummary}. Refusals arrive as
|
|
386
|
+
* {@link ActionTargetError}: `TARGET_TOO_LARGE` (`cap`), `TARGET_CHANGED`
|
|
387
|
+
* (`matched` differs from `expectCount`), `TARGET_INVALID`. Throws
|
|
388
|
+
* {@link ActionUnsupportedError} when the action takes no query target.
|
|
389
|
+
*
|
|
390
|
+
* @since 0.1.147
|
|
391
|
+
*/
|
|
392
|
+
async actionOnQuery(name, target, input) {
|
|
393
|
+
const action = await this._queryTargetAction(name);
|
|
394
|
+
const body = { query: queryTargetBody(target) };
|
|
395
|
+
if (input !== void 0) body.input = input;
|
|
396
|
+
return this._postQueryTarget(action, body);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* How many rows {@link actionOnQuery} would target right now (a dry run:
|
|
400
|
+
* the handler does not run) — pass the answer as `expectCount` to make the
|
|
401
|
+
* real run fail with `TARGET_CHANGED` if the set changed in between.
|
|
402
|
+
*
|
|
403
|
+
* @since 0.1.147
|
|
404
|
+
*/
|
|
405
|
+
async countActionTarget(name, target) {
|
|
406
|
+
const action = await this._queryTargetAction(name);
|
|
407
|
+
const query = {
|
|
408
|
+
...queryTargetBody(target),
|
|
409
|
+
dryRun: true
|
|
410
|
+
};
|
|
411
|
+
return this._postQueryTarget(action, { query });
|
|
412
|
+
}
|
|
413
|
+
async _queryTargetAction(name) {
|
|
414
|
+
const action = (await this.meta()).actions.find((a) => a.name === name);
|
|
415
|
+
if (!action) throw new ActionNotFoundError(name);
|
|
416
|
+
if (!action.queryTarget || action.processor !== "backend") throw new ActionUnsupportedError(name, action.processor, `Action "${name}" does not accept a query target (no \`queryTarget\` in /meta).`);
|
|
417
|
+
return action;
|
|
418
|
+
}
|
|
419
|
+
_postQueryTarget(action, body) {
|
|
420
|
+
const path = action.queryTarget?.url ?? action.value;
|
|
421
|
+
return this._requestUrl("POST", `${this._baseUrl}${path}`, body, true);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
343
424
|
* `GET /meta/actions/:id` or `GET /meta/actions?k1=v1&k2=v2` — the
|
|
344
425
|
* row-level actions the caller may run on one row right now, and the
|
|
345
426
|
* reasons of those disabled with one: `{ actions, disabledReasons? }`.
|
|
@@ -518,6 +599,7 @@ var Client = class {
|
|
|
518
599
|
};
|
|
519
600
|
}
|
|
520
601
|
if (errorBody.name === "ActionDisabledError") throw new ActionDisabledError(res.status, errorBody);
|
|
602
|
+
if (errorBody.name === "ActionTargetError") throw new ActionTargetError(res.status, errorBody);
|
|
521
603
|
if (errorBody.kind === "version_mismatch") throw new VersionMismatchError(res.status, errorBody);
|
|
522
604
|
throw new ClientError(res.status, errorBody);
|
|
523
605
|
}
|
|
@@ -539,6 +621,60 @@ function describeCause(cause) {
|
|
|
539
621
|
if (cause instanceof Error) return cause.message || cause.name;
|
|
540
622
|
return String(cause);
|
|
541
623
|
}
|
|
624
|
+
/** The value at a dot `path` of `row`. */
|
|
625
|
+
function valueAt(row, path) {
|
|
626
|
+
if (!path.includes(".") || Object.hasOwn(row, path)) return row[path];
|
|
627
|
+
let v = row;
|
|
628
|
+
for (const part of path.split(".")) v = v?.[part];
|
|
629
|
+
return v;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* The identifier `action` takes for one row (or identifier) of the
|
|
633
|
+
* controller whose `/meta` listed it (since 0.1.147):
|
|
634
|
+
*
|
|
635
|
+
* - a delegated action with `idMap` → the owner's identification,
|
|
636
|
+
* `{ [ownerField]: rowOrId[path] }` (dot paths allowed); a missing value
|
|
637
|
+
* throws `TypeError` naming the action and the path;
|
|
638
|
+
* - any other action → `rowOrId`'s `preferredId` fields when it carries them
|
|
639
|
+
* all, else `rowOrId` itself (already an identifier).
|
|
640
|
+
*
|
|
641
|
+
* UIs build `ids` from loaded rows with it; `Client.action()` applies the
|
|
642
|
+
* `idMap` mapping itself.
|
|
643
|
+
*
|
|
644
|
+
* @since 0.1.147
|
|
645
|
+
*/
|
|
646
|
+
function actionIdentifier(action, rowOrId, preferredId) {
|
|
647
|
+
if (action.idMap) {
|
|
648
|
+
const out = {};
|
|
649
|
+
for (const [field, path] of Object.entries(action.idMap)) {
|
|
650
|
+
const value = valueAt(rowOrId, path);
|
|
651
|
+
if (value === void 0 || value === null) throw new TypeError(`client.action("${action.name}"): the identifier has no "${path}" — needed for the owner's "${field}".`);
|
|
652
|
+
out[field] = value;
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
656
|
+
if (preferredId.length > 0 && preferredId.every((f) => rowOrId[f] !== void 0)) return Object.fromEntries(preferredId.map((f) => [f, rowOrId[f]]));
|
|
657
|
+
return rowOrId;
|
|
658
|
+
}
|
|
659
|
+
/** `action()`'s `id` argument through a delegated action's `idMap` (shape errors are left to the body builder). */
|
|
660
|
+
function mapDelegatedIds(action, id) {
|
|
661
|
+
const map = (one) => one !== null && typeof one === "object" && !Array.isArray(one) ? actionIdentifier(action, one, []) : one;
|
|
662
|
+
return Array.isArray(id) ? id.map(map) : map(id);
|
|
663
|
+
}
|
|
664
|
+
/** The wire `query` of a {@link TDbQueryTarget}. */
|
|
665
|
+
function queryTargetBody(target) {
|
|
666
|
+
const controls = {};
|
|
667
|
+
if (target.search !== void 0) controls.$search = target.search;
|
|
668
|
+
if (target.index !== void 0) controls.$index = target.index;
|
|
669
|
+
const out = { q: buildUrl({
|
|
670
|
+
filter: target.filter ?? {},
|
|
671
|
+
controls
|
|
672
|
+
}) };
|
|
673
|
+
if (target.exclude?.length) out.exclude = target.exclude;
|
|
674
|
+
if (target.expectCount !== void 0) out.expectCount = target.expectCount;
|
|
675
|
+
if (target.maxRows !== void 0) out.maxRows = target.maxRows;
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
542
678
|
/**
|
|
543
679
|
* Render a single identifier field for substitution into a navigate-URL
|
|
544
680
|
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
@@ -575,4 +711,4 @@ function describeShape(value) {
|
|
|
575
711
|
return typeof value;
|
|
576
712
|
}
|
|
577
713
|
//#endregion
|
|
578
|
-
export { ActionDisabledError, ActionNotFoundError, ActionUnsupportedError, Client, ClientError, TransportError, VersionMismatchError, bucketStartInstant, encodeNavigateId, formatIdentifier, formatIdentifierField, nextBucketLabel };
|
|
714
|
+
export { ActionDisabledError, ActionNotFoundError, ActionTargetError, ActionUnsupportedError, Client, ClientError, TransportError, VersionMismatchError, actionIdentifier, bucketSeries, bucketStartInstant, encodeNavigateId, formatIdentifier, formatIdentifierField, nextBucketLabel };
|
|
@@ -132,6 +132,28 @@ type ClientResponse<T, Q> = DbResponse<DataOf<T>, NavOf<T>, Q> & {
|
|
|
132
132
|
*/
|
|
133
133
|
$disabledReasons?: Record<string, string>;
|
|
134
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* "Every row matching this query" — the target of
|
|
137
|
+
* `Client.actionOnQuery()` / `countActionTarget()`, for a `'rows'` action
|
|
138
|
+
* whose `/meta` entry carries `queryTarget`. The same filter / search /
|
|
139
|
+
* index the user's `/query` used; the server resolves it under the caller's
|
|
140
|
+
* read scope and the action's own gate.
|
|
141
|
+
*
|
|
142
|
+
* @since 0.1.147
|
|
143
|
+
*/
|
|
144
|
+
interface TDbQueryTarget<T = AtscriptClientShape> {
|
|
145
|
+
filter?: Uniquery$1<OwnOf<T>, NavOf<T>>["filter"];
|
|
146
|
+
/** `$search` term. */
|
|
147
|
+
search?: string;
|
|
148
|
+
/** `$index` — the search index `search` uses. */
|
|
149
|
+
index?: string;
|
|
150
|
+
/** Identifiers to leave out (any identification of the controller the request goes to). */
|
|
151
|
+
exclude?: Record<string, unknown>[];
|
|
152
|
+
/** Fail with 409 `TARGET_CHANGED` when the target no longer matches exactly this many rows. */
|
|
153
|
+
expectCount?: number;
|
|
154
|
+
/** Client-side cap (never above the action's `queryTarget.maxRows`). */
|
|
155
|
+
maxRows?: number;
|
|
156
|
+
}
|
|
135
157
|
//#endregion
|
|
136
158
|
//#region src/validator.d.ts
|
|
137
159
|
/** Options for {@link ClientValidator} / {@link createClientValidator}. */
|
|
@@ -214,4 +236,4 @@ declare class ClientValidationError extends Error {
|
|
|
214
236
|
*/
|
|
215
237
|
declare function createClientValidator(meta: MetaResponse, opts?: ClientValidatorOptions): ClientValidator;
|
|
216
238
|
//#endregion
|
|
217
|
-
export { TDbDeleteResult$1 as A, OwnOf as C, RowOf as D, RelationInfo as E,
|
|
239
|
+
export { TDbDeleteResult$1 as A, OwnOf as C, RowOf as D, RelationInfo as E, TypedWithRelation as F, Uniquery$1 as I, UniqueryControls$1 as L, TDbInsertResult$1 as M, TDbQueryTarget as N, SearchIndexInfo as O, TDbUpdateResult$1 as P, ValidGroupBy$1 as R, NavOf as S, PatchOf as T, DbRow as _, ValidatorMode$1 as a, IdOf as b, AggregateResult$1 as c, BucketUnit as d, CalendarBucketLabel as f, DbPatch as g, DataOf as h, DbValidationContext as i, TDbInsertManyResult$1 as j, ServerError as k, AtscriptClientShape as l, ClientResponse as m, ClientValidator as n, createClientValidator as o, ClientOptions as p, ClientValidatorOptions as r, AggregateQuery$1 as s, ClientValidationError as t, BucketExpr$1 as u, FieldMeta as v, PageResult as w, MetaResponse as x, FilterExpr as y, WeekStart as z };
|
|
@@ -132,6 +132,28 @@ type ClientResponse<T, Q> = DbResponse<DataOf<T>, NavOf<T>, Q> & {
|
|
|
132
132
|
*/
|
|
133
133
|
$disabledReasons?: Record<string, string>;
|
|
134
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* "Every row matching this query" — the target of
|
|
137
|
+
* `Client.actionOnQuery()` / `countActionTarget()`, for a `'rows'` action
|
|
138
|
+
* whose `/meta` entry carries `queryTarget`. The same filter / search /
|
|
139
|
+
* index the user's `/query` used; the server resolves it under the caller's
|
|
140
|
+
* read scope and the action's own gate.
|
|
141
|
+
*
|
|
142
|
+
* @since 0.1.147
|
|
143
|
+
*/
|
|
144
|
+
interface TDbQueryTarget<T = AtscriptClientShape> {
|
|
145
|
+
filter?: Uniquery$1<OwnOf<T>, NavOf<T>>["filter"];
|
|
146
|
+
/** `$search` term. */
|
|
147
|
+
search?: string;
|
|
148
|
+
/** `$index` — the search index `search` uses. */
|
|
149
|
+
index?: string;
|
|
150
|
+
/** Identifiers to leave out (any identification of the controller the request goes to). */
|
|
151
|
+
exclude?: Record<string, unknown>[];
|
|
152
|
+
/** Fail with 409 `TARGET_CHANGED` when the target no longer matches exactly this many rows. */
|
|
153
|
+
expectCount?: number;
|
|
154
|
+
/** Client-side cap (never above the action's `queryTarget.maxRows`). */
|
|
155
|
+
maxRows?: number;
|
|
156
|
+
}
|
|
135
157
|
//#endregion
|
|
136
158
|
//#region src/validator.d.ts
|
|
137
159
|
/** Options for {@link ClientValidator} / {@link createClientValidator}. */
|
|
@@ -214,4 +236,4 @@ declare class ClientValidationError extends Error {
|
|
|
214
236
|
*/
|
|
215
237
|
declare function createClientValidator(meta: MetaResponse, opts?: ClientValidatorOptions): ClientValidator;
|
|
216
238
|
//#endregion
|
|
217
|
-
export { TDbDeleteResult$1 as A, OwnOf as C, RowOf as D, RelationInfo as E,
|
|
239
|
+
export { TDbDeleteResult$1 as A, OwnOf as C, RowOf as D, RelationInfo as E, TypedWithRelation as F, Uniquery$1 as I, UniqueryControls$1 as L, TDbInsertResult$1 as M, TDbQueryTarget as N, SearchIndexInfo as O, TDbUpdateResult$1 as P, ValidGroupBy$1 as R, NavOf as S, PatchOf as T, DbRow as _, ValidatorMode$1 as a, IdOf as b, AggregateResult$1 as c, BucketUnit as d, CalendarBucketLabel as f, DbPatch as g, DataOf as h, DbValidationContext as i, TDbInsertManyResult$1 as j, ServerError as k, AtscriptClientShape as l, ClientResponse as m, ClientValidator as n, createClientValidator as o, ClientOptions as p, ClientValidatorOptions as r, AggregateQuery$1 as s, ClientValidationError as t, BucketExpr$1 as u, FieldMeta as v, PageResult as w, MetaResponse as x, FilterExpr as y, WeekStart as z };
|
package/dist/validator.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ValidatorMode, i as DbValidationContext, n as ClientValidator, o as createClientValidator, r as ClientValidatorOptions, t as ClientValidationError } from "./validator-
|
|
1
|
+
import { a as ValidatorMode, i as DbValidationContext, n as ClientValidator, o as createClientValidator, r as ClientValidatorOptions, t as ClientValidationError } from "./validator-DVha6LOQ.cjs";
|
|
2
2
|
export { ClientValidationError, ClientValidator, ClientValidatorOptions, type DbValidationContext, type ValidatorMode, createClientValidator };
|
package/dist/validator.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ValidatorMode, i as DbValidationContext, n as ClientValidator, o as createClientValidator, r as ClientValidatorOptions, t as ClientValidationError } from "./validator-
|
|
1
|
+
import { a as ValidatorMode, i as DbValidationContext, n as ClientValidator, o as createClientValidator, r as ClientValidatorOptions, t as ClientValidationError } from "./validator-xkEq4h0X.mjs";
|
|
2
2
|
export { ClientValidationError, ClientValidator, ClientValidatorOptions, type DbValidationContext, type ValidatorMode, createClientValidator };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.147",
|
|
4
4
|
"description": "Browser-compatible HTTP client for @atscript/moost-db REST endpoints.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -43,18 +43,18 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@uniqu/core": "^0.1.
|
|
47
|
-
"@uniqu/url": "^0.1.
|
|
46
|
+
"@uniqu/core": "^0.1.12",
|
|
47
|
+
"@uniqu/url": "^0.1.12"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@atscript/core": "^0.1.
|
|
51
|
-
"@atscript/typescript": "^0.1.
|
|
52
|
-
"unplugin-atscript": "^0.1.
|
|
53
|
-
"@atscript/db": "0.1.
|
|
50
|
+
"@atscript/core": "^0.1.99",
|
|
51
|
+
"@atscript/typescript": "^0.1.99",
|
|
52
|
+
"unplugin-atscript": "^0.1.99",
|
|
53
|
+
"@atscript/db": "0.1.147"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@atscript/db": "^0.1.44",
|
|
57
|
-
"@atscript/typescript": "^0.1.
|
|
57
|
+
"@atscript/typescript": "^0.1.99"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"postinstall": "asc -f dts",
|