@atscript/db-client 0.1.58 → 0.1.60
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 +86 -34
- package/dist/index.d.cts +66 -25
- package/dist/index.d.mts +66 -25
- package/dist/index.mjs +84 -35
- package/dist/{validator-DNm9kCoq.d.cts → validator-BminFmAf.d.cts} +33 -2
- package/dist/{validator-DfNMCEKa.d.mts → validator-DkIQ_0Lc.d.mts} +33 -2
- package/dist/validator.d.cts +1 -1
- package/dist/validator.d.mts +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -22,7 +22,7 @@ var ClientError = class extends Error {
|
|
|
22
22
|
* `name === 'ActionDisabledError'`. The transport / status / base body are
|
|
23
23
|
* identical to a generic `ClientError`; this subclass adds typed accessors
|
|
24
24
|
* so consumers can write `catch (e) { if (e instanceof ActionDisabledError) … }`
|
|
25
|
-
* to access `action` / `
|
|
25
|
+
* to access `action` / `id` / `ids` without indexing into `body`.
|
|
26
26
|
*/
|
|
27
27
|
var ActionDisabledError = class extends ClientError {
|
|
28
28
|
name = "ActionDisabledError";
|
|
@@ -31,12 +31,12 @@ var ActionDisabledError = class extends ClientError {
|
|
|
31
31
|
return this.body.action;
|
|
32
32
|
}
|
|
33
33
|
/** Present only for `'row'`-level rejections. */
|
|
34
|
-
get
|
|
35
|
-
return this.body.
|
|
34
|
+
get id() {
|
|
35
|
+
return this.body.id;
|
|
36
36
|
}
|
|
37
|
-
/** Present only for `'rows'`-level rejections (full list of failing
|
|
38
|
-
get
|
|
39
|
-
return this.body.
|
|
37
|
+
/** Present only for `'rows'`-level rejections (full list of failing IDs). */
|
|
38
|
+
get ids() {
|
|
39
|
+
return this.body.ids;
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
/** Thrown by `Client.action()` when the action name is not present in `/meta`. */
|
|
@@ -101,6 +101,9 @@ var Client = class {
|
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* `GET /query` — query records with typed filter, sort, select, and relations.
|
|
104
|
+
*
|
|
105
|
+
* The response type narrows by the literal `$with` array in `query` —
|
|
106
|
+
* relations not listed in `$with` are stripped from the row type.
|
|
104
107
|
*/
|
|
105
108
|
async query(query) {
|
|
106
109
|
return this._get("query", query);
|
|
@@ -122,6 +125,9 @@ var Client = class {
|
|
|
122
125
|
}
|
|
123
126
|
/**
|
|
124
127
|
* `GET /pages` — paginated query with typed filter and relations.
|
|
128
|
+
*
|
|
129
|
+
* Response rows narrow by the literal `$with` array — same algebra as
|
|
130
|
+
* {@link query}.
|
|
125
131
|
*/
|
|
126
132
|
async pages(query, page = 1, size = 10) {
|
|
127
133
|
return this._get("pages", {
|
|
@@ -136,7 +142,8 @@ var Client = class {
|
|
|
136
142
|
/**
|
|
137
143
|
* `GET /one/:id` or `GET /one?k1=v1&k2=v2` — single record by primary key.
|
|
138
144
|
*
|
|
139
|
-
* Returns `null` on 404.
|
|
145
|
+
* Returns `null` on 404. Response narrows by the literal `$with` array in
|
|
146
|
+
* `query.controls` — same algebra as {@link query}.
|
|
140
147
|
*/
|
|
141
148
|
async one(id, query) {
|
|
142
149
|
const controlStr = query?.controls ? (0, _uniqu_url_builder.buildUrl)({ controls: query.controls }) : "";
|
|
@@ -187,33 +194,45 @@ var Client = class {
|
|
|
187
194
|
* Invoke a declared action by name. Resolves the action descriptor from the
|
|
188
195
|
* cached `/meta` response, then dispatches based on `processor`:
|
|
189
196
|
*
|
|
190
|
-
* - `'backend'` → POST
|
|
197
|
+
* - `'backend'` → POST the identifier as a JSON body to the action's path
|
|
191
198
|
* and return the parsed server response. The HTTP method is always POST.
|
|
192
199
|
* - `'navigate'` → for `level: 'row'`, substitute `$1` in `value` with the
|
|
193
|
-
*
|
|
194
|
-
* with `/`); for `level: 'rows'` or
|
|
195
|
-
* verbatim. The default navigator (browser
|
|
196
|
-
* `window.location.assign(url)`. Provide
|
|
197
|
-
* integrate with a SPA router.
|
|
200
|
+
* identifier values, walking `meta.preferredId` field order (each value
|
|
201
|
+
* URL-encoded, compound IDs joined with `/`); for `level: 'rows'` or
|
|
202
|
+
* `'table'`, navigate to `value` verbatim. The default navigator (browser
|
|
203
|
+
* only) calls `window.location.assign(url)`. Provide
|
|
204
|
+
* `ClientOptions.navigate` to integrate with a SPA router.
|
|
198
205
|
* - `'custom'` → throw {@link ActionUnsupportedError}; UI-dispatched events
|
|
199
206
|
* are the application's responsibility, not the client's.
|
|
200
207
|
*
|
|
201
208
|
* Throws {@link ActionNotFoundError} when the action is not present in `/meta`.
|
|
202
209
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
210
|
+
* **Identifier shape (server contract).** `id` is always an object (single)
|
|
211
|
+
* or array of objects (multi) — never a scalar. Each object's field set
|
|
212
|
+
* must exactly match one legitimate identification on the table (PK or any
|
|
213
|
+
* `@db.index.unique` group). Even single-field PK tables send `{ id: 'abc' }`,
|
|
214
|
+
* not `'abc'`. `level: 'table'` actions take no identifier (`undefined`).
|
|
215
|
+
*
|
|
216
|
+
* The TypeScript signature widens to `Partial<Own<T>>` because the server's
|
|
217
|
+
* exact-match validation cannot be expressed at the type level. Mismatched
|
|
218
|
+
* field sets produce HTTP 400; obvious type errors (scalars, `null`) are
|
|
219
|
+
* caught at compile time when `T` is typed.
|
|
220
|
+
*
|
|
221
|
+
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
222
|
+
* server returns whatever the handler emits (commonly
|
|
223
|
+
* `{ message?: string, ... }`); the client cannot validate.
|
|
206
224
|
*/
|
|
207
|
-
async action(name,
|
|
208
|
-
const
|
|
225
|
+
async action(name, id) {
|
|
226
|
+
const meta = await this.meta();
|
|
227
|
+
const action = meta.actions.find((a) => a.name === name);
|
|
209
228
|
if (!action) throw new ActionNotFoundError(name);
|
|
210
229
|
if (action.processor === "custom") throw new ActionUnsupportedError(name, "custom", `Action "${name}" has processor "custom" — applications must dispatch custom actions themselves; the client cannot.`);
|
|
211
230
|
if (action.processor === "navigate") {
|
|
212
|
-
const url = this._interpolateNavigateUrl(action,
|
|
231
|
+
const url = this._interpolateNavigateUrl(action, id, meta.preferredId);
|
|
213
232
|
await this._dispatchNavigate(action, url);
|
|
214
233
|
return;
|
|
215
234
|
}
|
|
216
|
-
const body = this._buildActionBody(action,
|
|
235
|
+
const body = this._buildActionBody(action, id);
|
|
217
236
|
return this._postAction(action, body);
|
|
218
237
|
}
|
|
219
238
|
/**
|
|
@@ -233,16 +252,21 @@ var Client = class {
|
|
|
233
252
|
});
|
|
234
253
|
return this._validatorPromise;
|
|
235
254
|
}
|
|
236
|
-
_buildActionBody(action,
|
|
255
|
+
_buildActionBody(action, id) {
|
|
237
256
|
if (action.level === "table") return void 0;
|
|
238
|
-
if (action.level
|
|
239
|
-
|
|
240
|
-
|
|
257
|
+
if (action.level === "rows") {
|
|
258
|
+
if (id === void 0) return [];
|
|
259
|
+
if (!Array.isArray(id)) throw new TypeError(`client.action("${action.name}"): rows-level actions require an array of identifier objects; received ${describeShape(id)}.`);
|
|
260
|
+
return id;
|
|
261
|
+
}
|
|
262
|
+
if (id !== null && typeof id === "object" && !Array.isArray(id)) return id;
|
|
263
|
+
throw new TypeError(`client.action("${action.name}"): row-level actions require an identifier object; received ${describeShape(id)}.`);
|
|
241
264
|
}
|
|
242
|
-
_interpolateNavigateUrl(action,
|
|
265
|
+
_interpolateNavigateUrl(action, id, preferredId) {
|
|
243
266
|
if (action.level !== "row") return action.value;
|
|
244
|
-
if (
|
|
245
|
-
|
|
267
|
+
if (id === void 0) return action.value;
|
|
268
|
+
if (id === null || typeof id !== "object" || Array.isArray(id)) throw new TypeError(`client.action("${action.name}"): row-level navigate actions require an identifier object; received ${describeShape(id)}.`);
|
|
269
|
+
return action.value.replace(/\$1/g, encodeNavigateId(id, preferredId));
|
|
246
270
|
}
|
|
247
271
|
async _dispatchNavigate(action, url) {
|
|
248
272
|
if (this._navigate) {
|
|
@@ -326,14 +350,39 @@ var Client = class {
|
|
|
326
350
|
}
|
|
327
351
|
};
|
|
328
352
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
|
|
353
|
+
* Render a single identifier field for substitution into a navigate-URL
|
|
354
|
+
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
355
|
+
* (NOT the literal `"undefined"` / `"null"` that `String()` would produce).
|
|
356
|
+
*/
|
|
357
|
+
function formatIdentifierField(v) {
|
|
358
|
+
if (v === null || v === void 0) return "";
|
|
359
|
+
if (typeof v === "string") return v;
|
|
360
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
|
|
361
|
+
return JSON.stringify(v);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Render a row identifier as a `/`-joined string in `preferredId`
|
|
365
|
+
* declaration order — NOT object-key insertion order (which is unstable
|
|
366
|
+
* across callers). Raw form, no URL-encoding; for prompt text, error
|
|
367
|
+
* messages, log lines, etc.
|
|
368
|
+
*/
|
|
369
|
+
function formatIdentifier(id, preferredId) {
|
|
370
|
+
if (id === void 0) return "";
|
|
371
|
+
return preferredId.map((f) => formatIdentifierField(id[f])).join("/");
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* URL-encoded form of `formatIdentifier` — for `processor: 'navigate'`
|
|
375
|
+
* `$1` substitution. Each field is `encodeURIComponent`'d, then joined
|
|
376
|
+
* with a literal `/`. Missing fields render as empty segments (e.g.
|
|
377
|
+
* `acme//jane`), not the literal `"undefined"`.
|
|
333
378
|
*/
|
|
334
|
-
function
|
|
335
|
-
|
|
336
|
-
|
|
379
|
+
function encodeNavigateId(id, preferredId) {
|
|
380
|
+
return preferredId.map((f) => encodeURIComponent(formatIdentifierField(id[f]))).join("/");
|
|
381
|
+
}
|
|
382
|
+
function describeShape(value) {
|
|
383
|
+
if (value === null) return "null";
|
|
384
|
+
if (Array.isArray(value)) return "array";
|
|
385
|
+
return typeof value;
|
|
337
386
|
}
|
|
338
387
|
//#endregion
|
|
339
388
|
exports.ActionDisabledError = ActionDisabledError;
|
|
@@ -341,3 +390,6 @@ exports.ActionNotFoundError = ActionNotFoundError;
|
|
|
341
390
|
exports.ActionUnsupportedError = ActionUnsupportedError;
|
|
342
391
|
exports.Client = Client;
|
|
343
392
|
exports.ClientError = ClientError;
|
|
393
|
+
exports.encodeNavigateId = encodeNavigateId;
|
|
394
|
+
exports.formatIdentifier = formatIdentifier;
|
|
395
|
+
exports.formatIdentifierField = formatIdentifierField;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as TDbInsertManyResult, D as Uniquery, E as TypedWithRelation, O as UniqueryControls, S as TDbDeleteResult, T as TDbUpdateResult, _ as OwnOf, b as SearchIndexInfo, c as AtscriptClientShape, d as DataOf, f as FieldMeta, g as NavOf, h as MetaResponse, i as ValidatorMode, l as ClientOptions, m as IdOf, n as ClientValidator, o as AggregateQuery, p as FilterExpr, s as AggregateResult, t as ClientValidationError, u as ClientResponse, v as PageResult, w as TDbInsertResult, x as ServerError, y as RelationInfo } from "./validator-BminFmAf.cjs";
|
|
2
2
|
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1 } from "@uniqu/core";
|
|
3
3
|
import { TCrudOp, TCrudPermissions, TDbActionInfo, TDbActionIntent, TDbActionLevel, TDbActionProcessor, TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1 } from "@atscript/db";
|
|
4
4
|
import { TSerializedAnnotatedType } from "@atscript/typescript/utils";
|
|
@@ -8,6 +8,7 @@ type Own<T> = OwnOf<T>;
|
|
|
8
8
|
type Nav<T> = NavOf<T>;
|
|
9
9
|
type Data<T> = DataOf<T>;
|
|
10
10
|
type Id<T> = IdOf<T>;
|
|
11
|
+
type Response<T, Q> = ClientResponse<T, Q>;
|
|
11
12
|
/**
|
|
12
13
|
* HTTP client for moost-db REST endpoints.
|
|
13
14
|
*
|
|
@@ -29,7 +30,7 @@ type Id<T> = IdOf<T>;
|
|
|
29
30
|
* const page = await users.pages({ filter: { active: true } }, 1, 20)
|
|
30
31
|
* ```
|
|
31
32
|
*/
|
|
32
|
-
declare class Client<T =
|
|
33
|
+
declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
33
34
|
private readonly _path;
|
|
34
35
|
private readonly _baseUrl;
|
|
35
36
|
private readonly _fetch;
|
|
@@ -40,8 +41,11 @@ declare class Client<T = Record<string, unknown>> {
|
|
|
40
41
|
constructor(path: string, opts?: ClientOptions);
|
|
41
42
|
/**
|
|
42
43
|
* `GET /query` — query records with typed filter, sort, select, and relations.
|
|
44
|
+
*
|
|
45
|
+
* The response type narrows by the literal `$with` array in `query` —
|
|
46
|
+
* relations not listed in `$with` are stripped from the row type.
|
|
43
47
|
*/
|
|
44
|
-
query
|
|
48
|
+
query<Q extends Uniquery$1<Own<T>, Nav<T>> = Uniquery$1<Own<T>, Nav<T>>>(query?: Q): Promise<Response<T, Q>[]>;
|
|
45
49
|
/**
|
|
46
50
|
* `GET /query` with `$count: true` — returns record count.
|
|
47
51
|
*/
|
|
@@ -57,16 +61,22 @@ declare class Client<T = Record<string, unknown>> {
|
|
|
57
61
|
})[] ? AggregateResult$1<Own<T>, Q["controls"]["$select"]>[] : Record<string, unknown>[]>;
|
|
58
62
|
/**
|
|
59
63
|
* `GET /pages` — paginated query with typed filter and relations.
|
|
64
|
+
*
|
|
65
|
+
* Response rows narrow by the literal `$with` array — same algebra as
|
|
66
|
+
* {@link query}.
|
|
60
67
|
*/
|
|
61
|
-
pages
|
|
68
|
+
pages<Q extends Uniquery$1<Own<T>, Nav<T>> = Uniquery$1<Own<T>, Nav<T>>>(query?: Q, page?: number, size?: number): Promise<PageResult<Response<T, Q>>>;
|
|
62
69
|
/**
|
|
63
70
|
* `GET /one/:id` or `GET /one?k1=v1&k2=v2` — single record by primary key.
|
|
64
71
|
*
|
|
65
|
-
* Returns `null` on 404.
|
|
72
|
+
* Returns `null` on 404. Response narrows by the literal `$with` array in
|
|
73
|
+
* `query.controls` — same algebra as {@link query}.
|
|
66
74
|
*/
|
|
67
|
-
one
|
|
75
|
+
one<Q extends {
|
|
76
|
+
controls?: UniqueryControls$1<Own<T>, Nav<T>>;
|
|
77
|
+
} = {
|
|
68
78
|
controls?: UniqueryControls$1<Own<T>, Nav<T>>;
|
|
69
|
-
}): Promise<
|
|
79
|
+
}>(id: Id<T>, query?: Q): Promise<Response<T, Q> | null>;
|
|
70
80
|
/**
|
|
71
81
|
* `POST /` — insert one record.
|
|
72
82
|
*/
|
|
@@ -95,24 +105,35 @@ declare class Client<T = Record<string, unknown>> {
|
|
|
95
105
|
* Invoke a declared action by name. Resolves the action descriptor from the
|
|
96
106
|
* cached `/meta` response, then dispatches based on `processor`:
|
|
97
107
|
*
|
|
98
|
-
* - `'backend'` → POST
|
|
108
|
+
* - `'backend'` → POST the identifier as a JSON body to the action's path
|
|
99
109
|
* and return the parsed server response. The HTTP method is always POST.
|
|
100
110
|
* - `'navigate'` → for `level: 'row'`, substitute `$1` in `value` with the
|
|
101
|
-
*
|
|
102
|
-
* with `/`); for `level: 'rows'` or
|
|
103
|
-
* verbatim. The default navigator (browser
|
|
104
|
-
* `window.location.assign(url)`. Provide
|
|
105
|
-
* integrate with a SPA router.
|
|
111
|
+
* identifier values, walking `meta.preferredId` field order (each value
|
|
112
|
+
* URL-encoded, compound IDs joined with `/`); for `level: 'rows'` or
|
|
113
|
+
* `'table'`, navigate to `value` verbatim. The default navigator (browser
|
|
114
|
+
* only) calls `window.location.assign(url)`. Provide
|
|
115
|
+
* `ClientOptions.navigate` to integrate with a SPA router.
|
|
106
116
|
* - `'custom'` → throw {@link ActionUnsupportedError}; UI-dispatched events
|
|
107
117
|
* are the application's responsibility, not the client's.
|
|
108
118
|
*
|
|
109
119
|
* Throws {@link ActionNotFoundError} when the action is not present in `/meta`.
|
|
110
120
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
121
|
+
* **Identifier shape (server contract).** `id` is always an object (single)
|
|
122
|
+
* or array of objects (multi) — never a scalar. Each object's field set
|
|
123
|
+
* must exactly match one legitimate identification on the table (PK or any
|
|
124
|
+
* `@db.index.unique` group). Even single-field PK tables send `{ id: 'abc' }`,
|
|
125
|
+
* not `'abc'`. `level: 'table'` actions take no identifier (`undefined`).
|
|
126
|
+
*
|
|
127
|
+
* The TypeScript signature widens to `Partial<Own<T>>` because the server's
|
|
128
|
+
* exact-match validation cannot be expressed at the type level. Mismatched
|
|
129
|
+
* field sets produce HTTP 400; obvious type errors (scalars, `null`) are
|
|
130
|
+
* caught at compile time when `T` is typed.
|
|
131
|
+
*
|
|
132
|
+
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
133
|
+
* server returns whatever the handler emits (commonly
|
|
134
|
+
* `{ message?: string, ... }`); the client cannot validate.
|
|
114
135
|
*/
|
|
115
|
-
action(name: string,
|
|
136
|
+
action<R = unknown>(name: string, id?: Partial<Own<T>> | Partial<Own<T>>[]): Promise<R>;
|
|
116
137
|
/**
|
|
117
138
|
* Returns a lazily-initialized {@link ClientValidator} backed by the `/meta` type.
|
|
118
139
|
* Useful for accessing `flatMap` and `navFields` (e.g. for form generation).
|
|
@@ -132,6 +153,26 @@ declare class Client<T = Record<string, unknown>> {
|
|
|
132
153
|
private _buildInit;
|
|
133
154
|
private _send;
|
|
134
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Render a single identifier field for substitution into a navigate-URL
|
|
158
|
+
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
159
|
+
* (NOT the literal `"undefined"` / `"null"` that `String()` would produce).
|
|
160
|
+
*/
|
|
161
|
+
declare function formatIdentifierField(v: unknown): string;
|
|
162
|
+
/**
|
|
163
|
+
* Render a row identifier as a `/`-joined string in `preferredId`
|
|
164
|
+
* declaration order — NOT object-key insertion order (which is unstable
|
|
165
|
+
* across callers). Raw form, no URL-encoding; for prompt text, error
|
|
166
|
+
* messages, log lines, etc.
|
|
167
|
+
*/
|
|
168
|
+
declare function formatIdentifier(id: Record<string, unknown> | undefined, preferredId: readonly string[]): string;
|
|
169
|
+
/**
|
|
170
|
+
* URL-encoded form of `formatIdentifier` — for `processor: 'navigate'`
|
|
171
|
+
* `$1` substitution. Each field is `encodeURIComponent`'d, then joined
|
|
172
|
+
* with a literal `/`. Missing fields render as empty segments (e.g.
|
|
173
|
+
* `acme//jane`), not the literal `"undefined"`.
|
|
174
|
+
*/
|
|
175
|
+
declare function encodeNavigateId(id: Record<string, unknown>, preferredId: readonly string[]): string;
|
|
135
176
|
//#endregion
|
|
136
177
|
//#region src/client-error.d.ts
|
|
137
178
|
/**
|
|
@@ -159,31 +200,31 @@ declare class ClientError extends Error {
|
|
|
159
200
|
/**
|
|
160
201
|
* Wire-body shape for `ActionDisabledError` responses (HTTP 409). Extends
|
|
161
202
|
* the base `ServerError` envelope with a `name` discriminator, the action
|
|
162
|
-
* name, and the offending
|
|
203
|
+
* name, and the offending identifier(s). The bridge between `@atscript/moost-db`'s
|
|
163
204
|
* server-side error and this typed client-side subclass is the wire JSON
|
|
164
205
|
* body — neither package depends on the other.
|
|
165
206
|
*/
|
|
166
207
|
interface ActionDisabledErrorBody extends ServerError {
|
|
167
208
|
name: "ActionDisabledError";
|
|
168
209
|
action: string;
|
|
169
|
-
|
|
170
|
-
|
|
210
|
+
id?: Record<string, unknown>;
|
|
211
|
+
ids?: Record<string, unknown>[];
|
|
171
212
|
}
|
|
172
213
|
/**
|
|
173
214
|
* Typed marker thrown by `Client._send` when the server response body's
|
|
174
215
|
* `name === 'ActionDisabledError'`. The transport / status / base body are
|
|
175
216
|
* identical to a generic `ClientError`; this subclass adds typed accessors
|
|
176
217
|
* so consumers can write `catch (e) { if (e instanceof ActionDisabledError) … }`
|
|
177
|
-
* to access `action` / `
|
|
218
|
+
* to access `action` / `id` / `ids` without indexing into `body`.
|
|
178
219
|
*/
|
|
179
220
|
declare class ActionDisabledError extends ClientError {
|
|
180
221
|
name: string;
|
|
181
222
|
/** The `@DbAction` name that rejected the request. */
|
|
182
223
|
get action(): string;
|
|
183
224
|
/** Present only for `'row'`-level rejections. */
|
|
184
|
-
get
|
|
185
|
-
/** Present only for `'rows'`-level rejections (full list of failing
|
|
186
|
-
get
|
|
225
|
+
get id(): Record<string, unknown> | undefined;
|
|
226
|
+
/** Present only for `'rows'`-level rejections (full list of failing IDs). */
|
|
227
|
+
get ids(): Record<string, unknown>[] | undefined;
|
|
187
228
|
}
|
|
188
229
|
/** Thrown by `Client.action()` when the action name is not present in `/meta`. */
|
|
189
230
|
declare class ActionNotFoundError extends Error {
|
|
@@ -204,4 +245,4 @@ declare class ActionUnsupportedError extends Error {
|
|
|
204
245
|
constructor(action: string, processor: string, message: string);
|
|
205
246
|
}
|
|
206
247
|
//#endregion
|
|
207
|
-
export { ActionDisabledError, type ActionDisabledErrorBody, ActionNotFoundError, ActionUnsupportedError, type AggregateQuery, type AggregateResult, Client, ClientError, type ClientOptions, type ClientValidationError, type ClientValidator, type DataOf, type FieldMeta, type FilterExpr, type IdOf, type MetaResponse, type NavOf, type OwnOf, type PageResult, type RelationInfo, type SearchIndexInfo, type ServerError, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbDeleteResult, type TDbInsertManyResult, type TDbInsertResult, type TDbUpdateResult, type TSerializedAnnotatedType, type TypedWithRelation, type Uniquery, type UniqueryControls, type ValidatorMode };
|
|
248
|
+
export { ActionDisabledError, type ActionDisabledErrorBody, ActionNotFoundError, ActionUnsupportedError, type AggregateQuery, type AggregateResult, type AtscriptClientShape, Client, ClientError, type ClientOptions, type ClientResponse, type ClientValidationError, type ClientValidator, type DataOf, type FieldMeta, type FilterExpr, type IdOf, type MetaResponse, type NavOf, type OwnOf, type PageResult, type RelationInfo, type SearchIndexInfo, type ServerError, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbDeleteResult, type TDbInsertManyResult, type TDbInsertResult, type TDbUpdateResult, type TSerializedAnnotatedType, type TypedWithRelation, type Uniquery, type UniqueryControls, type ValidatorMode, encodeNavigateId, formatIdentifier, formatIdentifierField };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as TDbInsertManyResult, D as Uniquery, E as TypedWithRelation, O as UniqueryControls, S as TDbDeleteResult, T as TDbUpdateResult, _ as OwnOf, b as SearchIndexInfo, c as AtscriptClientShape, d as DataOf, f as FieldMeta, g as NavOf, h as MetaResponse, i as ValidatorMode, l as ClientOptions, m as IdOf, n as ClientValidator, o as AggregateQuery, p as FilterExpr, s as AggregateResult, t as ClientValidationError, u as ClientResponse, v as PageResult, w as TDbInsertResult, x as ServerError, y as RelationInfo } from "./validator-DkIQ_0Lc.mjs";
|
|
2
2
|
import { TSerializedAnnotatedType } from "@atscript/typescript/utils";
|
|
3
3
|
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1 } from "@uniqu/core";
|
|
4
4
|
import { TCrudOp, TCrudPermissions, TDbActionInfo, TDbActionIntent, TDbActionLevel, TDbActionProcessor, TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1 } from "@atscript/db";
|
|
@@ -8,6 +8,7 @@ type Own<T> = OwnOf<T>;
|
|
|
8
8
|
type Nav<T> = NavOf<T>;
|
|
9
9
|
type Data<T> = DataOf<T>;
|
|
10
10
|
type Id<T> = IdOf<T>;
|
|
11
|
+
type Response<T, Q> = ClientResponse<T, Q>;
|
|
11
12
|
/**
|
|
12
13
|
* HTTP client for moost-db REST endpoints.
|
|
13
14
|
*
|
|
@@ -29,7 +30,7 @@ type Id<T> = IdOf<T>;
|
|
|
29
30
|
* const page = await users.pages({ filter: { active: true } }, 1, 20)
|
|
30
31
|
* ```
|
|
31
32
|
*/
|
|
32
|
-
declare class Client<T =
|
|
33
|
+
declare class Client<T extends AtscriptClientShape = AtscriptClientShape> {
|
|
33
34
|
private readonly _path;
|
|
34
35
|
private readonly _baseUrl;
|
|
35
36
|
private readonly _fetch;
|
|
@@ -40,8 +41,11 @@ declare class Client<T = Record<string, unknown>> {
|
|
|
40
41
|
constructor(path: string, opts?: ClientOptions);
|
|
41
42
|
/**
|
|
42
43
|
* `GET /query` — query records with typed filter, sort, select, and relations.
|
|
44
|
+
*
|
|
45
|
+
* The response type narrows by the literal `$with` array in `query` —
|
|
46
|
+
* relations not listed in `$with` are stripped from the row type.
|
|
43
47
|
*/
|
|
44
|
-
query
|
|
48
|
+
query<Q extends Uniquery$1<Own<T>, Nav<T>> = Uniquery$1<Own<T>, Nav<T>>>(query?: Q): Promise<Response<T, Q>[]>;
|
|
45
49
|
/**
|
|
46
50
|
* `GET /query` with `$count: true` — returns record count.
|
|
47
51
|
*/
|
|
@@ -57,16 +61,22 @@ declare class Client<T = Record<string, unknown>> {
|
|
|
57
61
|
})[] ? AggregateResult$1<Own<T>, Q["controls"]["$select"]>[] : Record<string, unknown>[]>;
|
|
58
62
|
/**
|
|
59
63
|
* `GET /pages` — paginated query with typed filter and relations.
|
|
64
|
+
*
|
|
65
|
+
* Response rows narrow by the literal `$with` array — same algebra as
|
|
66
|
+
* {@link query}.
|
|
60
67
|
*/
|
|
61
|
-
pages
|
|
68
|
+
pages<Q extends Uniquery$1<Own<T>, Nav<T>> = Uniquery$1<Own<T>, Nav<T>>>(query?: Q, page?: number, size?: number): Promise<PageResult<Response<T, Q>>>;
|
|
62
69
|
/**
|
|
63
70
|
* `GET /one/:id` or `GET /one?k1=v1&k2=v2` — single record by primary key.
|
|
64
71
|
*
|
|
65
|
-
* Returns `null` on 404.
|
|
72
|
+
* Returns `null` on 404. Response narrows by the literal `$with` array in
|
|
73
|
+
* `query.controls` — same algebra as {@link query}.
|
|
66
74
|
*/
|
|
67
|
-
one
|
|
75
|
+
one<Q extends {
|
|
76
|
+
controls?: UniqueryControls$1<Own<T>, Nav<T>>;
|
|
77
|
+
} = {
|
|
68
78
|
controls?: UniqueryControls$1<Own<T>, Nav<T>>;
|
|
69
|
-
}): Promise<
|
|
79
|
+
}>(id: Id<T>, query?: Q): Promise<Response<T, Q> | null>;
|
|
70
80
|
/**
|
|
71
81
|
* `POST /` — insert one record.
|
|
72
82
|
*/
|
|
@@ -95,24 +105,35 @@ declare class Client<T = Record<string, unknown>> {
|
|
|
95
105
|
* Invoke a declared action by name. Resolves the action descriptor from the
|
|
96
106
|
* cached `/meta` response, then dispatches based on `processor`:
|
|
97
107
|
*
|
|
98
|
-
* - `'backend'` → POST
|
|
108
|
+
* - `'backend'` → POST the identifier as a JSON body to the action's path
|
|
99
109
|
* and return the parsed server response. The HTTP method is always POST.
|
|
100
110
|
* - `'navigate'` → for `level: 'row'`, substitute `$1` in `value` with the
|
|
101
|
-
*
|
|
102
|
-
* with `/`); for `level: 'rows'` or
|
|
103
|
-
* verbatim. The default navigator (browser
|
|
104
|
-
* `window.location.assign(url)`. Provide
|
|
105
|
-
* integrate with a SPA router.
|
|
111
|
+
* identifier values, walking `meta.preferredId` field order (each value
|
|
112
|
+
* URL-encoded, compound IDs joined with `/`); for `level: 'rows'` or
|
|
113
|
+
* `'table'`, navigate to `value` verbatim. The default navigator (browser
|
|
114
|
+
* only) calls `window.location.assign(url)`. Provide
|
|
115
|
+
* `ClientOptions.navigate` to integrate with a SPA router.
|
|
106
116
|
* - `'custom'` → throw {@link ActionUnsupportedError}; UI-dispatched events
|
|
107
117
|
* are the application's responsibility, not the client's.
|
|
108
118
|
*
|
|
109
119
|
* Throws {@link ActionNotFoundError} when the action is not present in `/meta`.
|
|
110
120
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
121
|
+
* **Identifier shape (server contract).** `id` is always an object (single)
|
|
122
|
+
* or array of objects (multi) — never a scalar. Each object's field set
|
|
123
|
+
* must exactly match one legitimate identification on the table (PK or any
|
|
124
|
+
* `@db.index.unique` group). Even single-field PK tables send `{ id: 'abc' }`,
|
|
125
|
+
* not `'abc'`. `level: 'table'` actions take no identifier (`undefined`).
|
|
126
|
+
*
|
|
127
|
+
* The TypeScript signature widens to `Partial<Own<T>>` because the server's
|
|
128
|
+
* exact-match validation cannot be expressed at the type level. Mismatched
|
|
129
|
+
* field sets produce HTTP 400; obvious type errors (scalars, `null`) are
|
|
130
|
+
* caught at compile time when `T` is typed.
|
|
131
|
+
*
|
|
132
|
+
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
133
|
+
* server returns whatever the handler emits (commonly
|
|
134
|
+
* `{ message?: string, ... }`); the client cannot validate.
|
|
114
135
|
*/
|
|
115
|
-
action(name: string,
|
|
136
|
+
action<R = unknown>(name: string, id?: Partial<Own<T>> | Partial<Own<T>>[]): Promise<R>;
|
|
116
137
|
/**
|
|
117
138
|
* Returns a lazily-initialized {@link ClientValidator} backed by the `/meta` type.
|
|
118
139
|
* Useful for accessing `flatMap` and `navFields` (e.g. for form generation).
|
|
@@ -132,6 +153,26 @@ declare class Client<T = Record<string, unknown>> {
|
|
|
132
153
|
private _buildInit;
|
|
133
154
|
private _send;
|
|
134
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Render a single identifier field for substitution into a navigate-URL
|
|
158
|
+
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
159
|
+
* (NOT the literal `"undefined"` / `"null"` that `String()` would produce).
|
|
160
|
+
*/
|
|
161
|
+
declare function formatIdentifierField(v: unknown): string;
|
|
162
|
+
/**
|
|
163
|
+
* Render a row identifier as a `/`-joined string in `preferredId`
|
|
164
|
+
* declaration order — NOT object-key insertion order (which is unstable
|
|
165
|
+
* across callers). Raw form, no URL-encoding; for prompt text, error
|
|
166
|
+
* messages, log lines, etc.
|
|
167
|
+
*/
|
|
168
|
+
declare function formatIdentifier(id: Record<string, unknown> | undefined, preferredId: readonly string[]): string;
|
|
169
|
+
/**
|
|
170
|
+
* URL-encoded form of `formatIdentifier` — for `processor: 'navigate'`
|
|
171
|
+
* `$1` substitution. Each field is `encodeURIComponent`'d, then joined
|
|
172
|
+
* with a literal `/`. Missing fields render as empty segments (e.g.
|
|
173
|
+
* `acme//jane`), not the literal `"undefined"`.
|
|
174
|
+
*/
|
|
175
|
+
declare function encodeNavigateId(id: Record<string, unknown>, preferredId: readonly string[]): string;
|
|
135
176
|
//#endregion
|
|
136
177
|
//#region src/client-error.d.ts
|
|
137
178
|
/**
|
|
@@ -159,31 +200,31 @@ declare class ClientError extends Error {
|
|
|
159
200
|
/**
|
|
160
201
|
* Wire-body shape for `ActionDisabledError` responses (HTTP 409). Extends
|
|
161
202
|
* the base `ServerError` envelope with a `name` discriminator, the action
|
|
162
|
-
* name, and the offending
|
|
203
|
+
* name, and the offending identifier(s). The bridge between `@atscript/moost-db`'s
|
|
163
204
|
* server-side error and this typed client-side subclass is the wire JSON
|
|
164
205
|
* body — neither package depends on the other.
|
|
165
206
|
*/
|
|
166
207
|
interface ActionDisabledErrorBody extends ServerError {
|
|
167
208
|
name: "ActionDisabledError";
|
|
168
209
|
action: string;
|
|
169
|
-
|
|
170
|
-
|
|
210
|
+
id?: Record<string, unknown>;
|
|
211
|
+
ids?: Record<string, unknown>[];
|
|
171
212
|
}
|
|
172
213
|
/**
|
|
173
214
|
* Typed marker thrown by `Client._send` when the server response body's
|
|
174
215
|
* `name === 'ActionDisabledError'`. The transport / status / base body are
|
|
175
216
|
* identical to a generic `ClientError`; this subclass adds typed accessors
|
|
176
217
|
* so consumers can write `catch (e) { if (e instanceof ActionDisabledError) … }`
|
|
177
|
-
* to access `action` / `
|
|
218
|
+
* to access `action` / `id` / `ids` without indexing into `body`.
|
|
178
219
|
*/
|
|
179
220
|
declare class ActionDisabledError extends ClientError {
|
|
180
221
|
name: string;
|
|
181
222
|
/** The `@DbAction` name that rejected the request. */
|
|
182
223
|
get action(): string;
|
|
183
224
|
/** Present only for `'row'`-level rejections. */
|
|
184
|
-
get
|
|
185
|
-
/** Present only for `'rows'`-level rejections (full list of failing
|
|
186
|
-
get
|
|
225
|
+
get id(): Record<string, unknown> | undefined;
|
|
226
|
+
/** Present only for `'rows'`-level rejections (full list of failing IDs). */
|
|
227
|
+
get ids(): Record<string, unknown>[] | undefined;
|
|
187
228
|
}
|
|
188
229
|
/** Thrown by `Client.action()` when the action name is not present in `/meta`. */
|
|
189
230
|
declare class ActionNotFoundError extends Error {
|
|
@@ -204,4 +245,4 @@ declare class ActionUnsupportedError extends Error {
|
|
|
204
245
|
constructor(action: string, processor: string, message: string);
|
|
205
246
|
}
|
|
206
247
|
//#endregion
|
|
207
|
-
export { ActionDisabledError, type ActionDisabledErrorBody, ActionNotFoundError, ActionUnsupportedError, type AggregateQuery, type AggregateResult, Client, ClientError, type ClientOptions, type ClientValidationError, type ClientValidator, type DataOf, type FieldMeta, type FilterExpr, type IdOf, type MetaResponse, type NavOf, type OwnOf, type PageResult, type RelationInfo, type SearchIndexInfo, type ServerError, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbDeleteResult, type TDbInsertManyResult, type TDbInsertResult, type TDbUpdateResult, type TSerializedAnnotatedType, type TypedWithRelation, type Uniquery, type UniqueryControls, type ValidatorMode };
|
|
248
|
+
export { ActionDisabledError, type ActionDisabledErrorBody, ActionNotFoundError, ActionUnsupportedError, type AggregateQuery, type AggregateResult, type AtscriptClientShape, Client, ClientError, type ClientOptions, type ClientResponse, type ClientValidationError, type ClientValidator, type DataOf, type FieldMeta, type FilterExpr, type IdOf, type MetaResponse, type NavOf, type OwnOf, type PageResult, type RelationInfo, type SearchIndexInfo, type ServerError, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbDeleteResult, type TDbInsertManyResult, type TDbInsertResult, type TDbUpdateResult, type TSerializedAnnotatedType, type TypedWithRelation, type Uniquery, type UniqueryControls, type ValidatorMode, encodeNavigateId, formatIdentifier, formatIdentifierField };
|
package/dist/index.mjs
CHANGED
|
@@ -21,7 +21,7 @@ var ClientError = class extends Error {
|
|
|
21
21
|
* `name === 'ActionDisabledError'`. The transport / status / base body are
|
|
22
22
|
* identical to a generic `ClientError`; this subclass adds typed accessors
|
|
23
23
|
* so consumers can write `catch (e) { if (e instanceof ActionDisabledError) … }`
|
|
24
|
-
* to access `action` / `
|
|
24
|
+
* to access `action` / `id` / `ids` without indexing into `body`.
|
|
25
25
|
*/
|
|
26
26
|
var ActionDisabledError = class extends ClientError {
|
|
27
27
|
name = "ActionDisabledError";
|
|
@@ -30,12 +30,12 @@ var ActionDisabledError = class extends ClientError {
|
|
|
30
30
|
return this.body.action;
|
|
31
31
|
}
|
|
32
32
|
/** Present only for `'row'`-level rejections. */
|
|
33
|
-
get
|
|
34
|
-
return this.body.
|
|
33
|
+
get id() {
|
|
34
|
+
return this.body.id;
|
|
35
35
|
}
|
|
36
|
-
/** Present only for `'rows'`-level rejections (full list of failing
|
|
37
|
-
get
|
|
38
|
-
return this.body.
|
|
36
|
+
/** Present only for `'rows'`-level rejections (full list of failing IDs). */
|
|
37
|
+
get ids() {
|
|
38
|
+
return this.body.ids;
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
/** Thrown by `Client.action()` when the action name is not present in `/meta`. */
|
|
@@ -100,6 +100,9 @@ var Client = class {
|
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
102
|
* `GET /query` — query records with typed filter, sort, select, and relations.
|
|
103
|
+
*
|
|
104
|
+
* The response type narrows by the literal `$with` array in `query` —
|
|
105
|
+
* relations not listed in `$with` are stripped from the row type.
|
|
103
106
|
*/
|
|
104
107
|
async query(query) {
|
|
105
108
|
return this._get("query", query);
|
|
@@ -121,6 +124,9 @@ var Client = class {
|
|
|
121
124
|
}
|
|
122
125
|
/**
|
|
123
126
|
* `GET /pages` — paginated query with typed filter and relations.
|
|
127
|
+
*
|
|
128
|
+
* Response rows narrow by the literal `$with` array — same algebra as
|
|
129
|
+
* {@link query}.
|
|
124
130
|
*/
|
|
125
131
|
async pages(query, page = 1, size = 10) {
|
|
126
132
|
return this._get("pages", {
|
|
@@ -135,7 +141,8 @@ var Client = class {
|
|
|
135
141
|
/**
|
|
136
142
|
* `GET /one/:id` or `GET /one?k1=v1&k2=v2` — single record by primary key.
|
|
137
143
|
*
|
|
138
|
-
* Returns `null` on 404.
|
|
144
|
+
* Returns `null` on 404. Response narrows by the literal `$with` array in
|
|
145
|
+
* `query.controls` — same algebra as {@link query}.
|
|
139
146
|
*/
|
|
140
147
|
async one(id, query) {
|
|
141
148
|
const controlStr = query?.controls ? buildUrl({ controls: query.controls }) : "";
|
|
@@ -186,33 +193,45 @@ var Client = class {
|
|
|
186
193
|
* Invoke a declared action by name. Resolves the action descriptor from the
|
|
187
194
|
* cached `/meta` response, then dispatches based on `processor`:
|
|
188
195
|
*
|
|
189
|
-
* - `'backend'` → POST
|
|
196
|
+
* - `'backend'` → POST the identifier as a JSON body to the action's path
|
|
190
197
|
* and return the parsed server response. The HTTP method is always POST.
|
|
191
198
|
* - `'navigate'` → for `level: 'row'`, substitute `$1` in `value` with the
|
|
192
|
-
*
|
|
193
|
-
* with `/`); for `level: 'rows'` or
|
|
194
|
-
* verbatim. The default navigator (browser
|
|
195
|
-
* `window.location.assign(url)`. Provide
|
|
196
|
-
* integrate with a SPA router.
|
|
199
|
+
* identifier values, walking `meta.preferredId` field order (each value
|
|
200
|
+
* URL-encoded, compound IDs joined with `/`); for `level: 'rows'` or
|
|
201
|
+
* `'table'`, navigate to `value` verbatim. The default navigator (browser
|
|
202
|
+
* only) calls `window.location.assign(url)`. Provide
|
|
203
|
+
* `ClientOptions.navigate` to integrate with a SPA router.
|
|
197
204
|
* - `'custom'` → throw {@link ActionUnsupportedError}; UI-dispatched events
|
|
198
205
|
* are the application's responsibility, not the client's.
|
|
199
206
|
*
|
|
200
207
|
* Throws {@link ActionNotFoundError} when the action is not present in `/meta`.
|
|
201
208
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
209
|
+
* **Identifier shape (server contract).** `id` is always an object (single)
|
|
210
|
+
* or array of objects (multi) — never a scalar. Each object's field set
|
|
211
|
+
* must exactly match one legitimate identification on the table (PK or any
|
|
212
|
+
* `@db.index.unique` group). Even single-field PK tables send `{ id: 'abc' }`,
|
|
213
|
+
* not `'abc'`. `level: 'table'` actions take no identifier (`undefined`).
|
|
214
|
+
*
|
|
215
|
+
* The TypeScript signature widens to `Partial<Own<T>>` because the server's
|
|
216
|
+
* exact-match validation cannot be expressed at the type level. Mismatched
|
|
217
|
+
* field sets produce HTTP 400; obvious type errors (scalars, `null`) are
|
|
218
|
+
* caught at compile time when `T` is typed.
|
|
219
|
+
*
|
|
220
|
+
* @typeParam R Caller-asserted return shape from the action handler. The
|
|
221
|
+
* server returns whatever the handler emits (commonly
|
|
222
|
+
* `{ message?: string, ... }`); the client cannot validate.
|
|
205
223
|
*/
|
|
206
|
-
async action(name,
|
|
207
|
-
const
|
|
224
|
+
async action(name, id) {
|
|
225
|
+
const meta = await this.meta();
|
|
226
|
+
const action = meta.actions.find((a) => a.name === name);
|
|
208
227
|
if (!action) throw new ActionNotFoundError(name);
|
|
209
228
|
if (action.processor === "custom") throw new ActionUnsupportedError(name, "custom", `Action "${name}" has processor "custom" — applications must dispatch custom actions themselves; the client cannot.`);
|
|
210
229
|
if (action.processor === "navigate") {
|
|
211
|
-
const url = this._interpolateNavigateUrl(action,
|
|
230
|
+
const url = this._interpolateNavigateUrl(action, id, meta.preferredId);
|
|
212
231
|
await this._dispatchNavigate(action, url);
|
|
213
232
|
return;
|
|
214
233
|
}
|
|
215
|
-
const body = this._buildActionBody(action,
|
|
234
|
+
const body = this._buildActionBody(action, id);
|
|
216
235
|
return this._postAction(action, body);
|
|
217
236
|
}
|
|
218
237
|
/**
|
|
@@ -232,16 +251,21 @@ var Client = class {
|
|
|
232
251
|
});
|
|
233
252
|
return this._validatorPromise;
|
|
234
253
|
}
|
|
235
|
-
_buildActionBody(action,
|
|
254
|
+
_buildActionBody(action, id) {
|
|
236
255
|
if (action.level === "table") return void 0;
|
|
237
|
-
if (action.level
|
|
238
|
-
|
|
239
|
-
|
|
256
|
+
if (action.level === "rows") {
|
|
257
|
+
if (id === void 0) return [];
|
|
258
|
+
if (!Array.isArray(id)) throw new TypeError(`client.action("${action.name}"): rows-level actions require an array of identifier objects; received ${describeShape(id)}.`);
|
|
259
|
+
return id;
|
|
260
|
+
}
|
|
261
|
+
if (id !== null && typeof id === "object" && !Array.isArray(id)) return id;
|
|
262
|
+
throw new TypeError(`client.action("${action.name}"): row-level actions require an identifier object; received ${describeShape(id)}.`);
|
|
240
263
|
}
|
|
241
|
-
_interpolateNavigateUrl(action,
|
|
264
|
+
_interpolateNavigateUrl(action, id, preferredId) {
|
|
242
265
|
if (action.level !== "row") return action.value;
|
|
243
|
-
if (
|
|
244
|
-
|
|
266
|
+
if (id === void 0) return action.value;
|
|
267
|
+
if (id === null || typeof id !== "object" || Array.isArray(id)) throw new TypeError(`client.action("${action.name}"): row-level navigate actions require an identifier object; received ${describeShape(id)}.`);
|
|
268
|
+
return action.value.replace(/\$1/g, encodeNavigateId(id, preferredId));
|
|
245
269
|
}
|
|
246
270
|
async _dispatchNavigate(action, url) {
|
|
247
271
|
if (this._navigate) {
|
|
@@ -325,14 +349,39 @@ var Client = class {
|
|
|
325
349
|
}
|
|
326
350
|
};
|
|
327
351
|
/**
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
|
|
352
|
+
* Render a single identifier field for substitution into a navigate-URL
|
|
353
|
+
* template or human-readable string. `null` / `undefined` collapse to `""`
|
|
354
|
+
* (NOT the literal `"undefined"` / `"null"` that `String()` would produce).
|
|
355
|
+
*/
|
|
356
|
+
function formatIdentifierField(v) {
|
|
357
|
+
if (v === null || v === void 0) return "";
|
|
358
|
+
if (typeof v === "string") return v;
|
|
359
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
|
|
360
|
+
return JSON.stringify(v);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Render a row identifier as a `/`-joined string in `preferredId`
|
|
364
|
+
* declaration order — NOT object-key insertion order (which is unstable
|
|
365
|
+
* across callers). Raw form, no URL-encoding; for prompt text, error
|
|
366
|
+
* messages, log lines, etc.
|
|
367
|
+
*/
|
|
368
|
+
function formatIdentifier(id, preferredId) {
|
|
369
|
+
if (id === void 0) return "";
|
|
370
|
+
return preferredId.map((f) => formatIdentifierField(id[f])).join("/");
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* URL-encoded form of `formatIdentifier` — for `processor: 'navigate'`
|
|
374
|
+
* `$1` substitution. Each field is `encodeURIComponent`'d, then joined
|
|
375
|
+
* with a literal `/`. Missing fields render as empty segments (e.g.
|
|
376
|
+
* `acme//jane`), not the literal `"undefined"`.
|
|
332
377
|
*/
|
|
333
|
-
function
|
|
334
|
-
|
|
335
|
-
|
|
378
|
+
function encodeNavigateId(id, preferredId) {
|
|
379
|
+
return preferredId.map((f) => encodeURIComponent(formatIdentifierField(id[f]))).join("/");
|
|
380
|
+
}
|
|
381
|
+
function describeShape(value) {
|
|
382
|
+
if (value === null) return "null";
|
|
383
|
+
if (Array.isArray(value)) return "array";
|
|
384
|
+
return typeof value;
|
|
336
385
|
}
|
|
337
386
|
//#endregion
|
|
338
|
-
export { ActionDisabledError, ActionNotFoundError, ActionUnsupportedError, Client, ClientError };
|
|
387
|
+
export { ActionDisabledError, ActionNotFoundError, ActionUnsupportedError, Client, ClientError, encodeNavigateId, formatIdentifier, formatIdentifierField };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, FilterExpr, TypedWithRelation, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1 } from "@uniqu/core";
|
|
2
|
-
import { TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1, TFieldMeta, TMetaResponse, TRelationInfo, TSearchIndexInfo } from "@atscript/db";
|
|
2
|
+
import { DbResponse, TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1, TFieldMeta, TMetaResponse, TRelationInfo, TSearchIndexInfo } from "@atscript/db";
|
|
3
3
|
import { TAtscriptAnnotatedType, TAtscriptTypeObject } from "@atscript/typescript/utils";
|
|
4
4
|
import { DbValidationContext, ValidatorMode, ValidatorMode as ValidatorMode$1 } from "@atscript/db/validator";
|
|
5
5
|
|
|
@@ -59,6 +59,21 @@ interface ServerError {
|
|
|
59
59
|
details?: unknown[];
|
|
60
60
|
}>;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Minimal brand shape every `Client<T>` generic must satisfy. All fields are
|
|
64
|
+
* optional — plain interfaces and `Record<string, unknown>` satisfy this
|
|
65
|
+
* constraint, so `new Client('/path')` (no generic) keeps working with
|
|
66
|
+
* `unknown` / `Record<string, unknown>` fallbacks. Atscript-generated types
|
|
67
|
+
* fill these brand fields and unlock per-method inference.
|
|
68
|
+
*/
|
|
69
|
+
type AtscriptClientShape = {
|
|
70
|
+
__pk?: unknown;
|
|
71
|
+
__ownProps?: Record<string, unknown>;
|
|
72
|
+
__navProps?: Record<string, unknown>;
|
|
73
|
+
type?: {
|
|
74
|
+
__dataType?: unknown;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
62
77
|
/** Extract the data type from an Atscript annotated type `T`. */
|
|
63
78
|
type DataOf<T> = T extends {
|
|
64
79
|
type: {
|
|
@@ -77,6 +92,22 @@ type NavOf<T> = T extends {
|
|
|
77
92
|
type IdOf<T> = T extends {
|
|
78
93
|
__pk: infer PK;
|
|
79
94
|
} ? PK : unknown;
|
|
95
|
+
/**
|
|
96
|
+
* Narrow a read-method response type by the literal `$with` array in the
|
|
97
|
+
* query, mirroring the backend's `DbResponse<Data, Nav, Q>` algebra. Nav
|
|
98
|
+
* properties are stripped by default and re-added only for relations the
|
|
99
|
+
* caller listed in `$with`. When `T` carries no nav-prop brand, `DbResponse`
|
|
100
|
+
* short-circuits to the data type. `$actions` is always optional — the
|
|
101
|
+
* server emits it only when the request set `?$actions=true`.
|
|
102
|
+
*/
|
|
103
|
+
type ClientResponse<T, Q> = DbResponse<DataOf<T>, NavOf<T>, Q> & {
|
|
104
|
+
/**
|
|
105
|
+
* Server-evaluated per-row availability for `'row'` and `'rows'`-level
|
|
106
|
+
* actions. Each entry is the `name` of an action that is NOT disabled for
|
|
107
|
+
* this row.
|
|
108
|
+
*/
|
|
109
|
+
$actions?: string[];
|
|
110
|
+
};
|
|
80
111
|
//#endregion
|
|
81
112
|
//#region src/validator.d.ts
|
|
82
113
|
/**
|
|
@@ -125,4 +156,4 @@ declare class ClientValidationError extends Error {
|
|
|
125
156
|
*/
|
|
126
157
|
declare function createClientValidator(meta: MetaResponse): ClientValidator;
|
|
127
158
|
//#endregion
|
|
128
|
-
export {
|
|
159
|
+
export { TDbInsertManyResult$1 as C, Uniquery$1 as D, TypedWithRelation as E, UniqueryControls$1 as O, TDbDeleteResult$1 as S, TDbUpdateResult$1 as T, OwnOf as _, createClientValidator as a, SearchIndexInfo as b, AtscriptClientShape as c, DataOf as d, FieldMeta as f, NavOf as g, MetaResponse as h, ValidatorMode$1 as i, ClientOptions as l, IdOf as m, ClientValidator as n, AggregateQuery$1 as o, FilterExpr as p, DbValidationContext as r, AggregateResult$1 as s, ClientValidationError as t, ClientResponse as u, PageResult as v, TDbInsertResult$1 as w, ServerError as x, RelationInfo as y };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TAtscriptAnnotatedType, TAtscriptTypeObject } from "@atscript/typescript/utils";
|
|
2
2
|
import { DbValidationContext, ValidatorMode, ValidatorMode as ValidatorMode$1 } from "@atscript/db/validator";
|
|
3
3
|
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, FilterExpr, TypedWithRelation, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1 } from "@uniqu/core";
|
|
4
|
-
import { TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1, TFieldMeta, TMetaResponse, TRelationInfo, TSearchIndexInfo } from "@atscript/db";
|
|
4
|
+
import { DbResponse, 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
|
/** Options for creating a Client instance. */
|
|
@@ -59,6 +59,21 @@ interface ServerError {
|
|
|
59
59
|
details?: unknown[];
|
|
60
60
|
}>;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Minimal brand shape every `Client<T>` generic must satisfy. All fields are
|
|
64
|
+
* optional — plain interfaces and `Record<string, unknown>` satisfy this
|
|
65
|
+
* constraint, so `new Client('/path')` (no generic) keeps working with
|
|
66
|
+
* `unknown` / `Record<string, unknown>` fallbacks. Atscript-generated types
|
|
67
|
+
* fill these brand fields and unlock per-method inference.
|
|
68
|
+
*/
|
|
69
|
+
type AtscriptClientShape = {
|
|
70
|
+
__pk?: unknown;
|
|
71
|
+
__ownProps?: Record<string, unknown>;
|
|
72
|
+
__navProps?: Record<string, unknown>;
|
|
73
|
+
type?: {
|
|
74
|
+
__dataType?: unknown;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
62
77
|
/** Extract the data type from an Atscript annotated type `T`. */
|
|
63
78
|
type DataOf<T> = T extends {
|
|
64
79
|
type: {
|
|
@@ -77,6 +92,22 @@ type NavOf<T> = T extends {
|
|
|
77
92
|
type IdOf<T> = T extends {
|
|
78
93
|
__pk: infer PK;
|
|
79
94
|
} ? PK : unknown;
|
|
95
|
+
/**
|
|
96
|
+
* Narrow a read-method response type by the literal `$with` array in the
|
|
97
|
+
* query, mirroring the backend's `DbResponse<Data, Nav, Q>` algebra. Nav
|
|
98
|
+
* properties are stripped by default and re-added only for relations the
|
|
99
|
+
* caller listed in `$with`. When `T` carries no nav-prop brand, `DbResponse`
|
|
100
|
+
* short-circuits to the data type. `$actions` is always optional — the
|
|
101
|
+
* server emits it only when the request set `?$actions=true`.
|
|
102
|
+
*/
|
|
103
|
+
type ClientResponse<T, Q> = DbResponse<DataOf<T>, NavOf<T>, Q> & {
|
|
104
|
+
/**
|
|
105
|
+
* Server-evaluated per-row availability for `'row'` and `'rows'`-level
|
|
106
|
+
* actions. Each entry is the `name` of an action that is NOT disabled for
|
|
107
|
+
* this row.
|
|
108
|
+
*/
|
|
109
|
+
$actions?: string[];
|
|
110
|
+
};
|
|
80
111
|
//#endregion
|
|
81
112
|
//#region src/validator.d.ts
|
|
82
113
|
/**
|
|
@@ -125,4 +156,4 @@ declare class ClientValidationError extends Error {
|
|
|
125
156
|
*/
|
|
126
157
|
declare function createClientValidator(meta: MetaResponse): ClientValidator;
|
|
127
158
|
//#endregion
|
|
128
|
-
export {
|
|
159
|
+
export { TDbInsertManyResult$1 as C, Uniquery$1 as D, TypedWithRelation as E, UniqueryControls$1 as O, TDbDeleteResult$1 as S, TDbUpdateResult$1 as T, OwnOf as _, createClientValidator as a, SearchIndexInfo as b, AtscriptClientShape as c, DataOf as d, FieldMeta as f, NavOf as g, MetaResponse as h, ValidatorMode$1 as i, ClientOptions as l, IdOf as m, ClientValidator as n, AggregateQuery$1 as o, FilterExpr as p, DbValidationContext as r, AggregateResult$1 as s, ClientValidationError as t, ClientResponse as u, PageResult as v, TDbInsertResult$1 as w, ServerError as x, RelationInfo as y };
|
package/dist/validator.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as createClientValidator, i as ValidatorMode, n as ClientValidator, r as DbValidationContext, t as ClientValidationError } from "./validator-
|
|
1
|
+
import { a as createClientValidator, i as ValidatorMode, n as ClientValidator, r as DbValidationContext, t as ClientValidationError } from "./validator-BminFmAf.cjs";
|
|
2
2
|
export { ClientValidationError, ClientValidator, DbValidationContext, ValidatorMode, createClientValidator };
|
package/dist/validator.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as createClientValidator, i as ValidatorMode, n as ClientValidator, r as DbValidationContext, t as ClientValidationError } from "./validator-
|
|
1
|
+
import { a as createClientValidator, i as ValidatorMode, n as ClientValidator, r as DbValidationContext, t as ClientValidationError } from "./validator-DkIQ_0Lc.mjs";
|
|
2
2
|
export { ClientValidationError, ClientValidator, DbValidationContext, 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.60",
|
|
4
4
|
"description": "Browser-compatible HTTP client for @atscript/moost-db REST endpoints.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@atscript/typescript": "^0.1.50",
|
|
51
51
|
"@uniqu/core": "^0.1.5",
|
|
52
52
|
"unplugin-atscript": "^0.1.50",
|
|
53
|
-
"@atscript/db": "0.1.
|
|
53
|
+
"@atscript/db": "0.1.60"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@atscript/db": "^0.1.44",
|