@atscript/db-client 0.1.58 → 0.1.59

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 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` / `pk` / `pks` without indexing into `body`.
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 pk() {
35
- return this.body.pk;
34
+ get id() {
35
+ return this.body.id;
36
36
  }
37
- /** Present only for `'rows'`-level rejections (full list of failing PKs). */
38
- get pks() {
39
- return this.body.pks;
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 `pk` (or `pks`) as a JSON body to the action's path
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
- * PK (URL-encoded; composite PKs are URL-encoded per field and joined
194
- * with `/`); for `level: 'rows'` or `'table'`, navigate to `value`
195
- * verbatim. The default navigator (browser only) calls
196
- * `window.location.assign(url)`. Provide `ClientOptions.navigate` to
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
- * For `level: 'rows'`, `pk` must be an array. If a non-array is supplied
204
- * for a `'rows'` action it is wrapped into a single-element array the
205
- * server-side `@DbActionPKs()` resolver requires an array body.
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, pk) {
208
- const action = (await this.meta()).actions.find((a) => a.name === name);
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, pk);
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, pk);
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, pk) {
255
+ _buildActionBody(action, id) {
237
256
  if (action.level === "table") return void 0;
238
- if (action.level !== "rows") return pk;
239
- if (Array.isArray(pk)) return pk;
240
- return pk === void 0 ? [] : [pk];
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, pk) {
265
+ _interpolateNavigateUrl(action, id, preferredId) {
243
266
  if (action.level !== "row") return action.value;
244
- if (pk === void 0) return action.value;
245
- return action.value.replace(/\$1/g, encodeNavigatePk(pk));
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,18 @@ var Client = class {
326
350
  }
327
351
  };
328
352
  /**
329
- * Encode a row PK for substitution into a `processor: 'navigate'` URL template.
330
- * Scalars are URL-encoded directly; composite PK objects have each value
331
- * URL-encoded and joined with `/` in object-key order (which mirrors
332
- * `primaryKeys` for the table).
353
+ * Encode a row identifier for substitution into a `processor: 'navigate'` URL template.
354
+ * The values are URL-encoded and joined with `/` in `meta.preferredId` field
355
+ * declaration order NOT in object-key insertion order (which is unstable
356
+ * across callers).
333
357
  */
334
- function encodeNavigatePk(pk) {
335
- if (pk === null || pk === void 0) return "";
336
- return (typeof pk === "object" ? Object.values(pk) : [pk]).map((v) => encodeURIComponent(String(v))).join("/");
358
+ function encodeNavigateId(id, preferredId) {
359
+ return preferredId.map((f) => encodeURIComponent(String(id[f]))).join("/");
360
+ }
361
+ function describeShape(value) {
362
+ if (value === null) return "null";
363
+ if (Array.isArray(value)) return "array";
364
+ return typeof value;
337
365
  }
338
366
  //#endregion
339
367
  exports.ActionDisabledError = ActionDisabledError;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as TDbUpdateResult, E as UniqueryControls, S as TDbInsertResult, T as Uniquery, _ as RelationInfo, b as TDbDeleteResult, c as ClientOptions, d as FilterExpr, f as IdOf, g as PageResult, h as OwnOf, i as ValidatorMode, l as DataOf, m as NavOf, n as ClientValidator, o as AggregateQuery, p as MetaResponse, s as AggregateResult, t as ClientValidationError, u as FieldMeta, v as SearchIndexInfo, w as TypedWithRelation, x as TDbInsertManyResult, y as ServerError } from "./validator-DNm9kCoq.cjs";
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 = Record<string, unknown>> {
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(query?: Uniquery$1<Own<T>, Nav<T>>): Promise<Data<T>[]>;
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(query?: Uniquery$1<Own<T>, Nav<T>>, page?: number, size?: number): Promise<PageResult<Data<T>>>;
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(id: Id<T>, query?: {
75
+ one<Q extends {
76
+ controls?: UniqueryControls$1<Own<T>, Nav<T>>;
77
+ } = {
68
78
  controls?: UniqueryControls$1<Own<T>, Nav<T>>;
69
- }): Promise<Data<T> | null>;
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 `pk` (or `pks`) as a JSON body to the action's path
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
- * PK (URL-encoded; composite PKs are URL-encoded per field and joined
102
- * with `/`); for `level: 'rows'` or `'table'`, navigate to `value`
103
- * verbatim. The default navigator (browser only) calls
104
- * `window.location.assign(url)`. Provide `ClientOptions.navigate` to
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
- * For `level: 'rows'`, `pk` must be an array. If a non-array is supplied
112
- * for a `'rows'` action it is wrapped into a single-element array the
113
- * server-side `@DbActionPKs()` resolver requires an array body.
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, pk?: unknown): Promise<unknown>;
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).
@@ -159,31 +180,31 @@ declare class ClientError extends Error {
159
180
  /**
160
181
  * Wire-body shape for `ActionDisabledError` responses (HTTP 409). Extends
161
182
  * the base `ServerError` envelope with a `name` discriminator, the action
162
- * name, and the offending PK(s). The bridge between `@atscript/moost-db`'s
183
+ * name, and the offending identifier(s). The bridge between `@atscript/moost-db`'s
163
184
  * server-side error and this typed client-side subclass is the wire JSON
164
185
  * body — neither package depends on the other.
165
186
  */
166
187
  interface ActionDisabledErrorBody extends ServerError {
167
188
  name: "ActionDisabledError";
168
189
  action: string;
169
- pk?: unknown;
170
- pks?: unknown[];
190
+ id?: Record<string, unknown>;
191
+ ids?: Record<string, unknown>[];
171
192
  }
172
193
  /**
173
194
  * Typed marker thrown by `Client._send` when the server response body's
174
195
  * `name === 'ActionDisabledError'`. The transport / status / base body are
175
196
  * identical to a generic `ClientError`; this subclass adds typed accessors
176
197
  * so consumers can write `catch (e) { if (e instanceof ActionDisabledError) … }`
177
- * to access `action` / `pk` / `pks` without indexing into `body`.
198
+ * to access `action` / `id` / `ids` without indexing into `body`.
178
199
  */
179
200
  declare class ActionDisabledError extends ClientError {
180
201
  name: string;
181
202
  /** The `@DbAction` name that rejected the request. */
182
203
  get action(): string;
183
204
  /** Present only for `'row'`-level rejections. */
184
- get pk(): unknown;
185
- /** Present only for `'rows'`-level rejections (full list of failing PKs). */
186
- get pks(): unknown[] | undefined;
205
+ get id(): Record<string, unknown> | undefined;
206
+ /** Present only for `'rows'`-level rejections (full list of failing IDs). */
207
+ get ids(): Record<string, unknown>[] | undefined;
187
208
  }
188
209
  /** Thrown by `Client.action()` when the action name is not present in `/meta`. */
189
210
  declare class ActionNotFoundError extends Error {
@@ -204,4 +225,4 @@ declare class ActionUnsupportedError extends Error {
204
225
  constructor(action: string, processor: string, message: string);
205
226
  }
206
227
  //#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 };
228
+ 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 };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as TDbUpdateResult, E as UniqueryControls, S as TDbInsertResult, T as Uniquery, _ as RelationInfo, b as TDbDeleteResult, c as ClientOptions, d as FilterExpr, f as IdOf, g as PageResult, h as OwnOf, i as ValidatorMode, l as DataOf, m as NavOf, n as ClientValidator, o as AggregateQuery, p as MetaResponse, s as AggregateResult, t as ClientValidationError, u as FieldMeta, v as SearchIndexInfo, w as TypedWithRelation, x as TDbInsertManyResult, y as ServerError } from "./validator-DfNMCEKa.mjs";
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 = Record<string, unknown>> {
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(query?: Uniquery$1<Own<T>, Nav<T>>): Promise<Data<T>[]>;
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(query?: Uniquery$1<Own<T>, Nav<T>>, page?: number, size?: number): Promise<PageResult<Data<T>>>;
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(id: Id<T>, query?: {
75
+ one<Q extends {
76
+ controls?: UniqueryControls$1<Own<T>, Nav<T>>;
77
+ } = {
68
78
  controls?: UniqueryControls$1<Own<T>, Nav<T>>;
69
- }): Promise<Data<T> | null>;
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 `pk` (or `pks`) as a JSON body to the action's path
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
- * PK (URL-encoded; composite PKs are URL-encoded per field and joined
102
- * with `/`); for `level: 'rows'` or `'table'`, navigate to `value`
103
- * verbatim. The default navigator (browser only) calls
104
- * `window.location.assign(url)`. Provide `ClientOptions.navigate` to
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
- * For `level: 'rows'`, `pk` must be an array. If a non-array is supplied
112
- * for a `'rows'` action it is wrapped into a single-element array the
113
- * server-side `@DbActionPKs()` resolver requires an array body.
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, pk?: unknown): Promise<unknown>;
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).
@@ -159,31 +180,31 @@ declare class ClientError extends Error {
159
180
  /**
160
181
  * Wire-body shape for `ActionDisabledError` responses (HTTP 409). Extends
161
182
  * the base `ServerError` envelope with a `name` discriminator, the action
162
- * name, and the offending PK(s). The bridge between `@atscript/moost-db`'s
183
+ * name, and the offending identifier(s). The bridge between `@atscript/moost-db`'s
163
184
  * server-side error and this typed client-side subclass is the wire JSON
164
185
  * body — neither package depends on the other.
165
186
  */
166
187
  interface ActionDisabledErrorBody extends ServerError {
167
188
  name: "ActionDisabledError";
168
189
  action: string;
169
- pk?: unknown;
170
- pks?: unknown[];
190
+ id?: Record<string, unknown>;
191
+ ids?: Record<string, unknown>[];
171
192
  }
172
193
  /**
173
194
  * Typed marker thrown by `Client._send` when the server response body's
174
195
  * `name === 'ActionDisabledError'`. The transport / status / base body are
175
196
  * identical to a generic `ClientError`; this subclass adds typed accessors
176
197
  * so consumers can write `catch (e) { if (e instanceof ActionDisabledError) … }`
177
- * to access `action` / `pk` / `pks` without indexing into `body`.
198
+ * to access `action` / `id` / `ids` without indexing into `body`.
178
199
  */
179
200
  declare class ActionDisabledError extends ClientError {
180
201
  name: string;
181
202
  /** The `@DbAction` name that rejected the request. */
182
203
  get action(): string;
183
204
  /** Present only for `'row'`-level rejections. */
184
- get pk(): unknown;
185
- /** Present only for `'rows'`-level rejections (full list of failing PKs). */
186
- get pks(): unknown[] | undefined;
205
+ get id(): Record<string, unknown> | undefined;
206
+ /** Present only for `'rows'`-level rejections (full list of failing IDs). */
207
+ get ids(): Record<string, unknown>[] | undefined;
187
208
  }
188
209
  /** Thrown by `Client.action()` when the action name is not present in `/meta`. */
189
210
  declare class ActionNotFoundError extends Error {
@@ -204,4 +225,4 @@ declare class ActionUnsupportedError extends Error {
204
225
  constructor(action: string, processor: string, message: string);
205
226
  }
206
227
  //#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 };
228
+ 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 };
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` / `pk` / `pks` without indexing into `body`.
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 pk() {
34
- return this.body.pk;
33
+ get id() {
34
+ return this.body.id;
35
35
  }
36
- /** Present only for `'rows'`-level rejections (full list of failing PKs). */
37
- get pks() {
38
- return this.body.pks;
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 `pk` (or `pks`) as a JSON body to the action's path
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
- * PK (URL-encoded; composite PKs are URL-encoded per field and joined
193
- * with `/`); for `level: 'rows'` or `'table'`, navigate to `value`
194
- * verbatim. The default navigator (browser only) calls
195
- * `window.location.assign(url)`. Provide `ClientOptions.navigate` to
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
- * For `level: 'rows'`, `pk` must be an array. If a non-array is supplied
203
- * for a `'rows'` action it is wrapped into a single-element array the
204
- * server-side `@DbActionPKs()` resolver requires an array body.
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, pk) {
207
- const action = (await this.meta()).actions.find((a) => a.name === name);
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, pk);
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, pk);
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, pk) {
254
+ _buildActionBody(action, id) {
236
255
  if (action.level === "table") return void 0;
237
- if (action.level !== "rows") return pk;
238
- if (Array.isArray(pk)) return pk;
239
- return pk === void 0 ? [] : [pk];
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, pk) {
264
+ _interpolateNavigateUrl(action, id, preferredId) {
242
265
  if (action.level !== "row") return action.value;
243
- if (pk === void 0) return action.value;
244
- return action.value.replace(/\$1/g, encodeNavigatePk(pk));
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,18 @@ var Client = class {
325
349
  }
326
350
  };
327
351
  /**
328
- * Encode a row PK for substitution into a `processor: 'navigate'` URL template.
329
- * Scalars are URL-encoded directly; composite PK objects have each value
330
- * URL-encoded and joined with `/` in object-key order (which mirrors
331
- * `primaryKeys` for the table).
352
+ * Encode a row identifier for substitution into a `processor: 'navigate'` URL template.
353
+ * The values are URL-encoded and joined with `/` in `meta.preferredId` field
354
+ * declaration order NOT in object-key insertion order (which is unstable
355
+ * across callers).
332
356
  */
333
- function encodeNavigatePk(pk) {
334
- if (pk === null || pk === void 0) return "";
335
- return (typeof pk === "object" ? Object.values(pk) : [pk]).map((v) => encodeURIComponent(String(v))).join("/");
357
+ function encodeNavigateId(id, preferredId) {
358
+ return preferredId.map((f) => encodeURIComponent(String(id[f]))).join("/");
359
+ }
360
+ function describeShape(value) {
361
+ if (value === null) return "null";
362
+ if (Array.isArray(value)) return "array";
363
+ return typeof value;
336
364
  }
337
365
  //#endregion
338
366
  export { ActionDisabledError, ActionNotFoundError, ActionUnsupportedError, Client, ClientError };
@@ -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 { TDbUpdateResult$1 as C, UniqueryControls$1 as E, TDbInsertResult$1 as S, Uniquery$1 as T, RelationInfo as _, createClientValidator as a, TDbDeleteResult$1 as b, ClientOptions as c, FilterExpr as d, IdOf as f, PageResult as g, OwnOf as h, ValidatorMode$1 as i, DataOf as l, NavOf as m, ClientValidator as n, AggregateQuery$1 as o, MetaResponse as p, DbValidationContext as r, AggregateResult$1 as s, ClientValidationError as t, FieldMeta as u, SearchIndexInfo as v, TypedWithRelation as w, TDbInsertManyResult$1 as x, ServerError as y };
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 { TDbUpdateResult$1 as C, UniqueryControls$1 as E, TDbInsertResult$1 as S, Uniquery$1 as T, RelationInfo as _, createClientValidator as a, TDbDeleteResult$1 as b, ClientOptions as c, FilterExpr as d, IdOf as f, PageResult as g, OwnOf as h, ValidatorMode$1 as i, DataOf as l, NavOf as m, ClientValidator as n, AggregateQuery$1 as o, MetaResponse as p, DbValidationContext as r, AggregateResult$1 as s, ClientValidationError as t, FieldMeta as u, SearchIndexInfo as v, TypedWithRelation as w, TDbInsertManyResult$1 as x, ServerError as y };
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,2 +1,2 @@
1
- import { a as createClientValidator, i as ValidatorMode, n as ClientValidator, r as DbValidationContext, t as ClientValidationError } from "./validator-DNm9kCoq.cjs";
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 };
@@ -1,2 +1,2 @@
1
- import { a as createClientValidator, i as ValidatorMode, n as ClientValidator, r as DbValidationContext, t as ClientValidationError } from "./validator-DfNMCEKa.mjs";
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.58",
3
+ "version": "0.1.59",
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.58"
53
+ "@atscript/db": "0.1.59"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@atscript/db": "^0.1.44",