@atscript/db-client 0.1.146 → 0.1.148
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 +167 -5
- package/dist/index.d.cts +129 -14
- package/dist/index.d.mts +129 -14
- package/dist/index.mjs +161 -7
- package/dist/{validator-810-rhnQ.d.cts → validator-CPmmXTAt.d.mts} +37 -3
- package/dist/{validator-CIm1ZCS5.d.mts → validator-CVM7eNP7.d.cts} +37 -3
- 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
|
|
@@ -143,6 +166,17 @@ var ActionUnsupportedError = class extends Error {
|
|
|
143
166
|
* const all = await users.query()
|
|
144
167
|
* const page = await users.pages({ filter: { active: true } }, 1, 20)
|
|
145
168
|
* ```
|
|
169
|
+
*
|
|
170
|
+
* `D` (since 0.1.148) types the display-only fields the controller declares
|
|
171
|
+
* with `@DbDecorations`: `$select` of the read methods accepts their keys and
|
|
172
|
+
* rows carry them as optional properties. Filter and sort stay over `T`'s own
|
|
173
|
+
* fields — a decoration is never filterable or sortable.
|
|
174
|
+
*
|
|
175
|
+
* ```typescript
|
|
176
|
+
* const tickets = new Client<typeof Ticket, TicketDecorations>('/api/tickets')
|
|
177
|
+
* const rows = await tickets.query({ controls: { $select: ['title', 'ownerName'] } })
|
|
178
|
+
* rows[0].ownerName // string | undefined
|
|
179
|
+
* ```
|
|
146
180
|
*/
|
|
147
181
|
var Client = class {
|
|
148
182
|
_path;
|
|
@@ -186,8 +220,15 @@ var Client = class {
|
|
|
186
220
|
*
|
|
187
221
|
* `$select` may carry calendar buckets (`{ $bucket, $field, $tz?, $weekStart?, $as? }`);
|
|
188
222
|
* 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`
|
|
223
|
+
* bucket's value is typed as its `YYYY-MM-DD` (hour: `YYYY-MM-DDTHH:00`) label
|
|
224
|
+
* (`| null` for an optional source).
|
|
190
225
|
* Gap-fill between labels with `nextBucketLabel` (re-exported here).
|
|
226
|
+
*
|
|
227
|
+
* Arithmetic (since 0.1.148): `{ $fn: 'sum', $expr, $as }` aggregates a per-row
|
|
228
|
+
* expression over numeric fields; `{ $expr, $as }` computes over aliases of other
|
|
229
|
+
* numeric entries or numeric `$groupBy` fields (`number | null`). `first` / `last`
|
|
230
|
+
* read a representative row ordered by `$rowOrder`. The URL forms are `expr(a/b):x`,
|
|
231
|
+
* `sum(a*b):x`, `first(f):x` and `$rowOrder=f,-id`.
|
|
191
232
|
*/
|
|
192
233
|
async aggregate(query) {
|
|
193
234
|
return this._get("query", query);
|
|
@@ -250,8 +291,9 @@ var Client = class {
|
|
|
250
291
|
const controlStr = query?.controls ? (0, _uniqu_url_builder.buildUrl)({ controls: query.controls }) : "";
|
|
251
292
|
return this._getOrNull(this._idUrl("one", id, controlStr));
|
|
252
293
|
}
|
|
253
|
-
async insert(data) {
|
|
254
|
-
|
|
294
|
+
async insert(data, opts) {
|
|
295
|
+
const query = opts?.onConflict === "ignore" ? "?$onConflict=ignore" : "";
|
|
296
|
+
return this._request("POST", query, await this._prepareWrite(data, "insert"));
|
|
255
297
|
}
|
|
256
298
|
/**
|
|
257
299
|
* `PATCH /` — partial update one or many records by primary key.
|
|
@@ -323,6 +365,13 @@ var Client = class {
|
|
|
323
365
|
* `{ ids?, input? }` — `ids` carries `id` (object or array per level),
|
|
324
366
|
* `input` carries the form payload.
|
|
325
367
|
*
|
|
368
|
+
* **Delegated actions** (since 0.1.147). An action another controller owns
|
|
369
|
+
* (`owner`, e.g. a view listing its source table's actions) may carry
|
|
370
|
+
* `idMap`: each `id` is then a row (or identifier) of THIS controller and
|
|
371
|
+
* is mapped to the owner's identification — `{ [ownerField]:
|
|
372
|
+
* row[path] }` — before it is sent to `value` (the owner's route). A
|
|
373
|
+
* missing path throws `TypeError`. See {@link actionIdentifier}.
|
|
374
|
+
*
|
|
326
375
|
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
327
376
|
* server returns whatever the handler emits (commonly
|
|
328
377
|
* `{ message?: string, ... }`); the client cannot validate.
|
|
@@ -332,15 +381,65 @@ var Client = class {
|
|
|
332
381
|
const action = meta.actions.find((a) => a.name === name);
|
|
333
382
|
if (!action) throw new ActionNotFoundError(name);
|
|
334
383
|
if (action.processor === "custom") throw new ActionUnsupportedError(name, "custom", `Action "${name}" has processor "custom" — applications must dispatch custom actions themselves; the client cannot.`);
|
|
384
|
+
const mapped = action.idMap ? mapDelegatedIds(action, id) : id;
|
|
335
385
|
if (action.processor === "navigate") {
|
|
336
|
-
const
|
|
386
|
+
const order = action.idMap ? Object.keys(action.idMap) : meta.preferredId;
|
|
387
|
+
const url = this._interpolateNavigateUrl(action, mapped, order);
|
|
337
388
|
await this._dispatchNavigate(action, url);
|
|
338
389
|
return;
|
|
339
390
|
}
|
|
340
|
-
const body = this._buildActionBody(action,
|
|
391
|
+
const body = this._buildActionBody(action, mapped, input);
|
|
341
392
|
return this._postAction(action, body);
|
|
342
393
|
}
|
|
343
394
|
/**
|
|
395
|
+
* Runs a `'rows'` action on every row matching a query (since 0.1.147) —
|
|
396
|
+
* the action's `/meta` entry must carry `queryTarget`. POSTs
|
|
397
|
+
* `{ query: { q, exclude?, expectCount?, maxRows? }, input? }` to
|
|
398
|
+
* `queryTarget.url` (a delegated action: the view resolves the rows and
|
|
399
|
+
* runs the owner's action in batches) or `value`. `q` is the `/query`
|
|
400
|
+
* string of `target.filter` / `search` / `index`.
|
|
401
|
+
*
|
|
402
|
+
* The server answers what the handler returns — for a delegated action
|
|
403
|
+
* (and a handler returning `target.summary()`) a
|
|
404
|
+
* {@link TDbActionTargetSummary}. Refusals arrive as
|
|
405
|
+
* {@link ActionTargetError}: `TARGET_TOO_LARGE` (`cap`), `TARGET_CHANGED`
|
|
406
|
+
* (`matched` differs from `expectCount`), `TARGET_INVALID`. Throws
|
|
407
|
+
* {@link ActionUnsupportedError} when the action takes no query target.
|
|
408
|
+
*
|
|
409
|
+
* @since 0.1.147
|
|
410
|
+
*/
|
|
411
|
+
async actionOnQuery(name, target, input) {
|
|
412
|
+
const action = await this._queryTargetAction(name);
|
|
413
|
+
const body = { query: queryTargetBody(target) };
|
|
414
|
+
if (input !== void 0) body.input = input;
|
|
415
|
+
return this._postQueryTarget(action, body);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* How many rows {@link actionOnQuery} would target right now (a dry run:
|
|
419
|
+
* the handler does not run) — pass the answer as `expectCount` to make the
|
|
420
|
+
* real run fail with `TARGET_CHANGED` if the set changed in between.
|
|
421
|
+
*
|
|
422
|
+
* @since 0.1.147
|
|
423
|
+
*/
|
|
424
|
+
async countActionTarget(name, target) {
|
|
425
|
+
const action = await this._queryTargetAction(name);
|
|
426
|
+
const query = {
|
|
427
|
+
...queryTargetBody(target),
|
|
428
|
+
dryRun: true
|
|
429
|
+
};
|
|
430
|
+
return this._postQueryTarget(action, { query });
|
|
431
|
+
}
|
|
432
|
+
async _queryTargetAction(name) {
|
|
433
|
+
const action = (await this.meta()).actions.find((a) => a.name === name);
|
|
434
|
+
if (!action) throw new ActionNotFoundError(name);
|
|
435
|
+
if (!action.queryTarget || action.processor !== "backend") throw new ActionUnsupportedError(name, action.processor, `Action "${name}" does not accept a query target (no \`queryTarget\` in /meta).`);
|
|
436
|
+
return action;
|
|
437
|
+
}
|
|
438
|
+
_postQueryTarget(action, body) {
|
|
439
|
+
const path = action.queryTarget?.url ?? action.value;
|
|
440
|
+
return this._requestUrl("POST", `${this._baseUrl}${path}`, body, true);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
344
443
|
* `GET /meta/actions/:id` or `GET /meta/actions?k1=v1&k2=v2` — the
|
|
345
444
|
* row-level actions the caller may run on one row right now, and the
|
|
346
445
|
* reasons of those disabled with one: `{ actions, disabledReasons? }`.
|
|
@@ -519,6 +618,7 @@ var Client = class {
|
|
|
519
618
|
};
|
|
520
619
|
}
|
|
521
620
|
if (errorBody.name === "ActionDisabledError") throw new ActionDisabledError(res.status, errorBody);
|
|
621
|
+
if (errorBody.name === "ActionTargetError") throw new ActionTargetError(res.status, errorBody);
|
|
522
622
|
if (errorBody.kind === "version_mismatch") throw new VersionMismatchError(res.status, errorBody);
|
|
523
623
|
throw new ClientError(res.status, errorBody);
|
|
524
624
|
}
|
|
@@ -540,6 +640,60 @@ function describeCause(cause) {
|
|
|
540
640
|
if (cause instanceof Error) return cause.message || cause.name;
|
|
541
641
|
return String(cause);
|
|
542
642
|
}
|
|
643
|
+
/** The value at a dot `path` of `row`. */
|
|
644
|
+
function valueAt(row, path) {
|
|
645
|
+
if (!path.includes(".") || Object.hasOwn(row, path)) return row[path];
|
|
646
|
+
let v = row;
|
|
647
|
+
for (const part of path.split(".")) v = v?.[part];
|
|
648
|
+
return v;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* The identifier `action` takes for one row (or identifier) of the
|
|
652
|
+
* controller whose `/meta` listed it (since 0.1.147):
|
|
653
|
+
*
|
|
654
|
+
* - a delegated action with `idMap` → the owner's identification,
|
|
655
|
+
* `{ [ownerField]: rowOrId[path] }` (dot paths allowed); a missing value
|
|
656
|
+
* throws `TypeError` naming the action and the path;
|
|
657
|
+
* - any other action → `rowOrId`'s `preferredId` fields when it carries them
|
|
658
|
+
* all, else `rowOrId` itself (already an identifier).
|
|
659
|
+
*
|
|
660
|
+
* UIs build `ids` from loaded rows with it; `Client.action()` applies the
|
|
661
|
+
* `idMap` mapping itself.
|
|
662
|
+
*
|
|
663
|
+
* @since 0.1.147
|
|
664
|
+
*/
|
|
665
|
+
function actionIdentifier(action, rowOrId, preferredId) {
|
|
666
|
+
if (action.idMap) {
|
|
667
|
+
const out = {};
|
|
668
|
+
for (const [field, path] of Object.entries(action.idMap)) {
|
|
669
|
+
const value = valueAt(rowOrId, path);
|
|
670
|
+
if (value === void 0 || value === null) throw new TypeError(`client.action("${action.name}"): the identifier has no "${path}" — needed for the owner's "${field}".`);
|
|
671
|
+
out[field] = value;
|
|
672
|
+
}
|
|
673
|
+
return out;
|
|
674
|
+
}
|
|
675
|
+
if (preferredId.length > 0 && preferredId.every((f) => rowOrId[f] !== void 0)) return Object.fromEntries(preferredId.map((f) => [f, rowOrId[f]]));
|
|
676
|
+
return rowOrId;
|
|
677
|
+
}
|
|
678
|
+
/** `action()`'s `id` argument through a delegated action's `idMap` (shape errors are left to the body builder). */
|
|
679
|
+
function mapDelegatedIds(action, id) {
|
|
680
|
+
const map = (one) => one !== null && typeof one === "object" && !Array.isArray(one) ? actionIdentifier(action, one, []) : one;
|
|
681
|
+
return Array.isArray(id) ? id.map(map) : map(id);
|
|
682
|
+
}
|
|
683
|
+
/** The wire `query` of a {@link TDbQueryTarget}. */
|
|
684
|
+
function queryTargetBody(target) {
|
|
685
|
+
const controls = {};
|
|
686
|
+
if (target.search !== void 0) controls.$search = target.search;
|
|
687
|
+
if (target.index !== void 0) controls.$index = target.index;
|
|
688
|
+
const out = { q: (0, _uniqu_url_builder.buildUrl)({
|
|
689
|
+
filter: target.filter ?? {},
|
|
690
|
+
controls
|
|
691
|
+
}) };
|
|
692
|
+
if (target.exclude?.length) out.exclude = target.exclude;
|
|
693
|
+
if (target.expectCount !== void 0) out.expectCount = target.expectCount;
|
|
694
|
+
if (target.maxRows !== void 0) out.maxRows = target.maxRows;
|
|
695
|
+
return out;
|
|
696
|
+
}
|
|
543
697
|
/**
|
|
544
698
|
* Render a single identifier field for substitution into a navigate-URL
|
|
545
699
|
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
@@ -578,11 +732,19 @@ function describeShape(value) {
|
|
|
578
732
|
//#endregion
|
|
579
733
|
exports.ActionDisabledError = ActionDisabledError;
|
|
580
734
|
exports.ActionNotFoundError = ActionNotFoundError;
|
|
735
|
+
exports.ActionTargetError = ActionTargetError;
|
|
581
736
|
exports.ActionUnsupportedError = ActionUnsupportedError;
|
|
582
737
|
exports.Client = Client;
|
|
583
738
|
exports.ClientError = ClientError;
|
|
584
739
|
exports.TransportError = TransportError;
|
|
585
740
|
exports.VersionMismatchError = VersionMismatchError;
|
|
741
|
+
exports.actionIdentifier = actionIdentifier;
|
|
742
|
+
Object.defineProperty(exports, "bucketSeries", {
|
|
743
|
+
enumerable: true,
|
|
744
|
+
get: function() {
|
|
745
|
+
return _uniqu_core.bucketSeries;
|
|
746
|
+
}
|
|
747
|
+
});
|
|
586
748
|
Object.defineProperty(exports, "bucketStartInstant", {
|
|
587
749
|
enumerable: true,
|
|
588
750
|
get: function() {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { AggregateExpr, AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1,
|
|
1
|
+
import { A as SearchIndexInfo, B as Uniquery, C as MetaResponse, D as PatchOf, E as PageResult, F as TDbInsertManyResult, H as ValidGroupBy, I as TDbInsertResult, L as TDbQueryTarget, M as TDbDeleteResult, N as TDbInsertIgnoreResult, O as RelationInfo, P as TDbInsertManyIgnoreResult, R as TDbUpdateResult, S as IdOf, T as OwnOf, U as WeekStart, V as UniqueryControls, _ as DbRow, a as ValidatorMode, b as FieldMeta, c as AggregateResult, d as BucketUnit, f as CalendarBucketLabel, g as DbPatch, h as DataOf, j as ServerError, k as RowOf, 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 DecoratedControls, w as NavOf, x as FilterExpr, y as DecoratedQuery, z as TypedWithRelation } from "./validator-CVM7eNP7.cjs";
|
|
2
|
+
import { AggregateExpr, AggregateOfExpr, AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, BucketSeriesOptions, NextBucketOptions, SelectArithExpr, Uniquery as Uniquery$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, TDbInsertIgnoreResult as TDbInsertIgnoreResult$1, TDbInsertManyIgnoreResult as TDbInsertManyIgnoreResult$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>;
|
|
8
8
|
type Nav<T> = NavOf<T>;
|
|
9
9
|
type Id<T> = IdOf<T>;
|
|
10
10
|
type Response<T, Q> = ClientResponse<T, Q>;
|
|
11
|
+
/** A read row: the response over the own fields plus the declared decorations `D`, each optional. */
|
|
12
|
+
type DecoratedRow<T, Q, D> = Response<T, Q> & Partial<D>;
|
|
11
13
|
/**
|
|
12
14
|
* HTTP client for moost-db REST endpoints.
|
|
13
15
|
*
|
|
@@ -28,8 +30,19 @@ type Response<T, Q> = ClientResponse<T, Q>;
|
|
|
28
30
|
* const all = await users.query()
|
|
29
31
|
* const page = await users.pages({ filter: { active: true } }, 1, 20)
|
|
30
32
|
* ```
|
|
33
|
+
*
|
|
34
|
+
* `D` (since 0.1.148) types the display-only fields the controller declares
|
|
35
|
+
* with `@DbDecorations`: `$select` of the read methods accepts their keys and
|
|
36
|
+
* rows carry them as optional properties. Filter and sort stay over `T`'s own
|
|
37
|
+
* fields — a decoration is never filterable or sortable.
|
|
38
|
+
*
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const tickets = new Client<typeof Ticket, TicketDecorations>('/api/tickets')
|
|
41
|
+
* const rows = await tickets.query({ controls: { $select: ['title', 'ownerName'] } })
|
|
42
|
+
* rows[0].ownerName // string | undefined
|
|
43
|
+
* ```
|
|
31
44
|
*/
|
|
32
|
-
declare class Client<T extends AtscriptClientShape = AtscriptClientShape
|
|
45
|
+
declare class Client<T extends AtscriptClientShape = AtscriptClientShape, D extends object = Record<never, never>> {
|
|
33
46
|
private readonly _path;
|
|
34
47
|
private readonly _baseUrl;
|
|
35
48
|
private readonly _fetch;
|
|
@@ -47,7 +60,7 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
47
60
|
* The response type narrows by the literal `$with` array in `query` —
|
|
48
61
|
* relations not listed in `$with` are stripped from the row type.
|
|
49
62
|
*/
|
|
50
|
-
query<Q extends
|
|
63
|
+
query<Q extends DecoratedQuery<T, D> = DecoratedQuery<T, D>>(query?: Q): Promise<DecoratedRow<T, Q, D>[]>;
|
|
51
64
|
/**
|
|
52
65
|
* `GET /query` with `$count: true` — returns record count.
|
|
53
66
|
*/
|
|
@@ -59,17 +72,24 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
59
72
|
*
|
|
60
73
|
* `$select` may carry calendar buckets (`{ $bucket, $field, $tz?, $weekStart?, $as? }`);
|
|
61
74
|
* 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`
|
|
75
|
+
* bucket's value is typed as its `YYYY-MM-DD` (hour: `YYYY-MM-DDTHH:00`) label
|
|
76
|
+
* (`| null` for an optional source).
|
|
63
77
|
* Gap-fill between labels with `nextBucketLabel` (re-exported here).
|
|
78
|
+
*
|
|
79
|
+
* Arithmetic (since 0.1.148): `{ $fn: 'sum', $expr, $as }` aggregates a per-row
|
|
80
|
+
* expression over numeric fields; `{ $expr, $as }` computes over aliases of other
|
|
81
|
+
* numeric entries or numeric `$groupBy` fields (`number | null`). `first` / `last`
|
|
82
|
+
* read a representative row ordered by `$rowOrder`. The URL forms are `expr(a/b):x`,
|
|
83
|
+
* `sum(a*b):x`, `first(f):x` and `$rowOrder=f,-id`.
|
|
64
84
|
*/
|
|
65
|
-
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>[]>;
|
|
85
|
+
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 | AggregateOfExpr | SelectArithExpr)[] ? AggregateResult$1<Own<T>, Q["controls"]["$select"]>[] : Record<string, unknown>[]>;
|
|
66
86
|
/**
|
|
67
87
|
* `GET /pages` — paginated query with typed filter and relations.
|
|
68
88
|
*
|
|
69
89
|
* Response rows narrow by the literal `$with` array — same algebra as
|
|
70
90
|
* {@link query}.
|
|
71
91
|
*/
|
|
72
|
-
pages<Q extends
|
|
92
|
+
pages<Q extends DecoratedQuery<T, D> = DecoratedQuery<T, D>>(query?: Q, page?: number, size?: number): Promise<PageResult<DecoratedRow<T, Q, D>>>;
|
|
73
93
|
/**
|
|
74
94
|
* `GET /geo` — distance-ranked geospatial search (tables with `@db.index.geo`).
|
|
75
95
|
*
|
|
@@ -79,13 +99,13 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
79
99
|
* in `query.controls`; filter / `$select` / `$with` / `$skip` / `$limit`
|
|
80
100
|
* compose as usual.
|
|
81
101
|
*/
|
|
82
|
-
geoSearch<Q extends
|
|
102
|
+
geoSearch<Q extends DecoratedQuery<T, D> = DecoratedQuery<T, D>>(point: [number, number], query?: Q): Promise<Array<DecoratedRow<T, Q, D> & {
|
|
83
103
|
$distance: number;
|
|
84
104
|
}>>;
|
|
85
105
|
/**
|
|
86
106
|
* `GET /geo` with `$page` / `$size` — paginated distance-ranked search.
|
|
87
107
|
*/
|
|
88
|
-
geoPages<Q extends
|
|
108
|
+
geoPages<Q extends DecoratedQuery<T, D> = DecoratedQuery<T, D>>(point: [number, number], query?: Q, page?: number, size?: number): Promise<PageResult<DecoratedRow<T, Q, D> & {
|
|
89
109
|
$distance: number;
|
|
90
110
|
}>>;
|
|
91
111
|
/**
|
|
@@ -95,10 +115,10 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
95
115
|
* `query.controls` — same algebra as {@link query}.
|
|
96
116
|
*/
|
|
97
117
|
one<Q extends {
|
|
98
|
-
controls?:
|
|
118
|
+
controls?: DecoratedControls<T, D>;
|
|
99
119
|
} = {
|
|
100
|
-
controls?:
|
|
101
|
-
}>(id: Id<T>, query?: Q): Promise<
|
|
120
|
+
controls?: DecoratedControls<T, D>;
|
|
121
|
+
}>(id: Id<T>, query?: Q): Promise<DecoratedRow<T, Q, D> | null>;
|
|
102
122
|
/**
|
|
103
123
|
* `POST /` — insert one record. The version column (if any) is optional —
|
|
104
124
|
* the server initialises it; `$cas` is rejected (no meaning on insert).
|
|
@@ -108,6 +128,20 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
108
128
|
* `POST /` — insert many records.
|
|
109
129
|
*/
|
|
110
130
|
insert(data: PatchOf<T>[]): Promise<TDbInsertManyResult$1>;
|
|
131
|
+
/**
|
|
132
|
+
* `POST /?$onConflict=ignore` — insert one record, skipping it when it
|
|
133
|
+
* collides on the primary key or a unique index (since 0.1.148).
|
|
134
|
+
*/
|
|
135
|
+
insert(data: PatchOf<T>, opts: {
|
|
136
|
+
onConflict: "ignore";
|
|
137
|
+
}): Promise<TDbInsertIgnoreResult$1>;
|
|
138
|
+
/**
|
|
139
|
+
* `POST /?$onConflict=ignore` — insert many records, skipping the ones that
|
|
140
|
+
* collide; the result reports inserted and skipped input indices (since 0.1.148).
|
|
141
|
+
*/
|
|
142
|
+
insert(data: PatchOf<T>[], opts: {
|
|
143
|
+
onConflict: "ignore";
|
|
144
|
+
}): Promise<TDbInsertManyIgnoreResult$1>;
|
|
111
145
|
/**
|
|
112
146
|
* `PATCH /` — partial update one or many records by primary key.
|
|
113
147
|
*
|
|
@@ -166,11 +200,48 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
166
200
|
* `{ ids?, input? }` — `ids` carries `id` (object or array per level),
|
|
167
201
|
* `input` carries the form payload.
|
|
168
202
|
*
|
|
203
|
+
* **Delegated actions** (since 0.1.147). An action another controller owns
|
|
204
|
+
* (`owner`, e.g. a view listing its source table's actions) may carry
|
|
205
|
+
* `idMap`: each `id` is then a row (or identifier) of THIS controller and
|
|
206
|
+
* is mapped to the owner's identification — `{ [ownerField]:
|
|
207
|
+
* row[path] }` — before it is sent to `value` (the owner's route). A
|
|
208
|
+
* missing path throws `TypeError`. See {@link actionIdentifier}.
|
|
209
|
+
*
|
|
169
210
|
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
170
211
|
* server returns whatever the handler emits (commonly
|
|
171
212
|
* `{ message?: string, ... }`); the client cannot validate.
|
|
172
213
|
*/
|
|
173
214
|
action<R = unknown>(name: string, id?: Partial<Own<T>> | Partial<Own<T>>[], input?: unknown): Promise<R>;
|
|
215
|
+
/**
|
|
216
|
+
* Runs a `'rows'` action on every row matching a query (since 0.1.147) —
|
|
217
|
+
* the action's `/meta` entry must carry `queryTarget`. POSTs
|
|
218
|
+
* `{ query: { q, exclude?, expectCount?, maxRows? }, input? }` to
|
|
219
|
+
* `queryTarget.url` (a delegated action: the view resolves the rows and
|
|
220
|
+
* runs the owner's action in batches) or `value`. `q` is the `/query`
|
|
221
|
+
* string of `target.filter` / `search` / `index`.
|
|
222
|
+
*
|
|
223
|
+
* The server answers what the handler returns — for a delegated action
|
|
224
|
+
* (and a handler returning `target.summary()`) a
|
|
225
|
+
* {@link TDbActionTargetSummary}. Refusals arrive as
|
|
226
|
+
* {@link ActionTargetError}: `TARGET_TOO_LARGE` (`cap`), `TARGET_CHANGED`
|
|
227
|
+
* (`matched` differs from `expectCount`), `TARGET_INVALID`. Throws
|
|
228
|
+
* {@link ActionUnsupportedError} when the action takes no query target.
|
|
229
|
+
*
|
|
230
|
+
* @since 0.1.147
|
|
231
|
+
*/
|
|
232
|
+
actionOnQuery<R = TDbActionTargetSummary$1>(name: string, target: TDbQueryTarget<T>, input?: unknown): Promise<R>;
|
|
233
|
+
/**
|
|
234
|
+
* How many rows {@link actionOnQuery} would target right now (a dry run:
|
|
235
|
+
* the handler does not run) — pass the answer as `expectCount` to make the
|
|
236
|
+
* real run fail with `TARGET_CHANGED` if the set changed in between.
|
|
237
|
+
*
|
|
238
|
+
* @since 0.1.147
|
|
239
|
+
*/
|
|
240
|
+
countActionTarget(name: string, target: TDbQueryTarget<T>): Promise<{
|
|
241
|
+
matched: number;
|
|
242
|
+
}>;
|
|
243
|
+
private _queryTargetAction;
|
|
244
|
+
private _postQueryTarget;
|
|
174
245
|
/**
|
|
175
246
|
* `GET /meta/actions/:id` or `GET /meta/actions?k1=v1&k2=v2` — the
|
|
176
247
|
* row-level actions the caller may run on one row right now, and the
|
|
@@ -224,6 +295,22 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
224
295
|
private _requestUrl;
|
|
225
296
|
private _send;
|
|
226
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* The identifier `action` takes for one row (or identifier) of the
|
|
300
|
+
* controller whose `/meta` listed it (since 0.1.147):
|
|
301
|
+
*
|
|
302
|
+
* - a delegated action with `idMap` → the owner's identification,
|
|
303
|
+
* `{ [ownerField]: rowOrId[path] }` (dot paths allowed); a missing value
|
|
304
|
+
* throws `TypeError` naming the action and the path;
|
|
305
|
+
* - any other action → `rowOrId`'s `preferredId` fields when it carries them
|
|
306
|
+
* all, else `rowOrId` itself (already an identifier).
|
|
307
|
+
*
|
|
308
|
+
* UIs build `ids` from loaded rows with it; `Client.action()` applies the
|
|
309
|
+
* `idMap` mapping itself.
|
|
310
|
+
*
|
|
311
|
+
* @since 0.1.147
|
|
312
|
+
*/
|
|
313
|
+
declare function actionIdentifier(action: Pick<TDbActionInfo$1, "name" | "idMap">, rowOrId: Record<string, unknown>, preferredId: readonly string[]): Record<string, unknown>;
|
|
227
314
|
/**
|
|
228
315
|
* Render a single identifier field for substitution into a navigate-URL
|
|
229
316
|
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
@@ -318,6 +405,34 @@ declare class ActionDisabledError extends ClientError {
|
|
|
318
405
|
*/
|
|
319
406
|
get reasons(): (string | null)[] | undefined;
|
|
320
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Wire-body shape for `ActionTargetError` responses (since 0.1.147): a
|
|
410
|
+
* query target the server refused — `code` says why.
|
|
411
|
+
*/
|
|
412
|
+
interface ActionTargetErrorBody extends ServerError {
|
|
413
|
+
name: "ActionTargetError";
|
|
414
|
+
code: "TARGET_INVALID" | "TARGET_TOO_LARGE" | "TARGET_CHANGED";
|
|
415
|
+
action: string;
|
|
416
|
+
/** `TARGET_CHANGED`: the current match count. */
|
|
417
|
+
matched?: number;
|
|
418
|
+
/** `TARGET_TOO_LARGE`: the most rows one request may target. */
|
|
419
|
+
cap?: number;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Typed marker thrown by `Client` when a query-targeted action request is
|
|
423
|
+
* refused (body `name === 'ActionTargetError'`): `TARGET_INVALID` (400),
|
|
424
|
+
* `TARGET_TOO_LARGE` (400, `cap`) or `TARGET_CHANGED` (409, `matched` — the
|
|
425
|
+
* rows the query matches now; re-confirm and retry with that count).
|
|
426
|
+
*
|
|
427
|
+
* @since 0.1.147
|
|
428
|
+
*/
|
|
429
|
+
declare class ActionTargetError extends ClientError {
|
|
430
|
+
name: string;
|
|
431
|
+
get code(): ActionTargetErrorBody["code"];
|
|
432
|
+
get action(): string;
|
|
433
|
+
get matched(): number | undefined;
|
|
434
|
+
get cap(): number | undefined;
|
|
435
|
+
}
|
|
321
436
|
/**
|
|
322
437
|
* Wire-body shape for 409 OCC `version_mismatch` responses. Extends the base
|
|
323
438
|
* `ServerError` envelope with a `kind` discriminator and the row's current
|
|
@@ -382,4 +497,4 @@ declare class ActionUnsupportedError extends Error {
|
|
|
382
497
|
constructor(action: string, processor: string, message: string);
|
|
383
498
|
}
|
|
384
499
|
//#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 };
|
|
500
|
+
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 TDbInsertIgnoreResult, type TDbInsertManyIgnoreResult, 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,13 +1,15 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as SearchIndexInfo, B as Uniquery, C as MetaResponse, D as PatchOf, E as PageResult, F as TDbInsertManyResult, H as ValidGroupBy, I as TDbInsertResult, L as TDbQueryTarget, M as TDbDeleteResult, N as TDbInsertIgnoreResult, O as RelationInfo, P as TDbInsertManyIgnoreResult, R as TDbUpdateResult, S as IdOf, T as OwnOf, U as WeekStart, V as UniqueryControls, _ as DbRow, a as ValidatorMode, b as FieldMeta, c as AggregateResult, d as BucketUnit, f as CalendarBucketLabel, g as DbPatch, h as DataOf, j as ServerError, k as RowOf, 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 DecoratedControls, w as NavOf, x as FilterExpr, y as DecoratedQuery, z as TypedWithRelation } from "./validator-CPmmXTAt.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,
|
|
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, AggregateOfExpr, AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, BucketSeriesOptions, NextBucketOptions, SelectArithExpr, Uniquery as Uniquery$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, TDbInsertIgnoreResult as TDbInsertIgnoreResult$1, TDbInsertManyIgnoreResult as TDbInsertManyIgnoreResult$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>;
|
|
8
8
|
type Nav<T> = NavOf<T>;
|
|
9
9
|
type Id<T> = IdOf<T>;
|
|
10
10
|
type Response<T, Q> = ClientResponse<T, Q>;
|
|
11
|
+
/** A read row: the response over the own fields plus the declared decorations `D`, each optional. */
|
|
12
|
+
type DecoratedRow<T, Q, D> = Response<T, Q> & Partial<D>;
|
|
11
13
|
/**
|
|
12
14
|
* HTTP client for moost-db REST endpoints.
|
|
13
15
|
*
|
|
@@ -28,8 +30,19 @@ type Response<T, Q> = ClientResponse<T, Q>;
|
|
|
28
30
|
* const all = await users.query()
|
|
29
31
|
* const page = await users.pages({ filter: { active: true } }, 1, 20)
|
|
30
32
|
* ```
|
|
33
|
+
*
|
|
34
|
+
* `D` (since 0.1.148) types the display-only fields the controller declares
|
|
35
|
+
* with `@DbDecorations`: `$select` of the read methods accepts their keys and
|
|
36
|
+
* rows carry them as optional properties. Filter and sort stay over `T`'s own
|
|
37
|
+
* fields — a decoration is never filterable or sortable.
|
|
38
|
+
*
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const tickets = new Client<typeof Ticket, TicketDecorations>('/api/tickets')
|
|
41
|
+
* const rows = await tickets.query({ controls: { $select: ['title', 'ownerName'] } })
|
|
42
|
+
* rows[0].ownerName // string | undefined
|
|
43
|
+
* ```
|
|
31
44
|
*/
|
|
32
|
-
declare class Client<T extends AtscriptClientShape = AtscriptClientShape
|
|
45
|
+
declare class Client<T extends AtscriptClientShape = AtscriptClientShape, D extends object = Record<never, never>> {
|
|
33
46
|
private readonly _path;
|
|
34
47
|
private readonly _baseUrl;
|
|
35
48
|
private readonly _fetch;
|
|
@@ -47,7 +60,7 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
47
60
|
* The response type narrows by the literal `$with` array in `query` —
|
|
48
61
|
* relations not listed in `$with` are stripped from the row type.
|
|
49
62
|
*/
|
|
50
|
-
query<Q extends
|
|
63
|
+
query<Q extends DecoratedQuery<T, D> = DecoratedQuery<T, D>>(query?: Q): Promise<DecoratedRow<T, Q, D>[]>;
|
|
51
64
|
/**
|
|
52
65
|
* `GET /query` with `$count: true` — returns record count.
|
|
53
66
|
*/
|
|
@@ -59,17 +72,24 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
59
72
|
*
|
|
60
73
|
* `$select` may carry calendar buckets (`{ $bucket, $field, $tz?, $weekStart?, $as? }`);
|
|
61
74
|
* 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`
|
|
75
|
+
* bucket's value is typed as its `YYYY-MM-DD` (hour: `YYYY-MM-DDTHH:00`) label
|
|
76
|
+
* (`| null` for an optional source).
|
|
63
77
|
* Gap-fill between labels with `nextBucketLabel` (re-exported here).
|
|
78
|
+
*
|
|
79
|
+
* Arithmetic (since 0.1.148): `{ $fn: 'sum', $expr, $as }` aggregates a per-row
|
|
80
|
+
* expression over numeric fields; `{ $expr, $as }` computes over aliases of other
|
|
81
|
+
* numeric entries or numeric `$groupBy` fields (`number | null`). `first` / `last`
|
|
82
|
+
* read a representative row ordered by `$rowOrder`. The URL forms are `expr(a/b):x`,
|
|
83
|
+
* `sum(a*b):x`, `first(f):x` and `$rowOrder=f,-id`.
|
|
64
84
|
*/
|
|
65
|
-
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>[]>;
|
|
85
|
+
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 | AggregateOfExpr | SelectArithExpr)[] ? AggregateResult$1<Own<T>, Q["controls"]["$select"]>[] : Record<string, unknown>[]>;
|
|
66
86
|
/**
|
|
67
87
|
* `GET /pages` — paginated query with typed filter and relations.
|
|
68
88
|
*
|
|
69
89
|
* Response rows narrow by the literal `$with` array — same algebra as
|
|
70
90
|
* {@link query}.
|
|
71
91
|
*/
|
|
72
|
-
pages<Q extends
|
|
92
|
+
pages<Q extends DecoratedQuery<T, D> = DecoratedQuery<T, D>>(query?: Q, page?: number, size?: number): Promise<PageResult<DecoratedRow<T, Q, D>>>;
|
|
73
93
|
/**
|
|
74
94
|
* `GET /geo` — distance-ranked geospatial search (tables with `@db.index.geo`).
|
|
75
95
|
*
|
|
@@ -79,13 +99,13 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
79
99
|
* in `query.controls`; filter / `$select` / `$with` / `$skip` / `$limit`
|
|
80
100
|
* compose as usual.
|
|
81
101
|
*/
|
|
82
|
-
geoSearch<Q extends
|
|
102
|
+
geoSearch<Q extends DecoratedQuery<T, D> = DecoratedQuery<T, D>>(point: [number, number], query?: Q): Promise<Array<DecoratedRow<T, Q, D> & {
|
|
83
103
|
$distance: number;
|
|
84
104
|
}>>;
|
|
85
105
|
/**
|
|
86
106
|
* `GET /geo` with `$page` / `$size` — paginated distance-ranked search.
|
|
87
107
|
*/
|
|
88
|
-
geoPages<Q extends
|
|
108
|
+
geoPages<Q extends DecoratedQuery<T, D> = DecoratedQuery<T, D>>(point: [number, number], query?: Q, page?: number, size?: number): Promise<PageResult<DecoratedRow<T, Q, D> & {
|
|
89
109
|
$distance: number;
|
|
90
110
|
}>>;
|
|
91
111
|
/**
|
|
@@ -95,10 +115,10 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
95
115
|
* `query.controls` — same algebra as {@link query}.
|
|
96
116
|
*/
|
|
97
117
|
one<Q extends {
|
|
98
|
-
controls?:
|
|
118
|
+
controls?: DecoratedControls<T, D>;
|
|
99
119
|
} = {
|
|
100
|
-
controls?:
|
|
101
|
-
}>(id: Id<T>, query?: Q): Promise<
|
|
120
|
+
controls?: DecoratedControls<T, D>;
|
|
121
|
+
}>(id: Id<T>, query?: Q): Promise<DecoratedRow<T, Q, D> | null>;
|
|
102
122
|
/**
|
|
103
123
|
* `POST /` — insert one record. The version column (if any) is optional —
|
|
104
124
|
* the server initialises it; `$cas` is rejected (no meaning on insert).
|
|
@@ -108,6 +128,20 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
108
128
|
* `POST /` — insert many records.
|
|
109
129
|
*/
|
|
110
130
|
insert(data: PatchOf<T>[]): Promise<TDbInsertManyResult$1>;
|
|
131
|
+
/**
|
|
132
|
+
* `POST /?$onConflict=ignore` — insert one record, skipping it when it
|
|
133
|
+
* collides on the primary key or a unique index (since 0.1.148).
|
|
134
|
+
*/
|
|
135
|
+
insert(data: PatchOf<T>, opts: {
|
|
136
|
+
onConflict: "ignore";
|
|
137
|
+
}): Promise<TDbInsertIgnoreResult$1>;
|
|
138
|
+
/**
|
|
139
|
+
* `POST /?$onConflict=ignore` — insert many records, skipping the ones that
|
|
140
|
+
* collide; the result reports inserted and skipped input indices (since 0.1.148).
|
|
141
|
+
*/
|
|
142
|
+
insert(data: PatchOf<T>[], opts: {
|
|
143
|
+
onConflict: "ignore";
|
|
144
|
+
}): Promise<TDbInsertManyIgnoreResult$1>;
|
|
111
145
|
/**
|
|
112
146
|
* `PATCH /` — partial update one or many records by primary key.
|
|
113
147
|
*
|
|
@@ -166,11 +200,48 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
166
200
|
* `{ ids?, input? }` — `ids` carries `id` (object or array per level),
|
|
167
201
|
* `input` carries the form payload.
|
|
168
202
|
*
|
|
203
|
+
* **Delegated actions** (since 0.1.147). An action another controller owns
|
|
204
|
+
* (`owner`, e.g. a view listing its source table's actions) may carry
|
|
205
|
+
* `idMap`: each `id` is then a row (or identifier) of THIS controller and
|
|
206
|
+
* is mapped to the owner's identification — `{ [ownerField]:
|
|
207
|
+
* row[path] }` — before it is sent to `value` (the owner's route). A
|
|
208
|
+
* missing path throws `TypeError`. See {@link actionIdentifier}.
|
|
209
|
+
*
|
|
169
210
|
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
170
211
|
* server returns whatever the handler emits (commonly
|
|
171
212
|
* `{ message?: string, ... }`); the client cannot validate.
|
|
172
213
|
*/
|
|
173
214
|
action<R = unknown>(name: string, id?: Partial<Own<T>> | Partial<Own<T>>[], input?: unknown): Promise<R>;
|
|
215
|
+
/**
|
|
216
|
+
* Runs a `'rows'` action on every row matching a query (since 0.1.147) —
|
|
217
|
+
* the action's `/meta` entry must carry `queryTarget`. POSTs
|
|
218
|
+
* `{ query: { q, exclude?, expectCount?, maxRows? }, input? }` to
|
|
219
|
+
* `queryTarget.url` (a delegated action: the view resolves the rows and
|
|
220
|
+
* runs the owner's action in batches) or `value`. `q` is the `/query`
|
|
221
|
+
* string of `target.filter` / `search` / `index`.
|
|
222
|
+
*
|
|
223
|
+
* The server answers what the handler returns — for a delegated action
|
|
224
|
+
* (and a handler returning `target.summary()`) a
|
|
225
|
+
* {@link TDbActionTargetSummary}. Refusals arrive as
|
|
226
|
+
* {@link ActionTargetError}: `TARGET_TOO_LARGE` (`cap`), `TARGET_CHANGED`
|
|
227
|
+
* (`matched` differs from `expectCount`), `TARGET_INVALID`. Throws
|
|
228
|
+
* {@link ActionUnsupportedError} when the action takes no query target.
|
|
229
|
+
*
|
|
230
|
+
* @since 0.1.147
|
|
231
|
+
*/
|
|
232
|
+
actionOnQuery<R = TDbActionTargetSummary$1>(name: string, target: TDbQueryTarget<T>, input?: unknown): Promise<R>;
|
|
233
|
+
/**
|
|
234
|
+
* How many rows {@link actionOnQuery} would target right now (a dry run:
|
|
235
|
+
* the handler does not run) — pass the answer as `expectCount` to make the
|
|
236
|
+
* real run fail with `TARGET_CHANGED` if the set changed in between.
|
|
237
|
+
*
|
|
238
|
+
* @since 0.1.147
|
|
239
|
+
*/
|
|
240
|
+
countActionTarget(name: string, target: TDbQueryTarget<T>): Promise<{
|
|
241
|
+
matched: number;
|
|
242
|
+
}>;
|
|
243
|
+
private _queryTargetAction;
|
|
244
|
+
private _postQueryTarget;
|
|
174
245
|
/**
|
|
175
246
|
* `GET /meta/actions/:id` or `GET /meta/actions?k1=v1&k2=v2` — the
|
|
176
247
|
* row-level actions the caller may run on one row right now, and the
|
|
@@ -224,6 +295,22 @@ declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
|
224
295
|
private _requestUrl;
|
|
225
296
|
private _send;
|
|
226
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* The identifier `action` takes for one row (or identifier) of the
|
|
300
|
+
* controller whose `/meta` listed it (since 0.1.147):
|
|
301
|
+
*
|
|
302
|
+
* - a delegated action with `idMap` → the owner's identification,
|
|
303
|
+
* `{ [ownerField]: rowOrId[path] }` (dot paths allowed); a missing value
|
|
304
|
+
* throws `TypeError` naming the action and the path;
|
|
305
|
+
* - any other action → `rowOrId`'s `preferredId` fields when it carries them
|
|
306
|
+
* all, else `rowOrId` itself (already an identifier).
|
|
307
|
+
*
|
|
308
|
+
* UIs build `ids` from loaded rows with it; `Client.action()` applies the
|
|
309
|
+
* `idMap` mapping itself.
|
|
310
|
+
*
|
|
311
|
+
* @since 0.1.147
|
|
312
|
+
*/
|
|
313
|
+
declare function actionIdentifier(action: Pick<TDbActionInfo$1, "name" | "idMap">, rowOrId: Record<string, unknown>, preferredId: readonly string[]): Record<string, unknown>;
|
|
227
314
|
/**
|
|
228
315
|
* Render a single identifier field for substitution into a navigate-URL
|
|
229
316
|
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
@@ -318,6 +405,34 @@ declare class ActionDisabledError extends ClientError {
|
|
|
318
405
|
*/
|
|
319
406
|
get reasons(): (string | null)[] | undefined;
|
|
320
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Wire-body shape for `ActionTargetError` responses (since 0.1.147): a
|
|
410
|
+
* query target the server refused — `code` says why.
|
|
411
|
+
*/
|
|
412
|
+
interface ActionTargetErrorBody extends ServerError {
|
|
413
|
+
name: "ActionTargetError";
|
|
414
|
+
code: "TARGET_INVALID" | "TARGET_TOO_LARGE" | "TARGET_CHANGED";
|
|
415
|
+
action: string;
|
|
416
|
+
/** `TARGET_CHANGED`: the current match count. */
|
|
417
|
+
matched?: number;
|
|
418
|
+
/** `TARGET_TOO_LARGE`: the most rows one request may target. */
|
|
419
|
+
cap?: number;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Typed marker thrown by `Client` when a query-targeted action request is
|
|
423
|
+
* refused (body `name === 'ActionTargetError'`): `TARGET_INVALID` (400),
|
|
424
|
+
* `TARGET_TOO_LARGE` (400, `cap`) or `TARGET_CHANGED` (409, `matched` — the
|
|
425
|
+
* rows the query matches now; re-confirm and retry with that count).
|
|
426
|
+
*
|
|
427
|
+
* @since 0.1.147
|
|
428
|
+
*/
|
|
429
|
+
declare class ActionTargetError extends ClientError {
|
|
430
|
+
name: string;
|
|
431
|
+
get code(): ActionTargetErrorBody["code"];
|
|
432
|
+
get action(): string;
|
|
433
|
+
get matched(): number | undefined;
|
|
434
|
+
get cap(): number | undefined;
|
|
435
|
+
}
|
|
321
436
|
/**
|
|
322
437
|
* Wire-body shape for 409 OCC `version_mismatch` responses. Extends the base
|
|
323
438
|
* `ServerError` envelope with a `kind` discriminator and the row's current
|
|
@@ -382,4 +497,4 @@ declare class ActionUnsupportedError extends Error {
|
|
|
382
497
|
constructor(action: string, processor: string, message: string);
|
|
383
498
|
}
|
|
384
499
|
//#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 };
|
|
500
|
+
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 TDbInsertIgnoreResult, type TDbInsertManyIgnoreResult, 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
|
|
@@ -142,6 +165,17 @@ var ActionUnsupportedError = class extends Error {
|
|
|
142
165
|
* const all = await users.query()
|
|
143
166
|
* const page = await users.pages({ filter: { active: true } }, 1, 20)
|
|
144
167
|
* ```
|
|
168
|
+
*
|
|
169
|
+
* `D` (since 0.1.148) types the display-only fields the controller declares
|
|
170
|
+
* with `@DbDecorations`: `$select` of the read methods accepts their keys and
|
|
171
|
+
* rows carry them as optional properties. Filter and sort stay over `T`'s own
|
|
172
|
+
* fields — a decoration is never filterable or sortable.
|
|
173
|
+
*
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const tickets = new Client<typeof Ticket, TicketDecorations>('/api/tickets')
|
|
176
|
+
* const rows = await tickets.query({ controls: { $select: ['title', 'ownerName'] } })
|
|
177
|
+
* rows[0].ownerName // string | undefined
|
|
178
|
+
* ```
|
|
145
179
|
*/
|
|
146
180
|
var Client = class {
|
|
147
181
|
_path;
|
|
@@ -185,8 +219,15 @@ var Client = class {
|
|
|
185
219
|
*
|
|
186
220
|
* `$select` may carry calendar buckets (`{ $bucket, $field, $tz?, $weekStart?, $as? }`);
|
|
187
221
|
* 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`
|
|
222
|
+
* bucket's value is typed as its `YYYY-MM-DD` (hour: `YYYY-MM-DDTHH:00`) label
|
|
223
|
+
* (`| null` for an optional source).
|
|
189
224
|
* Gap-fill between labels with `nextBucketLabel` (re-exported here).
|
|
225
|
+
*
|
|
226
|
+
* Arithmetic (since 0.1.148): `{ $fn: 'sum', $expr, $as }` aggregates a per-row
|
|
227
|
+
* expression over numeric fields; `{ $expr, $as }` computes over aliases of other
|
|
228
|
+
* numeric entries or numeric `$groupBy` fields (`number | null`). `first` / `last`
|
|
229
|
+
* read a representative row ordered by `$rowOrder`. The URL forms are `expr(a/b):x`,
|
|
230
|
+
* `sum(a*b):x`, `first(f):x` and `$rowOrder=f,-id`.
|
|
190
231
|
*/
|
|
191
232
|
async aggregate(query) {
|
|
192
233
|
return this._get("query", query);
|
|
@@ -249,8 +290,9 @@ var Client = class {
|
|
|
249
290
|
const controlStr = query?.controls ? buildUrl({ controls: query.controls }) : "";
|
|
250
291
|
return this._getOrNull(this._idUrl("one", id, controlStr));
|
|
251
292
|
}
|
|
252
|
-
async insert(data) {
|
|
253
|
-
|
|
293
|
+
async insert(data, opts) {
|
|
294
|
+
const query = opts?.onConflict === "ignore" ? "?$onConflict=ignore" : "";
|
|
295
|
+
return this._request("POST", query, await this._prepareWrite(data, "insert"));
|
|
254
296
|
}
|
|
255
297
|
/**
|
|
256
298
|
* `PATCH /` — partial update one or many records by primary key.
|
|
@@ -322,6 +364,13 @@ var Client = class {
|
|
|
322
364
|
* `{ ids?, input? }` — `ids` carries `id` (object or array per level),
|
|
323
365
|
* `input` carries the form payload.
|
|
324
366
|
*
|
|
367
|
+
* **Delegated actions** (since 0.1.147). An action another controller owns
|
|
368
|
+
* (`owner`, e.g. a view listing its source table's actions) may carry
|
|
369
|
+
* `idMap`: each `id` is then a row (or identifier) of THIS controller and
|
|
370
|
+
* is mapped to the owner's identification — `{ [ownerField]:
|
|
371
|
+
* row[path] }` — before it is sent to `value` (the owner's route). A
|
|
372
|
+
* missing path throws `TypeError`. See {@link actionIdentifier}.
|
|
373
|
+
*
|
|
325
374
|
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
326
375
|
* server returns whatever the handler emits (commonly
|
|
327
376
|
* `{ message?: string, ... }`); the client cannot validate.
|
|
@@ -331,15 +380,65 @@ var Client = class {
|
|
|
331
380
|
const action = meta.actions.find((a) => a.name === name);
|
|
332
381
|
if (!action) throw new ActionNotFoundError(name);
|
|
333
382
|
if (action.processor === "custom") throw new ActionUnsupportedError(name, "custom", `Action "${name}" has processor "custom" — applications must dispatch custom actions themselves; the client cannot.`);
|
|
383
|
+
const mapped = action.idMap ? mapDelegatedIds(action, id) : id;
|
|
334
384
|
if (action.processor === "navigate") {
|
|
335
|
-
const
|
|
385
|
+
const order = action.idMap ? Object.keys(action.idMap) : meta.preferredId;
|
|
386
|
+
const url = this._interpolateNavigateUrl(action, mapped, order);
|
|
336
387
|
await this._dispatchNavigate(action, url);
|
|
337
388
|
return;
|
|
338
389
|
}
|
|
339
|
-
const body = this._buildActionBody(action,
|
|
390
|
+
const body = this._buildActionBody(action, mapped, input);
|
|
340
391
|
return this._postAction(action, body);
|
|
341
392
|
}
|
|
342
393
|
/**
|
|
394
|
+
* Runs a `'rows'` action on every row matching a query (since 0.1.147) —
|
|
395
|
+
* the action's `/meta` entry must carry `queryTarget`. POSTs
|
|
396
|
+
* `{ query: { q, exclude?, expectCount?, maxRows? }, input? }` to
|
|
397
|
+
* `queryTarget.url` (a delegated action: the view resolves the rows and
|
|
398
|
+
* runs the owner's action in batches) or `value`. `q` is the `/query`
|
|
399
|
+
* string of `target.filter` / `search` / `index`.
|
|
400
|
+
*
|
|
401
|
+
* The server answers what the handler returns — for a delegated action
|
|
402
|
+
* (and a handler returning `target.summary()`) a
|
|
403
|
+
* {@link TDbActionTargetSummary}. Refusals arrive as
|
|
404
|
+
* {@link ActionTargetError}: `TARGET_TOO_LARGE` (`cap`), `TARGET_CHANGED`
|
|
405
|
+
* (`matched` differs from `expectCount`), `TARGET_INVALID`. Throws
|
|
406
|
+
* {@link ActionUnsupportedError} when the action takes no query target.
|
|
407
|
+
*
|
|
408
|
+
* @since 0.1.147
|
|
409
|
+
*/
|
|
410
|
+
async actionOnQuery(name, target, input) {
|
|
411
|
+
const action = await this._queryTargetAction(name);
|
|
412
|
+
const body = { query: queryTargetBody(target) };
|
|
413
|
+
if (input !== void 0) body.input = input;
|
|
414
|
+
return this._postQueryTarget(action, body);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* How many rows {@link actionOnQuery} would target right now (a dry run:
|
|
418
|
+
* the handler does not run) — pass the answer as `expectCount` to make the
|
|
419
|
+
* real run fail with `TARGET_CHANGED` if the set changed in between.
|
|
420
|
+
*
|
|
421
|
+
* @since 0.1.147
|
|
422
|
+
*/
|
|
423
|
+
async countActionTarget(name, target) {
|
|
424
|
+
const action = await this._queryTargetAction(name);
|
|
425
|
+
const query = {
|
|
426
|
+
...queryTargetBody(target),
|
|
427
|
+
dryRun: true
|
|
428
|
+
};
|
|
429
|
+
return this._postQueryTarget(action, { query });
|
|
430
|
+
}
|
|
431
|
+
async _queryTargetAction(name) {
|
|
432
|
+
const action = (await this.meta()).actions.find((a) => a.name === name);
|
|
433
|
+
if (!action) throw new ActionNotFoundError(name);
|
|
434
|
+
if (!action.queryTarget || action.processor !== "backend") throw new ActionUnsupportedError(name, action.processor, `Action "${name}" does not accept a query target (no \`queryTarget\` in /meta).`);
|
|
435
|
+
return action;
|
|
436
|
+
}
|
|
437
|
+
_postQueryTarget(action, body) {
|
|
438
|
+
const path = action.queryTarget?.url ?? action.value;
|
|
439
|
+
return this._requestUrl("POST", `${this._baseUrl}${path}`, body, true);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
343
442
|
* `GET /meta/actions/:id` or `GET /meta/actions?k1=v1&k2=v2` — the
|
|
344
443
|
* row-level actions the caller may run on one row right now, and the
|
|
345
444
|
* reasons of those disabled with one: `{ actions, disabledReasons? }`.
|
|
@@ -518,6 +617,7 @@ var Client = class {
|
|
|
518
617
|
};
|
|
519
618
|
}
|
|
520
619
|
if (errorBody.name === "ActionDisabledError") throw new ActionDisabledError(res.status, errorBody);
|
|
620
|
+
if (errorBody.name === "ActionTargetError") throw new ActionTargetError(res.status, errorBody);
|
|
521
621
|
if (errorBody.kind === "version_mismatch") throw new VersionMismatchError(res.status, errorBody);
|
|
522
622
|
throw new ClientError(res.status, errorBody);
|
|
523
623
|
}
|
|
@@ -539,6 +639,60 @@ function describeCause(cause) {
|
|
|
539
639
|
if (cause instanceof Error) return cause.message || cause.name;
|
|
540
640
|
return String(cause);
|
|
541
641
|
}
|
|
642
|
+
/** The value at a dot `path` of `row`. */
|
|
643
|
+
function valueAt(row, path) {
|
|
644
|
+
if (!path.includes(".") || Object.hasOwn(row, path)) return row[path];
|
|
645
|
+
let v = row;
|
|
646
|
+
for (const part of path.split(".")) v = v?.[part];
|
|
647
|
+
return v;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* The identifier `action` takes for one row (or identifier) of the
|
|
651
|
+
* controller whose `/meta` listed it (since 0.1.147):
|
|
652
|
+
*
|
|
653
|
+
* - a delegated action with `idMap` → the owner's identification,
|
|
654
|
+
* `{ [ownerField]: rowOrId[path] }` (dot paths allowed); a missing value
|
|
655
|
+
* throws `TypeError` naming the action and the path;
|
|
656
|
+
* - any other action → `rowOrId`'s `preferredId` fields when it carries them
|
|
657
|
+
* all, else `rowOrId` itself (already an identifier).
|
|
658
|
+
*
|
|
659
|
+
* UIs build `ids` from loaded rows with it; `Client.action()` applies the
|
|
660
|
+
* `idMap` mapping itself.
|
|
661
|
+
*
|
|
662
|
+
* @since 0.1.147
|
|
663
|
+
*/
|
|
664
|
+
function actionIdentifier(action, rowOrId, preferredId) {
|
|
665
|
+
if (action.idMap) {
|
|
666
|
+
const out = {};
|
|
667
|
+
for (const [field, path] of Object.entries(action.idMap)) {
|
|
668
|
+
const value = valueAt(rowOrId, path);
|
|
669
|
+
if (value === void 0 || value === null) throw new TypeError(`client.action("${action.name}"): the identifier has no "${path}" — needed for the owner's "${field}".`);
|
|
670
|
+
out[field] = value;
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
if (preferredId.length > 0 && preferredId.every((f) => rowOrId[f] !== void 0)) return Object.fromEntries(preferredId.map((f) => [f, rowOrId[f]]));
|
|
675
|
+
return rowOrId;
|
|
676
|
+
}
|
|
677
|
+
/** `action()`'s `id` argument through a delegated action's `idMap` (shape errors are left to the body builder). */
|
|
678
|
+
function mapDelegatedIds(action, id) {
|
|
679
|
+
const map = (one) => one !== null && typeof one === "object" && !Array.isArray(one) ? actionIdentifier(action, one, []) : one;
|
|
680
|
+
return Array.isArray(id) ? id.map(map) : map(id);
|
|
681
|
+
}
|
|
682
|
+
/** The wire `query` of a {@link TDbQueryTarget}. */
|
|
683
|
+
function queryTargetBody(target) {
|
|
684
|
+
const controls = {};
|
|
685
|
+
if (target.search !== void 0) controls.$search = target.search;
|
|
686
|
+
if (target.index !== void 0) controls.$index = target.index;
|
|
687
|
+
const out = { q: buildUrl({
|
|
688
|
+
filter: target.filter ?? {},
|
|
689
|
+
controls
|
|
690
|
+
}) };
|
|
691
|
+
if (target.exclude?.length) out.exclude = target.exclude;
|
|
692
|
+
if (target.expectCount !== void 0) out.expectCount = target.expectCount;
|
|
693
|
+
if (target.maxRows !== void 0) out.maxRows = target.maxRows;
|
|
694
|
+
return out;
|
|
695
|
+
}
|
|
542
696
|
/**
|
|
543
697
|
* Render a single identifier field for substitution into a navigate-URL
|
|
544
698
|
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
@@ -575,4 +729,4 @@ function describeShape(value) {
|
|
|
575
729
|
return typeof value;
|
|
576
730
|
}
|
|
577
731
|
//#endregion
|
|
578
|
-
export { ActionDisabledError, ActionNotFoundError, ActionUnsupportedError, Client, ClientError, TransportError, VersionMismatchError, bucketStartInstant, encodeNavigateId, formatIdentifier, formatIdentifierField, nextBucketLabel };
|
|
732
|
+
export { ActionDisabledError, ActionNotFoundError, ActionTargetError, ActionUnsupportedError, Client, ClientError, TransportError, VersionMismatchError, actionIdentifier, bucketSeries, bucketStartInstant, encodeNavigateId, formatIdentifier, formatIdentifierField, nextBucketLabel };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, BucketUnit, CalendarBucketLabel, FilterExpr, TypedWithRelation, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1, ValidGroupBy as ValidGroupBy$1, WeekStart } from "@uniqu/core";
|
|
2
1
|
import { TAtscriptAnnotatedType, TAtscriptTypeObject } from "@atscript/typescript/utils";
|
|
3
|
-
import {
|
|
2
|
+
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, BucketUnit, CalendarBucketLabel, FilterExpr, SelectExpr, TypedWithRelation, Uniquery as Uniquery$1, UniqueryControls, ValidGroupBy as ValidGroupBy$1, WeekStart } from "@uniqu/core";
|
|
4
3
|
import { DbValidationContext, ValidatorMode, ValidatorMode as ValidatorMode$1 } from "@atscript/db/validator";
|
|
4
|
+
import { DbPatch, DbResponse, DbRow, TDbDeleteResult as TDbDeleteResult$1, TDbInsertIgnoreResult as TDbInsertIgnoreResult$1, TDbInsertManyIgnoreResult as TDbInsertManyIgnoreResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1, TFieldMeta, TMetaResponse, TRelationInfo, TSearchIndexInfo } from "@atscript/db";
|
|
5
5
|
|
|
6
6
|
//#region src/types.d.ts
|
|
7
7
|
/**
|
|
@@ -96,6 +96,18 @@ type DataOf<T> = T extends {
|
|
|
96
96
|
__dataType?: infer D;
|
|
97
97
|
};
|
|
98
98
|
} ? unknown extends D ? T extends (new (...a: any[]) => infer I) ? I : Record<string, unknown> : D & Record<string, unknown> : Record<string, unknown>;
|
|
99
|
+
/**
|
|
100
|
+
* `$select` controls of a read over `T` whose controller declares the display-only
|
|
101
|
+
* fields `D` (`@DbDecorations`): `$select` also accepts `keyof D`; filter, sort
|
|
102
|
+
* and every other control stay over the own fields. Since 0.1.148.
|
|
103
|
+
*/
|
|
104
|
+
type DecoratedControls<T, D> = Omit<UniqueryControls<OwnOf<T>, NavOf<T>>, "$select"> & {
|
|
105
|
+
$select?: SelectExpr<OwnOf<T> & D>;
|
|
106
|
+
};
|
|
107
|
+
/** A read query over `T` — {@link DecoratedControls} for the controls. Since 0.1.148. */
|
|
108
|
+
type DecoratedQuery<T, D> = Omit<Uniquery$1<OwnOf<T>, NavOf<T>>, "controls"> & {
|
|
109
|
+
controls?: DecoratedControls<T, D>;
|
|
110
|
+
};
|
|
99
111
|
/** Extract own (non-nav) properties from an Atscript annotated type. */
|
|
100
112
|
type OwnOf<T> = T extends {
|
|
101
113
|
__ownProps: infer O;
|
|
@@ -132,6 +144,28 @@ type ClientResponse<T, Q> = DbResponse<DataOf<T>, NavOf<T>, Q> & {
|
|
|
132
144
|
*/
|
|
133
145
|
$disabledReasons?: Record<string, string>;
|
|
134
146
|
};
|
|
147
|
+
/**
|
|
148
|
+
* "Every row matching this query" — the target of
|
|
149
|
+
* `Client.actionOnQuery()` / `countActionTarget()`, for a `'rows'` action
|
|
150
|
+
* whose `/meta` entry carries `queryTarget`. The same filter / search /
|
|
151
|
+
* index the user's `/query` used; the server resolves it under the caller's
|
|
152
|
+
* read scope and the action's own gate.
|
|
153
|
+
*
|
|
154
|
+
* @since 0.1.147
|
|
155
|
+
*/
|
|
156
|
+
interface TDbQueryTarget<T = AtscriptClientShape> {
|
|
157
|
+
filter?: Uniquery$1<OwnOf<T>, NavOf<T>>["filter"];
|
|
158
|
+
/** `$search` term. */
|
|
159
|
+
search?: string;
|
|
160
|
+
/** `$index` — the search index `search` uses. */
|
|
161
|
+
index?: string;
|
|
162
|
+
/** Identifiers to leave out (any identification of the controller the request goes to). */
|
|
163
|
+
exclude?: Record<string, unknown>[];
|
|
164
|
+
/** Fail with 409 `TARGET_CHANGED` when the target no longer matches exactly this many rows. */
|
|
165
|
+
expectCount?: number;
|
|
166
|
+
/** Client-side cap (never above the action's `queryTarget.maxRows`). */
|
|
167
|
+
maxRows?: number;
|
|
168
|
+
}
|
|
135
169
|
//#endregion
|
|
136
170
|
//#region src/validator.d.ts
|
|
137
171
|
/** Options for {@link ClientValidator} / {@link createClientValidator}. */
|
|
@@ -214,4 +248,4 @@ declare class ClientValidationError extends Error {
|
|
|
214
248
|
*/
|
|
215
249
|
declare function createClientValidator(meta: MetaResponse, opts?: ClientValidatorOptions): ClientValidator;
|
|
216
250
|
//#endregion
|
|
217
|
-
export {
|
|
251
|
+
export { SearchIndexInfo as A, Uniquery$1 as B, MetaResponse as C, PatchOf as D, PageResult as E, TDbInsertManyResult$1 as F, ValidGroupBy$1 as H, TDbInsertResult$1 as I, TDbQueryTarget as L, TDbDeleteResult$1 as M, TDbInsertIgnoreResult$1 as N, RelationInfo as O, TDbInsertManyIgnoreResult$1 as P, TDbUpdateResult$1 as R, IdOf as S, OwnOf as T, WeekStart as U, UniqueryControls as V, DbRow as _, ValidatorMode$1 as a, FieldMeta as b, AggregateResult$1 as c, BucketUnit as d, CalendarBucketLabel as f, DbPatch as g, DataOf as h, DbValidationContext as i, ServerError as j, RowOf 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, DecoratedControls as v, NavOf as w, FilterExpr as x, DecoratedQuery as y, TypedWithRelation as z };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, BucketExpr as BucketExpr$1, BucketUnit, CalendarBucketLabel, FilterExpr, SelectExpr, TypedWithRelation, Uniquery as Uniquery$1, UniqueryControls, ValidGroupBy as ValidGroupBy$1, WeekStart } from "@uniqu/core";
|
|
1
2
|
import { TAtscriptAnnotatedType, TAtscriptTypeObject } from "@atscript/typescript/utils";
|
|
2
|
-
import {
|
|
3
|
+
import { DbPatch, DbResponse, DbRow, TDbDeleteResult as TDbDeleteResult$1, TDbInsertIgnoreResult as TDbInsertIgnoreResult$1, TDbInsertManyIgnoreResult as TDbInsertManyIgnoreResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1, TFieldMeta, TMetaResponse, TRelationInfo, TSearchIndexInfo } from "@atscript/db";
|
|
3
4
|
import { DbValidationContext, ValidatorMode, ValidatorMode as ValidatorMode$1 } from "@atscript/db/validator";
|
|
4
|
-
import { DbPatch, DbResponse, DbRow, TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1, TFieldMeta, TMetaResponse, TRelationInfo, TSearchIndexInfo } from "@atscript/db";
|
|
5
5
|
|
|
6
6
|
//#region src/types.d.ts
|
|
7
7
|
/**
|
|
@@ -96,6 +96,18 @@ type DataOf<T> = T extends {
|
|
|
96
96
|
__dataType?: infer D;
|
|
97
97
|
};
|
|
98
98
|
} ? unknown extends D ? T extends (new (...a: any[]) => infer I) ? I : Record<string, unknown> : D & Record<string, unknown> : Record<string, unknown>;
|
|
99
|
+
/**
|
|
100
|
+
* `$select` controls of a read over `T` whose controller declares the display-only
|
|
101
|
+
* fields `D` (`@DbDecorations`): `$select` also accepts `keyof D`; filter, sort
|
|
102
|
+
* and every other control stay over the own fields. Since 0.1.148.
|
|
103
|
+
*/
|
|
104
|
+
type DecoratedControls<T, D> = Omit<UniqueryControls<OwnOf<T>, NavOf<T>>, "$select"> & {
|
|
105
|
+
$select?: SelectExpr<OwnOf<T> & D>;
|
|
106
|
+
};
|
|
107
|
+
/** A read query over `T` — {@link DecoratedControls} for the controls. Since 0.1.148. */
|
|
108
|
+
type DecoratedQuery<T, D> = Omit<Uniquery$1<OwnOf<T>, NavOf<T>>, "controls"> & {
|
|
109
|
+
controls?: DecoratedControls<T, D>;
|
|
110
|
+
};
|
|
99
111
|
/** Extract own (non-nav) properties from an Atscript annotated type. */
|
|
100
112
|
type OwnOf<T> = T extends {
|
|
101
113
|
__ownProps: infer O;
|
|
@@ -132,6 +144,28 @@ type ClientResponse<T, Q> = DbResponse<DataOf<T>, NavOf<T>, Q> & {
|
|
|
132
144
|
*/
|
|
133
145
|
$disabledReasons?: Record<string, string>;
|
|
134
146
|
};
|
|
147
|
+
/**
|
|
148
|
+
* "Every row matching this query" — the target of
|
|
149
|
+
* `Client.actionOnQuery()` / `countActionTarget()`, for a `'rows'` action
|
|
150
|
+
* whose `/meta` entry carries `queryTarget`. The same filter / search /
|
|
151
|
+
* index the user's `/query` used; the server resolves it under the caller's
|
|
152
|
+
* read scope and the action's own gate.
|
|
153
|
+
*
|
|
154
|
+
* @since 0.1.147
|
|
155
|
+
*/
|
|
156
|
+
interface TDbQueryTarget<T = AtscriptClientShape> {
|
|
157
|
+
filter?: Uniquery$1<OwnOf<T>, NavOf<T>>["filter"];
|
|
158
|
+
/** `$search` term. */
|
|
159
|
+
search?: string;
|
|
160
|
+
/** `$index` — the search index `search` uses. */
|
|
161
|
+
index?: string;
|
|
162
|
+
/** Identifiers to leave out (any identification of the controller the request goes to). */
|
|
163
|
+
exclude?: Record<string, unknown>[];
|
|
164
|
+
/** Fail with 409 `TARGET_CHANGED` when the target no longer matches exactly this many rows. */
|
|
165
|
+
expectCount?: number;
|
|
166
|
+
/** Client-side cap (never above the action's `queryTarget.maxRows`). */
|
|
167
|
+
maxRows?: number;
|
|
168
|
+
}
|
|
135
169
|
//#endregion
|
|
136
170
|
//#region src/validator.d.ts
|
|
137
171
|
/** Options for {@link ClientValidator} / {@link createClientValidator}. */
|
|
@@ -214,4 +248,4 @@ declare class ClientValidationError extends Error {
|
|
|
214
248
|
*/
|
|
215
249
|
declare function createClientValidator(meta: MetaResponse, opts?: ClientValidatorOptions): ClientValidator;
|
|
216
250
|
//#endregion
|
|
217
|
-
export {
|
|
251
|
+
export { SearchIndexInfo as A, Uniquery$1 as B, MetaResponse as C, PatchOf as D, PageResult as E, TDbInsertManyResult$1 as F, ValidGroupBy$1 as H, TDbInsertResult$1 as I, TDbQueryTarget as L, TDbDeleteResult$1 as M, TDbInsertIgnoreResult$1 as N, RelationInfo as O, TDbInsertManyIgnoreResult$1 as P, TDbUpdateResult$1 as R, IdOf as S, OwnOf as T, WeekStart as U, UniqueryControls as V, DbRow as _, ValidatorMode$1 as a, FieldMeta as b, AggregateResult$1 as c, BucketUnit as d, CalendarBucketLabel as f, DbPatch as g, DataOf as h, DbValidationContext as i, ServerError as j, RowOf 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, DecoratedControls as v, NavOf as w, FilterExpr as x, DecoratedQuery as y, TypedWithRelation 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-CVM7eNP7.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-CPmmXTAt.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.148",
|
|
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.13",
|
|
47
|
+
"@uniqu/url": "^0.1.13"
|
|
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.100",
|
|
51
|
+
"@atscript/typescript": "^0.1.100",
|
|
52
|
+
"unplugin-atscript": "^0.1.100",
|
|
53
|
+
"@atscript/db": "0.1.148"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@atscript/db": "^0.1.44",
|
|
57
|
-
"@atscript/typescript": "^0.1.
|
|
57
|
+
"@atscript/typescript": "^0.1.100"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"postinstall": "asc -f dts",
|