@haddowg/json-api-client 1.1.0 → 1.3.0
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/README.md +5 -1
- package/dist/ajv.d.cts +1 -1
- package/dist/ajv.d.mts +1 -1
- package/dist/index.cjs +128 -37
- package/dist/index.d.cts +31 -9
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +31 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +125 -38
- package/dist/index.mjs.map +1 -1
- package/dist/{validate-CfrCMZkA.d.cts → validate-CJPivkqT.d.cts} +382 -78
- package/dist/validate-CJPivkqT.d.cts.map +1 -0
- package/dist/{validate-CfrCMZkA.d.mts → validate-CJPivkqT.d.mts} +382 -78
- package/dist/validate-CJPivkqT.d.mts.map +1 -0
- package/package.json +1 -1
- package/dist/validate-CfrCMZkA.d.cts.map +0 -1
- package/dist/validate-CfrCMZkA.d.mts.map +0 -1
|
@@ -24,6 +24,21 @@ interface RelationMutations {
|
|
|
24
24
|
replace?: boolean;
|
|
25
25
|
set?: boolean;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The pivot fields one side of the wire carries on a member's `meta.pivot`: field name to
|
|
29
|
+
* wire-format hint (the vocabulary of {@link ResourceDescriptor.attributes}), the fields that
|
|
30
|
+
* are always present, and the fields that may be `null`.
|
|
31
|
+
*/
|
|
32
|
+
interface PivotDescriptor {
|
|
33
|
+
fields: Readonly<Record<string, string>>;
|
|
34
|
+
/**
|
|
35
|
+
* On a response, the fields every member renders; on a request, the fields every member must
|
|
36
|
+
* send. Absent means every field.
|
|
37
|
+
*/
|
|
38
|
+
required?: readonly string[];
|
|
39
|
+
/** The fields whose value may be `null`. Absent means none. */
|
|
40
|
+
nullable?: readonly string[];
|
|
41
|
+
}
|
|
27
42
|
interface RelationDescriptor {
|
|
28
43
|
cardinality: Cardinality;
|
|
29
44
|
/** Related JSON:API type(s); more than one = polymorphic. */
|
|
@@ -31,12 +46,30 @@ interface RelationDescriptor {
|
|
|
31
46
|
/** Whether members carry pivot data (`meta.pivot`). */
|
|
32
47
|
pivot?: boolean;
|
|
33
48
|
/**
|
|
34
|
-
* The pivot fields carried on each member's `meta.pivot
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
49
|
+
* The pivot fields carried on each member's `meta.pivot`, as a field-name → wire-format-hint
|
|
50
|
+
* map, typed as always present and never `null` on both reads and writes (D33).
|
|
51
|
+
*
|
|
52
|
+
* @deprecated Describe each side with {@link readPivot} and {@link writePivot}. Read only when
|
|
53
|
+
* those are absent.
|
|
38
54
|
*/
|
|
39
55
|
pivotFields?: Readonly<Record<string, string>>;
|
|
56
|
+
/** The `meta.pivot` a response renders on each member (`<Type><Rel>Pivot`). */
|
|
57
|
+
readPivot?: PivotDescriptor;
|
|
58
|
+
/**
|
|
59
|
+
* The `meta.pivot` a request may send on each member (`<Type><Rel>WritePivot`): a create,
|
|
60
|
+
* update or atomic body, or a relationship mutation.
|
|
61
|
+
*/
|
|
62
|
+
writePivot?: PivotDescriptor;
|
|
63
|
+
/**
|
|
64
|
+
* `true` for a relation create and update accept but no response renders. It stays settable
|
|
65
|
+
* on write inputs and is left out of the read view, `fields` and the relationship reads.
|
|
66
|
+
*/
|
|
67
|
+
writeOnly?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The relation's segment in its related and relationship paths, when it differs from the
|
|
70
|
+
* relation's name (`/articles/{id}/published-by` for `publisher`). Absent means the name.
|
|
71
|
+
*/
|
|
72
|
+
uriField?: string;
|
|
40
73
|
/**
|
|
41
74
|
* `false` when the related endpoint (`GET /{type}/{id}/{rel}`) is suppressed for this relation
|
|
42
75
|
* (the bundle's `withoutRelatedEndpoint()`, ADR 0027) — `.related()` is then a compile error /
|
|
@@ -64,7 +97,7 @@ interface RelationDescriptor {
|
|
|
64
97
|
*/
|
|
65
98
|
countable?: CountableDescriptor;
|
|
66
99
|
/**
|
|
67
|
-
* The paginator
|
|
100
|
+
* The paginator this relation's OWN related/relationship endpoints advertise
|
|
68
101
|
* (`GET /{type}/{id}/{rel}` / `.../relationships/{rel}`), when it DIVERGES from the related
|
|
69
102
|
* type's collection-level {@link ResourceDescriptor.paginator}. A related endpoint may paginate
|
|
70
103
|
* differently than the related type's own collection (e.g. a cursor-paginated related to-many
|
|
@@ -72,9 +105,37 @@ interface RelationDescriptor {
|
|
|
72
105
|
* related/embedded to-many array's `$page.kind` from this per-relation value first, falling back
|
|
73
106
|
* to the related type's collection paginator (D6a). Absent when the two agree (the common case).
|
|
74
107
|
*/
|
|
75
|
-
paginator?:
|
|
108
|
+
paginator?: PaginatorDescriptor;
|
|
109
|
+
}
|
|
110
|
+
/** A built-in pagination strategy. */
|
|
111
|
+
type PaginatorStrategy = 'page' | 'offset' | 'cursor' | 'fixed';
|
|
112
|
+
/** The strategy that answers a read, or `none` for an unpaginated endpoint. */
|
|
113
|
+
type PaginatorKind = PaginatorStrategy | 'none';
|
|
114
|
+
/**
|
|
115
|
+
* A role a page parameter fills. `page`: number, size; `offset`: offset, limit; `cursor`: after,
|
|
116
|
+
* before, size; `fixed`: number.
|
|
117
|
+
*/
|
|
118
|
+
type PaginatorRole = 'number' | 'size' | 'offset' | 'limit' | 'after' | 'before';
|
|
119
|
+
/** A strategy whose roles are filled by renamed wire keys, e.g. `{ number: 'p', size: 'size' }`. */
|
|
120
|
+
interface StrategyDescriptor {
|
|
121
|
+
strategy: PaginatorStrategy;
|
|
122
|
+
keys: Readonly<Partial<Record<PaginatorRole, string>>>;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A MultiPaginator menu: the client picks an arm by its `kind` (sent as `page[kind]`), and the
|
|
126
|
+
* server answers with `default` when the request names none. Arm keys are the `kind` values.
|
|
127
|
+
*/
|
|
128
|
+
interface MenuDescriptor {
|
|
129
|
+
strategy: 'multi';
|
|
130
|
+
default: string;
|
|
131
|
+
arms: Readonly<Record<string, PaginatorStrategy | StrategyDescriptor>>;
|
|
76
132
|
}
|
|
77
|
-
|
|
133
|
+
/**
|
|
134
|
+
* How an endpoint paginates. A bare kind uses the canonical wire keys (`page[number]`,
|
|
135
|
+
* `page[size]`, ...); a {@link StrategyDescriptor} names renamed keys; a {@link MenuDescriptor}
|
|
136
|
+
* offers several strategies.
|
|
137
|
+
*/
|
|
138
|
+
type PaginatorDescriptor = PaginatorKind | StrategyDescriptor | MenuDescriptor;
|
|
78
139
|
type ClientIdPolicy = 'forbidden' | 'optional' | 'required';
|
|
79
140
|
/** Where a custom action is invoked: collection-scoped (`/{type}/-actions/{name}`) or resource-scoped (`/{type}/{id}/-actions/{name}`). */
|
|
80
141
|
type ActionScope = 'collection' | 'resource';
|
|
@@ -145,7 +206,7 @@ interface ResourceDescriptor {
|
|
|
145
206
|
relations: Readonly<Record<string, RelationDescriptor>>;
|
|
146
207
|
/** Per-operation path templates, e.g. `{ fetchOne: '/albums/{id}' }`. */
|
|
147
208
|
paths: Readonly<Record<string, string>>;
|
|
148
|
-
paginator:
|
|
209
|
+
paginator: PaginatorDescriptor;
|
|
149
210
|
clientId: ClientIdPolicy;
|
|
150
211
|
/**
|
|
151
212
|
* The `withCount` count tokens + negotiation profile for this type's read endpoints.
|
|
@@ -187,6 +248,168 @@ interface AtomicDescriptor {
|
|
|
187
248
|
path: string;
|
|
188
249
|
}
|
|
189
250
|
//#endregion
|
|
251
|
+
//#region src/pagination.d.ts
|
|
252
|
+
/** The wire key for each role when the server keeps the default names. */
|
|
253
|
+
declare const CANONICAL_PAGE_KEYS: {
|
|
254
|
+
readonly page: {
|
|
255
|
+
readonly number: "number";
|
|
256
|
+
readonly size: "size";
|
|
257
|
+
};
|
|
258
|
+
readonly offset: {
|
|
259
|
+
readonly offset: "offset";
|
|
260
|
+
readonly limit: "limit";
|
|
261
|
+
};
|
|
262
|
+
readonly cursor: {
|
|
263
|
+
readonly after: "after";
|
|
264
|
+
readonly before: "before";
|
|
265
|
+
readonly size: "size";
|
|
266
|
+
};
|
|
267
|
+
readonly fixed: {
|
|
268
|
+
readonly number: "number";
|
|
269
|
+
};
|
|
270
|
+
readonly none: {};
|
|
271
|
+
};
|
|
272
|
+
/** Role -> wire key for the strategy that answered. */
|
|
273
|
+
type PageKeys = Readonly<Partial<Record<PaginatorRole, string>>>;
|
|
274
|
+
/** The strategy that answers one read, with its wire keys and, for a menu pick, the arm's `kind`. */
|
|
275
|
+
interface ResolvedPaginator {
|
|
276
|
+
kind: PaginatorKind;
|
|
277
|
+
keys: PageKeys;
|
|
278
|
+
/** The menu arm the request picked with `page[kind]`. Absent when it picked none (the default). */
|
|
279
|
+
arm?: string;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Resolve the strategy that answers a read from the endpoint's paginator and the `page` the
|
|
283
|
+
* request sends. A menu answers with the arm named by `page.kind`, else its `default`.
|
|
284
|
+
*/
|
|
285
|
+
declare function resolvePaginator(paginator: PaginatorDescriptor | undefined, page?: Readonly<Record<string, unknown>>): ResolvedPaginator;
|
|
286
|
+
/** Navigation links by relation (`first`/`prev`/`next`/`last`), when present. */
|
|
287
|
+
interface PageLinks {
|
|
288
|
+
first?: string;
|
|
289
|
+
prev?: string;
|
|
290
|
+
next?: string;
|
|
291
|
+
last?: string;
|
|
292
|
+
}
|
|
293
|
+
/** Normalised pagination state on a collection, as the runtime builds it; count-free-safe. */
|
|
294
|
+
interface Page {
|
|
295
|
+
kind: PaginatorKind;
|
|
296
|
+
/** Role -> wire key for the strategy that answered. */
|
|
297
|
+
keys: PageKeys;
|
|
298
|
+
/** The menu arm the request picked with `page[kind]`, when it picked one. */
|
|
299
|
+
arm?: string;
|
|
300
|
+
/** The raw `meta.page` block, when present (shape varies by kind). */
|
|
301
|
+
meta?: Record<string, unknown>;
|
|
302
|
+
links: PageLinks;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The `meta.page` block each built-in strategy renders. `total`/`lastPage` (and `to` on an empty
|
|
306
|
+
* page) are absent when the server does not count.
|
|
307
|
+
*/
|
|
308
|
+
type PageMeta<K extends PaginatorKind> = K extends 'page' ? {
|
|
309
|
+
currentPage: number;
|
|
310
|
+
perPage: number;
|
|
311
|
+
from: number;
|
|
312
|
+
to?: number;
|
|
313
|
+
total?: number;
|
|
314
|
+
lastPage?: number;
|
|
315
|
+
} : K extends 'fixed' ? {
|
|
316
|
+
currentPage: number;
|
|
317
|
+
total?: number;
|
|
318
|
+
lastPage?: number;
|
|
319
|
+
} : K extends 'offset' ? {
|
|
320
|
+
offset: number;
|
|
321
|
+
limit: number;
|
|
322
|
+
from: number;
|
|
323
|
+
to?: number;
|
|
324
|
+
total?: number;
|
|
325
|
+
} : K extends 'cursor' ? {
|
|
326
|
+
perPage: number;
|
|
327
|
+
from?: string;
|
|
328
|
+
to?: string;
|
|
329
|
+
hasMore: boolean;
|
|
330
|
+
} : Record<string, unknown>;
|
|
331
|
+
/** `$page` typed by the strategy `K` that answers, its wire keys and the picked menu arm. */
|
|
332
|
+
type TypedPage<K extends PaginatorKind, Keys extends PageKeys, Arm extends string | undefined = undefined> = {
|
|
333
|
+
kind: K;
|
|
334
|
+
keys: Keys;
|
|
335
|
+
meta?: PageMeta<K>;
|
|
336
|
+
links: PageLinks;
|
|
337
|
+
} & (Arm extends string ? {
|
|
338
|
+
arm: Arm;
|
|
339
|
+
} : {
|
|
340
|
+
arm?: never;
|
|
341
|
+
});
|
|
342
|
+
/** A non-menu paginator entry as `{ strategy, keys }` (a bare kind takes the canonical keys). */
|
|
343
|
+
type StrategyOf<P> = P extends PaginatorKind ? {
|
|
344
|
+
strategy: P;
|
|
345
|
+
keys: (typeof CANONICAL_PAGE_KEYS)[P];
|
|
346
|
+
} : P extends StrategyDescriptor ? P : never;
|
|
347
|
+
/** Cursors are opaque strings; every other role is a count. */
|
|
348
|
+
type RoleValue<R> = R extends 'after' | 'before' ? string : number;
|
|
349
|
+
/** The page members one strategy accepts, keyed by their wire names. */
|
|
350
|
+
type StrategyParams<S> = S extends {
|
|
351
|
+
keys: infer K;
|
|
352
|
+
} ? { -readonly [R in keyof K as K[R] extends string ? K[R] : never]?: RoleValue<R> } : never;
|
|
353
|
+
/** True when `P` is the loose `PaginatorDescriptor` union rather than a generated literal. */
|
|
354
|
+
type IsLoose<P> = [PaginatorKind] extends [P] ? true : false;
|
|
355
|
+
type ArmName<M extends MenuDescriptor> = Extract<keyof M['arms'], string>;
|
|
356
|
+
type ArmParams<M extends MenuDescriptor, A> = A extends ArmName<M> ? {
|
|
357
|
+
kind: A;
|
|
358
|
+
} & StrategyParams<StrategyOf<M['arms'][A]>> : never;
|
|
359
|
+
type KeysOfUnion<U> = U extends unknown ? keyof U : never;
|
|
360
|
+
/**
|
|
361
|
+
* The default arm's members without `kind`. Members only another arm reads are `never`: the
|
|
362
|
+
* server would select that arm by them, while the response is typed as the default.
|
|
363
|
+
*/
|
|
364
|
+
type DefaultArmParams<M extends MenuDescriptor> = StrategyParams<StrategyOf<M['arms'][M['default']]>> & {
|
|
365
|
+
kind?: undefined;
|
|
366
|
+
} & { [X in Exclude<KeysOfUnion<StrategyParams<StrategyOf<M['arms'][ArmName<M>]>>>, keyof StrategyParams<StrategyOf<M['arms'][M['default']]>>>]?: never };
|
|
367
|
+
/**
|
|
368
|
+
* The `page` parameter an endpoint with paginator `P` accepts: its strategy's members under
|
|
369
|
+
* their wire keys (numbers, cursors as strings). A menu takes one arm tagged by `kind`, or the
|
|
370
|
+
* default arm's members untagged. `never` for an unpaginated endpoint; a loose record when the
|
|
371
|
+
* descriptor is not a generated literal.
|
|
372
|
+
*/
|
|
373
|
+
type PageParams<P> = IsLoose<P> extends true ? Readonly<Record<string, unknown>> : P extends 'none' ? never : P extends MenuDescriptor ? ArmParams<P, ArmName<P>> | DefaultArmParams<P> : StrategyParams<StrategyOf<P>>;
|
|
374
|
+
/** The member of page parameter union `P` that argument `Q` selects by its `kind`, if any. */
|
|
375
|
+
type SelectedArm<Q, P> = Q extends {
|
|
376
|
+
kind: infer K;
|
|
377
|
+
} ? Extract<P, {
|
|
378
|
+
kind: K;
|
|
379
|
+
}> : Extract<P, {
|
|
380
|
+
kind?: undefined;
|
|
381
|
+
}>;
|
|
382
|
+
/**
|
|
383
|
+
* A page argument `Q` with any member outside the arm it selects turned into `never`, so a typo,
|
|
384
|
+
* or another arm's member, is an error.
|
|
385
|
+
*/
|
|
386
|
+
type ExactPageParams<Q, P> = Q & { [K in Exclude<keyof Q, KeysOfUnion<[SelectedArm<Q, P>] extends [never] ? P : SelectedArm<Q, P>>>]: never };
|
|
387
|
+
type StrategyPage<S, Arm extends string | undefined = undefined> = S extends {
|
|
388
|
+
strategy: infer K extends PaginatorStrategy;
|
|
389
|
+
keys: infer Keys extends PageKeys;
|
|
390
|
+
} ? TypedPage<K, Keys, Arm> : Page;
|
|
391
|
+
type MenuPage<M extends MenuDescriptor, Q> = [Q] extends [never] ? StrategyPage<StrategyOf<M['arms'][M['default']]>> : Q extends {
|
|
392
|
+
kind: infer A extends ArmName<M>;
|
|
393
|
+
} ? StrategyPage<StrategyOf<M['arms'][A]>, A> : StrategyPage<StrategyOf<M['arms'][M['default']]>>;
|
|
394
|
+
/**
|
|
395
|
+
* The `$page` a read on an endpoint with paginator `P` carries, given the `page` argument `Q`
|
|
396
|
+
* it sent: the picked menu arm's strategy, the default's when it picked none, else the
|
|
397
|
+
* endpoint's own strategy.
|
|
398
|
+
*/
|
|
399
|
+
type PageOf<P, Q = undefined> = IsLoose<P> extends true ? Page : P extends MenuDescriptor ? MenuPage<P, Q> : P extends 'none' ? TypedPage<'none', Record<never, never>> : StrategyPage<StrategyOf<P>>;
|
|
400
|
+
/**
|
|
401
|
+
* The `page` argument that reads another page from the strategy that answered `P` (a `$page`):
|
|
402
|
+
* its members under their wire keys, tagged with the menu arm it picked. A loose record for an
|
|
403
|
+
* untyped `Page`.
|
|
404
|
+
*/
|
|
405
|
+
type PageParamsOf<P extends Page> = PaginatorKind extends P['kind'] ? Readonly<Record<string, string | number>> : StrategyParams<{
|
|
406
|
+
keys: P['keys'];
|
|
407
|
+
}> & (P extends {
|
|
408
|
+
arm: infer A extends string;
|
|
409
|
+
} ? {
|
|
410
|
+
kind: A;
|
|
411
|
+
} : unknown);
|
|
412
|
+
//#endregion
|
|
190
413
|
//#region src/materialise.d.ts
|
|
191
414
|
/** A trimmed, by-reference top-level envelope shared by every resource from one response. */
|
|
192
415
|
interface DocumentEnvelope {
|
|
@@ -204,19 +427,6 @@ interface Edge {
|
|
|
204
427
|
links?: Record<string, unknown>;
|
|
205
428
|
meta?: Record<string, unknown>;
|
|
206
429
|
}
|
|
207
|
-
/** Normalised pagination, discriminated by paginator kind; count-free-safe. */
|
|
208
|
-
interface Page {
|
|
209
|
-
kind: PaginatorKind;
|
|
210
|
-
/** The raw `meta.page` block, when present (shape varies by kind). */
|
|
211
|
-
meta?: Record<string, unknown>;
|
|
212
|
-
/** Navigation links by relation (`first`/`prev`/`next`/`last`), when present. */
|
|
213
|
-
links: {
|
|
214
|
-
first?: string;
|
|
215
|
-
prev?: string;
|
|
216
|
-
next?: string;
|
|
217
|
-
last?: string;
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
430
|
/** The uniform shape `$rel(name)` returns. */
|
|
221
431
|
interface RelationView {
|
|
222
432
|
data?: unknown;
|
|
@@ -257,10 +467,11 @@ interface MaterialiseContext {
|
|
|
257
467
|
* Re-fetch + re-materialise a link (wired in Build 4); returns the materialised value.
|
|
258
468
|
* `linkage` propagates the originating surface so a paginated relationship-endpoint
|
|
259
469
|
* array re-materialises as identifiers (not resources) on `$next()`/`$prev()`. `paginator`
|
|
260
|
-
* carries the current page
|
|
261
|
-
* (a divergent per-relation paginator, or an EMPTY next page
|
|
470
|
+
* carries the strategy that answered the current page, so the re-materialised page reports the
|
|
471
|
+
* same `$page` (a divergent per-relation paginator, a picked menu arm, or an EMPTY next page
|
|
472
|
+
* that carries no member to sniff).
|
|
262
473
|
*/
|
|
263
|
-
navigate: (url: string, linkage?: boolean, paginator?:
|
|
474
|
+
navigate: (url: string, linkage?: boolean, paginator?: ResolvedPaginator, signal?: AbortSignal) => Promise<unknown>;
|
|
264
475
|
/**
|
|
265
476
|
* The opt-in per-resource validator (ADR 0004), resolved from `ClientOptions.validate`. When
|
|
266
477
|
* present, every wire resource in `data`/`included` is validated against its per-type schema
|
|
@@ -301,14 +512,15 @@ interface RawRelationship {
|
|
|
301
512
|
* full resource) — a structural guess can't tell them apart for an attribute-less,
|
|
302
513
|
* relation-less resource, so the caller passes the surface it issued.
|
|
303
514
|
*
|
|
304
|
-
* `paginatorOverride` sets the top-level array's `$page
|
|
305
|
-
*
|
|
306
|
-
*
|
|
515
|
+
* `paginatorOverride` sets the top-level array's `$page` for a related/relationship read, where the
|
|
516
|
+
* paginator is the RELATION's (which may diverge from the related type's collection — D6a), for a
|
|
517
|
+
* menu read that picked an arm, and where an empty page carries no member to sniff a type from. A
|
|
518
|
+
* bare kind is read with its canonical wire keys. `primaryType` is preferred for a
|
|
307
519
|
* top-level collection read (statically-known); both override the sniff-from-`data[0]` fallback.
|
|
308
520
|
*/
|
|
309
|
-
declare function materialise(doc: Document, ctx: MaterialiseContext, linkage?: boolean, primaryType?: string, paginatorOverride?: PaginatorKind): unknown;
|
|
521
|
+
declare function materialise(doc: Document, ctx: MaterialiseContext, linkage?: boolean, primaryType?: string, paginatorOverride?: ResolvedPaginator | PaginatorKind): unknown;
|
|
310
522
|
/**
|
|
311
|
-
* A to-many relation's paginator
|
|
523
|
+
* A to-many relation's paginator. The relation's OWN related/relationship endpoint may
|
|
312
524
|
* paginate differently than the related type's top-level collection (D6a), so prefer the
|
|
313
525
|
* per-relation `paginator` (carried in the descriptor only when it diverges); otherwise fall back
|
|
314
526
|
* to the related type's own paginator (the relationship and related endpoints both paginate that
|
|
@@ -316,6 +528,8 @@ declare function materialise(doc: Document, ctx: MaterialiseContext, linkage?: b
|
|
|
316
528
|
* resolves off its first declared related type (its members span types, matching the relationship
|
|
317
529
|
* endpoint's first-member resolution).
|
|
318
530
|
*/
|
|
531
|
+
declare function relatedPaginator(relation: RelationDescriptor | undefined, ctx: MaterialiseContext): PaginatorDescriptor | undefined;
|
|
532
|
+
/** The kind a relation's related read answers with when it sends no `page`. See {@link relatedPaginator}. */
|
|
319
533
|
declare function relatedPaginatorKind(relation: RelationDescriptor | undefined, ctx: MaterialiseContext): PaginatorKind;
|
|
320
534
|
//#endregion
|
|
321
535
|
//#region src/types.d.ts
|
|
@@ -489,11 +703,13 @@ declare function runAtomic<D extends ApiDescriptor, A, W, const Ops>(request: Js
|
|
|
489
703
|
* a real, read-only `T[]` carrying the non-enumerable relationship-level envelope
|
|
490
704
|
* (`$page`/`$links`/`$meta`) plus link-driven `$next()`/`$prev()` navigation. The same
|
|
491
705
|
* model backs top-level collections, related-endpoint collections, and hydrated/linkage
|
|
492
|
-
* to-many relationship values ("one model, three surfaces").
|
|
706
|
+
* to-many relationship values ("one model, three surfaces"). `P` types `$page` by the strategy
|
|
707
|
+
* that answered (see {@link PageOf}); following a link keeps it.
|
|
493
708
|
*/
|
|
494
|
-
type Collection<T> = ReadonlyArray<T> & Omit<ArrayEnvelope, '$next' | '$prev'> & {
|
|
495
|
-
$
|
|
496
|
-
$
|
|
709
|
+
type Collection<T, P = Page> = ReadonlyArray<T> & Omit<ArrayEnvelope, '$page' | '$next' | '$prev'> & {
|
|
710
|
+
readonly $page: P;
|
|
711
|
+
$next(options?: NavigateOptions): Promise<Collection<T, P> | undefined>;
|
|
712
|
+
$prev(options?: NavigateOptions): Promise<Collection<T, P> | undefined>;
|
|
497
713
|
};
|
|
498
714
|
/**
|
|
499
715
|
* The edge-local accessors carried by a materialised related VALUE (a per-edge view):
|
|
@@ -518,6 +734,12 @@ interface ResourceAccessors {
|
|
|
518
734
|
type TypeName<D extends ApiDescriptor> = Extract<keyof D, string>;
|
|
519
735
|
/** The relation names declared on type `T` of descriptor `D`. */
|
|
520
736
|
type RelationName<D extends ApiDescriptor, T extends TypeName<D>> = Extract<keyof D[T]['relations'], string>;
|
|
737
|
+
/** The relations of type `T` a response never renders (the descriptor's `writeOnly: true`). */
|
|
738
|
+
type WriteOnlyRelationName<D extends ApiDescriptor, T extends TypeName<D>> = { [R in RelationName<D, T>]: D[T]['relations'][R] extends {
|
|
739
|
+
writeOnly: true;
|
|
740
|
+
} ? R : never }[RelationName<D, T>];
|
|
741
|
+
/** The relations of type `T` a response can render: every relation but the write-only ones. */
|
|
742
|
+
type ReadableRelationName<D extends ApiDescriptor, T extends TypeName<D>> = Exclude<RelationName<D, T>, WriteOnlyRelationName<D, T>>;
|
|
521
743
|
/** The attribute object for type `T`, from the generated attributes map `A` (falls back to `{}`). */
|
|
522
744
|
type AttributesOf<A, T> = T extends keyof A ? A[T] : Record<string, never>;
|
|
523
745
|
/**
|
|
@@ -553,6 +775,15 @@ type PickSelected<Obj, Sel> = [Sel] extends [AllFields] ? Obj : Pick<Obj, Extrac
|
|
|
553
775
|
type RelatedType<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = Extract<D[T]['relations'][R]['types'][number], TypeName<D>>;
|
|
554
776
|
/** True when relation `R` of type `T` is to-many. */
|
|
555
777
|
type IsMany<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = D[T]['relations'][R]['cardinality'] extends 'many' ? true : false;
|
|
778
|
+
/**
|
|
779
|
+
* The paginator relation `R`'s related/relationship endpoints use: the relation's own when it
|
|
780
|
+
* diverges (D6a), else its first related type's collection paginator (as the runtime resolves).
|
|
781
|
+
*/
|
|
782
|
+
type RelationPaginator<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = D[T]['relations'][R] extends {
|
|
783
|
+
paginator: infer P;
|
|
784
|
+
} ? P : D[T]['relations'][R]['types'][0] extends infer First extends TypeName<D> ? D[First]['paginator'] : 'none';
|
|
785
|
+
/** The `$page` of relation `R`'s to-many value read with page argument `Q` (none = the default). */
|
|
786
|
+
type RelationPage<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>, Q = undefined> = PageOf<RelationPaginator<D, T, R>, Q>;
|
|
556
787
|
/**
|
|
557
788
|
* The bare resource-identifier shape a non-included (but linked) relation takes,
|
|
558
789
|
* carrying the per-edge `$edge`/`$pivot` accessors the runtime attaches to every
|
|
@@ -584,13 +815,13 @@ type Identifier<TType extends string, Pivot = Record<string, unknown>> = TType e
|
|
|
584
815
|
* (Excluded to-many stays a `Collection`: linkage may still be present, and a lazy
|
|
585
816
|
* to-many materialises as an augmented array regardless — only its members differ.)
|
|
586
817
|
*/
|
|
587
|
-
type RelationValue<D extends ApiDescriptor, A, T extends TypeName<D>, R extends RelationName<D, T>, Inc, F = unknown> = R extends Inc ? IsMany<D, T, R> extends true ? Collection<Hydrated<D, A, RelatedType<D, T, R>, F>> : Hydrated<D, A, RelatedType<D, T, R>, F> | null : IsMany<D, T, R> extends true ? Collection<Identifier<RelatedType<D, T, R
|
|
818
|
+
type RelationValue<D extends ApiDescriptor, A, T extends TypeName<D>, R extends RelationName<D, T>, Inc, F = unknown> = R extends Inc ? IsMany<D, T, R> extends true ? Collection<Hydrated<D, A, RelatedType<D, T, R>, F>, RelationPage<D, T, R>> : Hydrated<D, A, RelatedType<D, T, R>, F> | null : IsMany<D, T, R> extends true ? Collection<Identifier<RelatedType<D, T, R>>, RelationPage<D, T, R>> : Identifier<RelatedType<D, T, R>> | null | undefined;
|
|
588
819
|
/**
|
|
589
820
|
* The relation slots of type `T`, each typed by cardinality and include-presence, then
|
|
590
821
|
* narrowed to the fieldset selection for `T` (relations are sparse-fieldset members too — an
|
|
591
|
-
* unrequested relation is statically absent).
|
|
822
|
+
* unrequested relation is statically absent). A write-only relation has no slot.
|
|
592
823
|
*/
|
|
593
|
-
type Relations<D extends ApiDescriptor, A, T extends TypeName<D>, Inc, F = unknown> = PickSelected<{ [R in
|
|
824
|
+
type Relations<D extends ApiDescriptor, A, T extends TypeName<D>, Inc, F = unknown> = PickSelected<{ [R in ReadableRelationName<D, T>]: RelationValue<D, A, T, R, Inc, F> }, SelectedFields<F, T>>;
|
|
594
825
|
/**
|
|
595
826
|
* A materialised resource object: flattened `type` + `id` + attributes + relation slots
|
|
596
827
|
* as own enumerable props, intersected with the resource-level `$`-accessors. `Inc` is
|
|
@@ -665,18 +896,20 @@ type FilterQuery<D extends ApiDescriptor, T extends TypeName<D>> = [FilterKey<D,
|
|
|
665
896
|
type FieldsMap<D extends ApiDescriptor> = { [T in TypeName<D>]?: readonly MemberName<D, T>[] };
|
|
666
897
|
/**
|
|
667
898
|
* A selectable sparse-fieldset member of type `T`: an attribute name (the descriptor's
|
|
668
|
-
* `attributes` keys) or a relation
|
|
669
|
-
* `fields` entries are constrained to real members.
|
|
899
|
+
* `attributes` keys) or a relation a response renders. The descriptor carries the literal member
|
|
900
|
+
* names, so `fields` entries are constrained to real members.
|
|
670
901
|
*/
|
|
671
|
-
type MemberName<D extends ApiDescriptor, T extends TypeName<D>> = Extract<keyof D[T]['attributes'], string> |
|
|
902
|
+
type MemberName<D extends ApiDescriptor, T extends TypeName<D>> = Extract<keyof D[T]['attributes'], string> | ReadableRelationName<D, T>;
|
|
903
|
+
/** The `page` argument a collection read of type `T` accepts (see {@link PageParams}). */
|
|
904
|
+
type ListPageParams<D extends ApiDescriptor, T extends TypeName<D>> = PageParams<D[T]['paginator']>;
|
|
672
905
|
/**
|
|
673
906
|
* A read query carrying a typed, narrowing `include`, a typed `fields` (drives sparse-fieldset
|
|
674
|
-
* return narrowing), a `withCount` constrained to the type's count tokens,
|
|
675
|
-
* JSON:API query families. `Inc`/`F` are inferred (as `const`) at the call site. Used for the
|
|
907
|
+
* return narrowing), a `withCount` constrained to the type's count tokens, a `page` typed by the
|
|
908
|
+
* collection's paginator (`Pg` drives the `$page` type), plus the loose JSON:API query families. `Inc`/`F` are inferred (as `const`) at the call site. Used for the
|
|
676
909
|
* COLLECTION read (`list`); the single-resource read (`get`) uses {@link SingleReadQuery}, which
|
|
677
910
|
* drops `withCount` (no `GET /{type}/{id}` endpoint advertises it).
|
|
678
911
|
*/
|
|
679
|
-
interface TypedReadQuery<D extends ApiDescriptor, T extends TypeName<D>, Inc extends readonly IncludePath<D, T>[], F extends FieldsMap<D> = FieldsMap<D
|
|
912
|
+
interface TypedReadQuery<D extends ApiDescriptor, T extends TypeName<D>, Inc extends readonly IncludePath<D, T>[], F extends FieldsMap<D> = FieldsMap<D>, Pg extends ListPageParams<D, T> | undefined = ListPageParams<D, T> | undefined> {
|
|
680
913
|
/** The relations to include — drives static narrowing; constrained to the advertised paths. */
|
|
681
914
|
include?: Inc;
|
|
682
915
|
/** Filter the collection — keys constrained to the type's advertised `filter[...]` params. */
|
|
@@ -687,7 +920,11 @@ interface TypedReadQuery<D extends ApiDescriptor, T extends TypeName<D>, Inc ext
|
|
|
687
920
|
fields?: F;
|
|
688
921
|
/** Relationship-count tokens (the Countable profile) — comma-joined onto `withCount`. */
|
|
689
922
|
withCount?: readonly CountToken<D, T>[];
|
|
690
|
-
|
|
923
|
+
/**
|
|
924
|
+
* The page to read, under the paginator's wire keys. On a menu, `kind` picks a strategy;
|
|
925
|
+
* without it the server answers with its default.
|
|
926
|
+
*/
|
|
927
|
+
page?: ExactPageParams<Pg, ListPageParams<D, T>>;
|
|
691
928
|
/** Cancels the read when signalled (e.g. a superseded request). Not sent to the server. */
|
|
692
929
|
signal?: AbortSignal;
|
|
693
930
|
}
|
|
@@ -711,13 +948,16 @@ interface SingleReadQuery<D extends ApiDescriptor, T extends TypeName<D>, Inc ex
|
|
|
711
948
|
* narrowed to the requested sparse fieldset.
|
|
712
949
|
*/
|
|
713
950
|
type ReadResult<D extends ApiDescriptor, A, T extends TypeName<D>, Inc extends readonly IncludePath<D, T>[] = [], F = unknown> = ResourceObjectView<D, A, T, IncludedRelations<D, T, Inc>, F>;
|
|
714
|
-
/**
|
|
715
|
-
|
|
951
|
+
/**
|
|
952
|
+
* A read query for the relationship/related endpoints: loose filter/sort/include/fields/withCount,
|
|
953
|
+
* and a `page` of type `Pg` (typed from the relation's paginator on a typed accessor).
|
|
954
|
+
*/
|
|
955
|
+
interface RelationReadQuery<Pg = Readonly<Record<string, unknown>>> {
|
|
716
956
|
filter?: Record<string, unknown>;
|
|
717
957
|
sort?: string | readonly string[];
|
|
718
958
|
include?: readonly string[];
|
|
719
959
|
fields?: Record<string, readonly string[]>;
|
|
720
|
-
page?:
|
|
960
|
+
page?: Pg;
|
|
721
961
|
/**
|
|
722
962
|
* Relationship-count tokens (`_self_` + the relation's countable relations) — the related/
|
|
723
963
|
* relationship endpoints advertise their own `withCount`, and the client negotiates the
|
|
@@ -729,27 +969,64 @@ interface RelationReadQuery {
|
|
|
729
969
|
}
|
|
730
970
|
/** The TS type a descriptor wire-format hint maps to (mirrors the runtime coercion vocabulary). */
|
|
731
971
|
type FormatToTs<H> = H extends 'number' | 'integer' ? number : H extends 'boolean' ? boolean : H extends 'object' ? Record<string, unknown> : H extends 'array' ? readonly unknown[] : H extends 'unknown' ? unknown : string;
|
|
732
|
-
/**
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
972
|
+
/** The string members of a descriptor list, or `never` when it is absent. */
|
|
973
|
+
type ListMember<L> = L extends readonly (infer M)[] ? M & string : never;
|
|
974
|
+
/** The fields of a {@link PivotDescriptor} every member carries (all of them when `required` is absent). */
|
|
975
|
+
type RequiredPivotField<P> = P extends {
|
|
976
|
+
fields: infer Fl;
|
|
977
|
+
} ? P extends {
|
|
978
|
+
required: infer Req;
|
|
979
|
+
} ? Extract<keyof Fl, ListMember<Req>> : keyof Fl : never;
|
|
980
|
+
/** One pivot field's TS type: its format hint's type, plus `null` when the field is nullable. */
|
|
981
|
+
type PivotValue<P, K> = P extends {
|
|
982
|
+
fields: infer Fl;
|
|
983
|
+
} ? K extends keyof Fl ? FormatToTs<Fl[K]> | (K extends ListMember<P extends {
|
|
984
|
+
nullable: infer N;
|
|
985
|
+
} ? N : never> ? null : never) : never : never;
|
|
986
|
+
/** The fields of a {@link PivotDescriptor}. */
|
|
987
|
+
type PivotFieldNames<P> = P extends {
|
|
988
|
+
fields: infer Fl;
|
|
989
|
+
} ? keyof Fl : never;
|
|
990
|
+
/** The `meta.pivot` object a {@link PivotDescriptor} describes. */
|
|
991
|
+
type PivotObject<P> = { readonly [K in PivotFieldNames<P> as K extends RequiredPivotField<P> ? K : never]: PivotValue<P, K> } & { readonly [K in PivotFieldNames<P> as K extends RequiredPivotField<P> ? never : K]?: PivotValue<P, K> };
|
|
992
|
+
/** The legacy hint-only pivot shape: every field present and non-null. */
|
|
993
|
+
type LegacyPivot<P> = { readonly [K in keyof P]: FormatToTs<P[K]> };
|
|
994
|
+
/**
|
|
995
|
+
* The typed `meta.pivot` a response renders on each member of relation `R`, from the descriptor's
|
|
996
|
+
* `readPivot`, e.g. `{ position: number; weight: number | null; addedAt: string }`. A descriptor
|
|
997
|
+
* with only `pivotFields` types every field present and non-null; one with neither falls back to
|
|
998
|
+
* a loose record (D33).
|
|
736
999
|
*/
|
|
737
1000
|
type PivotFieldsOf<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = D[T]['relations'][R] extends {
|
|
1001
|
+
readPivot: infer P;
|
|
1002
|
+
} ? PivotObject<P> : D[T]['relations'][R] extends {
|
|
738
1003
|
pivotFields: infer P;
|
|
739
|
-
} ?
|
|
1004
|
+
} ? LegacyPivot<P> : Record<string, unknown>;
|
|
1005
|
+
/**
|
|
1006
|
+
* The typed `meta.pivot` a request may send on each member of relation `R`, from the descriptor's
|
|
1007
|
+
* `writePivot` (only writable fields, optional unless required). Falls back like
|
|
1008
|
+
* {@link PivotFieldsOf} when the descriptor has no `writePivot`.
|
|
1009
|
+
*/
|
|
1010
|
+
type WritePivotOf<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = D[T]['relations'][R] extends {
|
|
1011
|
+
writePivot: infer P;
|
|
1012
|
+
} ? PivotObject<P> : D[T]['relations'][R] extends {
|
|
1013
|
+
pivotFields: infer P;
|
|
1014
|
+
} ? LegacyPivot<P> : PivotInput;
|
|
740
1015
|
/**
|
|
741
1016
|
* A hydrated related VALUE for relation `R` of type `T`, resolved by cardinality over the
|
|
742
1017
|
* (possibly polymorphic) related type(s): a `Collection` of hydrated members for to-many,
|
|
743
1018
|
* the hydrated member (or `null`) for to-one. Each member's `$pivot` is typed from the
|
|
744
|
-
* relation's `
|
|
1019
|
+
* relation's `readPivot` (D33).
|
|
745
1020
|
*/
|
|
746
|
-
type RelatedValue<D extends ApiDescriptor, A, T extends TypeName<D>, R extends RelationName<D, T
|
|
1021
|
+
type RelatedValue<D extends ApiDescriptor, A, T extends TypeName<D>, R extends RelationName<D, T>, Q = undefined> = IsMany<D, T, R> extends true ? Collection<Hydrated<D, A, RelatedType<D, T, R>, unknown, PivotFieldsOf<D, T, R>>, RelationPage<D, T, R, Q>> : Hydrated<D, A, RelatedType<D, T, R>, unknown, PivotFieldsOf<D, T, R>> | null;
|
|
747
1022
|
/**
|
|
748
1023
|
* A linkage VALUE for relation `R` (the `/relationships/{rel}` endpoint, identifiers only):
|
|
749
1024
|
* a `Collection` of identifier members for to-many, a single identifier (or `null`) for to-one.
|
|
750
|
-
* Each member's `$pivot` is typed from the relation's `
|
|
1025
|
+
* Each member's `$pivot` is typed from the relation's `readPivot` (D33).
|
|
751
1026
|
*/
|
|
752
|
-
type LinkageValue<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T
|
|
1027
|
+
type LinkageValue<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>, Q = undefined> = IsMany<D, T, R> extends true ? Collection<Identifier<RelatedType<D, T, R>, PivotFieldsOf<D, T, R>>, RelationPage<D, T, R, Q>> : Identifier<RelatedType<D, T, R>, PivotFieldsOf<D, T, R>> | null;
|
|
1028
|
+
/** The `page` argument relation `R`'s to-many reads accept; `never` for a to-one. */
|
|
1029
|
+
type RelationPageParams<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = IsMany<D, T, R> extends true ? PageParams<RelationPaginator<D, T, R>> : never;
|
|
753
1030
|
/**
|
|
754
1031
|
* A relationship accessor off a {@link ResourceHandle} (`client.albums.id('1').tracks`):
|
|
755
1032
|
* reads — `.get()` (linkage) / `.related()` (related collection); writes — to-many
|
|
@@ -763,27 +1040,30 @@ type LinkageValue<D extends ApiDescriptor, T extends TypeName<D>, R extends Rela
|
|
|
763
1040
|
* linkage, or `void` when the server replies `204`.
|
|
764
1041
|
*/
|
|
765
1042
|
interface RelationshipAccessor<D extends ApiDescriptor, A, T extends TypeName<D>, R extends RelationName<D, T>> {
|
|
766
|
-
get:
|
|
767
|
-
related: ExposesRelated<D, T, R> extends true ? (query?: RelationReadQuery) => Promise<RelatedValue<D, A, T, R>> : never;
|
|
1043
|
+
get: ReadsRelationship<D, T, R> extends true ? <const Pg extends RelationPageParams<D, T, R> | undefined = undefined>(query?: RelationReadQuery<ExactPageParams<Pg, RelationPageParams<D, T, R>>>) => Promise<LinkageValue<D, T, R, Pg>> : never;
|
|
1044
|
+
related: ExposesRelated<D, T, R> extends true ? <const Pg extends RelationPageParams<D, T, R> | undefined = undefined>(query?: RelationReadQuery<ExactPageParams<Pg, RelationPageParams<D, T, R>>>) => Promise<RelatedValue<D, A, T, R, Pg>> : never;
|
|
768
1045
|
add: RelationMutation<D, T, R, 'add', readonly WriteRef<D, T, R>[]>;
|
|
769
1046
|
remove: RelationMutation<D, T, R, 'remove', readonly LinkageRefOf<RelatedType<D, T, R>>[]>;
|
|
770
1047
|
replace: RelationMutation<D, T, R, 'replace', readonly WriteRef<D, T, R>[]>;
|
|
771
1048
|
set: RelationMutation<D, T, R, 'set', LinkageRefOf<RelatedType<D, T, R>> | null>;
|
|
772
1049
|
}
|
|
773
1050
|
/**
|
|
774
|
-
* A linkage ref for a to-many membership WRITE (`add`/`replace
|
|
775
|
-
* typed from the relation's `
|
|
776
|
-
* shape (`{ position, weight
|
|
1051
|
+
* A linkage ref for a to-many membership WRITE on relation `R` (`add`/`replace`, or a create or
|
|
1052
|
+
* update input), with `$pivot` typed from the relation's `writePivot` — so a `belongsToMany` add
|
|
1053
|
+
* carries the precise pivot shape (`{ position, weight?, … }`) instead of a loose record (D33).
|
|
777
1054
|
*/
|
|
778
1055
|
type WriteRef<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = LinkageRefOf<RelatedType<D, T, R>> & {
|
|
779
|
-
$pivot?:
|
|
1056
|
+
$pivot?: WritePivotOf<D, T, R>;
|
|
780
1057
|
};
|
|
781
1058
|
/**
|
|
782
1059
|
* True unless relation `R`'s related endpoint is suppressed (`related: false`, the bundle's
|
|
783
|
-
* `withoutRelatedEndpoint()`). Absent flag ⇒ exposed (the default /
|
|
1060
|
+
* `withoutRelatedEndpoint()`) or the relation is write-only. Absent flag ⇒ exposed (the default /
|
|
1061
|
+
* a hand-written descriptor).
|
|
784
1062
|
*/
|
|
785
1063
|
type ExposesRelated<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = D[T]['relations'][R] extends {
|
|
786
1064
|
related: false;
|
|
1065
|
+
} | {
|
|
1066
|
+
writeOnly: true;
|
|
787
1067
|
} ? false : true;
|
|
788
1068
|
/**
|
|
789
1069
|
* True unless relation `R`'s relationship endpoint is suppressed (`relationship: false`, the
|
|
@@ -792,6 +1072,10 @@ type ExposesRelated<D extends ApiDescriptor, T extends TypeName<D>, R extends Re
|
|
|
792
1072
|
type ExposesRelationship<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = D[T]['relations'][R] extends {
|
|
793
1073
|
relationship: false;
|
|
794
1074
|
} ? false : true;
|
|
1075
|
+
/** Whether relation `R`'s linkage can be read back: exposed, and not write-only. */
|
|
1076
|
+
type ReadsRelationship<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = D[T]['relations'][R] extends {
|
|
1077
|
+
writeOnly: true;
|
|
1078
|
+
} ? false : ExposesRelationship<D, T, R>;
|
|
795
1079
|
/** The mutation verb a relationship accessor method maps to (`add`/`remove`/`replace` to-many, `set` to-one). */
|
|
796
1080
|
type MutationVerb = 'add' | 'remove' | 'replace' | 'set';
|
|
797
1081
|
/** The cardinality a mutation verb belongs to (`set` is to-one; the rest are to-many). */
|
|
@@ -818,9 +1102,9 @@ type RelationMutation<D extends ApiDescriptor, T extends TypeName<D>, R extends
|
|
|
818
1102
|
/** The relationship accessors of type `T`, keyed by relation name. */
|
|
819
1103
|
type RelationshipAccessors<D extends ApiDescriptor, A, T extends TypeName<D>> = { [R in RelationName<D, T>]: RelationshipAccessor<D, A, T, R> };
|
|
820
1104
|
/**
|
|
821
|
-
* Writable pivot data carried on a to-many member as `$pivot` (a belongsToMany relation
|
|
822
|
-
*
|
|
823
|
-
*
|
|
1105
|
+
* Writable pivot data carried on a to-many member as `$pivot` (a belongsToMany relation with
|
|
1106
|
+
* `pivot: true`), passed through as `meta.pivot`. The loose fallback for a relation whose
|
|
1107
|
+
* descriptor has no `writePivot`.
|
|
824
1108
|
*/
|
|
825
1109
|
type PivotInput = Record<string, unknown>;
|
|
826
1110
|
/**
|
|
@@ -841,10 +1125,14 @@ type LinkageRef<TType extends string> = (ResourceIdentifier<TType> & {
|
|
|
841
1125
|
type LinkageRefOf<TType extends string> = TType extends string ? LinkageRef<TType> : never;
|
|
842
1126
|
/**
|
|
843
1127
|
* The relationship VALUE accepted in a create/update input slot, by cardinality:
|
|
844
|
-
* to-one accepts a single ref or `null` (clear); to-many accepts an array of refs
|
|
1128
|
+
* to-one accepts a single ref or `null` (clear); to-many accepts an array of refs, each with an
|
|
1129
|
+
* optional `$pivot` typed from the relation's `writePivot`.
|
|
1130
|
+
*/
|
|
1131
|
+
type RelationInput<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = IsMany<D, T, R> extends true ? readonly WriteRef<D, T, R>[] : LinkageRefOf<RelatedType<D, T, R>> | null;
|
|
1132
|
+
/**
|
|
1133
|
+
* The relationship slots of type `T` as optional write input keys (one per declared relation,
|
|
1134
|
+
* write-only ones included).
|
|
845
1135
|
*/
|
|
846
|
-
type RelationInput<D extends ApiDescriptor, T extends TypeName<D>, R extends RelationName<D, T>> = IsMany<D, T, R> extends true ? readonly LinkageRefOf<RelatedType<D, T, R>>[] : LinkageRefOf<RelatedType<D, T, R>> | null;
|
|
847
|
-
/** The relationship slots of type `T` as optional write input keys (one per declared relation). */
|
|
848
1136
|
type RelationInputs<D extends ApiDescriptor, T extends TypeName<D>> = { [R in RelationName<D, T>]?: RelationInput<D, T, R> };
|
|
849
1137
|
/** The `{ create; update }` write-attribute pair for type `T` (or an open pair when `T` is absent from `W`). */
|
|
850
1138
|
type WritePair<W, T> = T extends keyof W ? W[T] : {
|
|
@@ -993,7 +1281,7 @@ type ResourceHandle<D extends ApiDescriptor, A, W, T extends TypeName<D>, Act =
|
|
|
993
1281
|
} & RelationshipAccessors<D, A, T>;
|
|
994
1282
|
/** The collection-scoped accessor for one wire type (`client.albums`). */
|
|
995
1283
|
interface TypeAccessor<D extends ApiDescriptor, A, W, T extends TypeName<D>, Act = DefaultActionTypes> {
|
|
996
|
-
list<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(query?: TypedReadQuery<D, T, Inc, F>): Promise<Collection<ReadResult<D, A, T, Inc, F>>>;
|
|
1284
|
+
list<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}, const Pg extends ListPageParams<D, T> | undefined = undefined>(query?: TypedReadQuery<D, T, Inc, F, Pg>): Promise<Collection<ReadResult<D, A, T, Inc, F>, PageOf<D[T]['paginator'], Pg>>>;
|
|
997
1285
|
get<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(id: string, query?: SingleReadQuery<D, T, Inc, F>): Promise<ReadResult<D, A, T, Inc, F>>;
|
|
998
1286
|
create<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(input: CreateInput<D, W, T>, opts?: WriteOptions<D, T, Inc, F>): Promise<ReadResult<D, A, T, Inc, F>>;
|
|
999
1287
|
id(id: string): ResourceHandle<D, A, W, T, Act>;
|
|
@@ -1004,9 +1292,10 @@ interface TypeAccessor<D extends ApiDescriptor, A, W, T extends TypeName<D>, Act
|
|
|
1004
1292
|
* The descriptor-driven client surface: one {@link TypeAccessor} per wire type. `W` is the
|
|
1005
1293
|
* generated `WriteAttributes` map (per-type `{ create; update }` attribute pairs) and `Act`
|
|
1006
1294
|
* the generated per-action body-type map; both thread through to the write/action surfaces and
|
|
1007
|
-
* default so read-only / codegen-less callers compile.
|
|
1295
|
+
* default so read-only / codegen-less callers compile. `E` is the server's documented
|
|
1296
|
+
* error-code union, carried at the type level only (read it back with `ErrorCodeOf`).
|
|
1008
1297
|
*/
|
|
1009
|
-
type Client<D extends ApiDescriptor, A = DefaultAttributes<D>, W = DefaultWriteAttributes<D>, Act = DefaultActionTypes> = { [T in TypeName<D>]: TypeAccessor<D, A, W, T, Act> } & {
|
|
1298
|
+
type Client<D extends ApiDescriptor, A = DefaultAttributes<D>, W = DefaultWriteAttributes<D>, Act = DefaultActionTypes, E extends string = string> = { [T in TypeName<D>]: TypeAccessor<D, A, W, T, Act> } & {
|
|
1010
1299
|
/**
|
|
1011
1300
|
* Run an Atomic Operations batch (CONTEXT.md "Atomic — typed transaction builder"): the
|
|
1012
1301
|
* callback records `create`/`update`/`delete` ops on a non-type-scoped recorder (each carries
|
|
@@ -1024,7 +1313,13 @@ type Client<D extends ApiDescriptor, A = DefaultAttributes<D>, W = DefaultWriteA
|
|
|
1024
1313
|
* - return void/nothing -> the loose, ordered `AtomicResult[]` (backward-compatible).
|
|
1025
1314
|
*/
|
|
1026
1315
|
atomic<const Ops>(build: (tx: AtomicRecorder<D, W>) => Ops): Promise<Ops extends readonly AtomicHandle[] ? AtomicResults<D, A, Ops> : AtomicResult[]>;
|
|
1316
|
+
readonly [errorCodes]?: E;
|
|
1027
1317
|
};
|
|
1318
|
+
declare const errorCodes: unique symbol;
|
|
1319
|
+
/** The error-code union a client type carries, or `string` for one that carries none. */
|
|
1320
|
+
type ErrorCodeOf<T> = T extends {
|
|
1321
|
+
readonly [errorCodes]?: infer C extends string;
|
|
1322
|
+
} ? C : string;
|
|
1028
1323
|
/**
|
|
1029
1324
|
* The fallback attribute map when a caller doesn't supply the generated `Attributes`
|
|
1030
1325
|
* (direct `createClient` users without codegen): every type's attributes are an open
|
|
@@ -1113,9 +1408,9 @@ declare const HANDLE_RESERVED: ReadonlySet<string>;
|
|
|
1113
1408
|
* not per-type codegen: `client.<type>` yields a {@link TypeAccessor}, `.id(id)` a handle,
|
|
1114
1409
|
* and a handle's relation accessors drive the relationship/related endpoints. Navigation
|
|
1115
1410
|
* (`$next()`/`$prev()`, related links) is wired through `materialise`'s `navigate` seam,
|
|
1116
|
-
* which
|
|
1411
|
+
* which resolves the server's link against `baseUrl`, executes it and re-materialises.
|
|
1117
1412
|
*/
|
|
1118
|
-
declare function createClient<D extends ApiDescriptor, A = DefaultAttributes<D>, W = DefaultWriteAttributes<D>, Act = DefaultActionTypes>(descriptor: D, options: ClientOptions): Client<D, A, W, Act>;
|
|
1413
|
+
declare function createClient<D extends ApiDescriptor, A = DefaultAttributes<D>, W = DefaultWriteAttributes<D>, Act = DefaultActionTypes, E extends string = string>(descriptor: D, options: ClientOptions): Client<D, A, W, Act, E>;
|
|
1119
1414
|
//#endregion
|
|
1120
1415
|
//#region src/request.d.ts
|
|
1121
1416
|
/**
|
|
@@ -1150,8 +1445,17 @@ interface JsonApiContext {
|
|
|
1150
1445
|
/** A single read/write request, before serialisation. */
|
|
1151
1446
|
interface JsonApiRequest {
|
|
1152
1447
|
method: string;
|
|
1153
|
-
/**
|
|
1448
|
+
/**
|
|
1449
|
+
* A path appended to `baseUrl` (a filled path template such as `/albums/1`), or an absolute
|
|
1450
|
+
* `http(s)://` URL. With {@link link} set, a link from the server instead.
|
|
1451
|
+
*/
|
|
1154
1452
|
path: string;
|
|
1453
|
+
/**
|
|
1454
|
+
* `path` is a link the server sent (a `links` member such as `next`, or a `Location` header).
|
|
1455
|
+
* It resolves against `baseUrl` per RFC 3986, so a root-relative `/api/v5/albums?page[number]=2`
|
|
1456
|
+
* keeps the base's origin but not its path, where a path template is appended to the base.
|
|
1457
|
+
*/
|
|
1458
|
+
link?: boolean;
|
|
1155
1459
|
query?: ReadQuery;
|
|
1156
1460
|
body?: unknown;
|
|
1157
1461
|
/**
|
|
@@ -1195,8 +1499,8 @@ interface JsonApiRequest {
|
|
|
1195
1499
|
*/
|
|
1196
1500
|
declare function serializeQuery(query: ReadQuery): string;
|
|
1197
1501
|
/**
|
|
1198
|
-
* Execute a request: build the URL (
|
|
1199
|
-
* else `baseUrl + path`), apply content negotiation and any async per-request headers,
|
|
1502
|
+
* Execute a request: build the URL (a server link resolved against `baseUrl`, an absolute path
|
|
1503
|
+
* verbatim, else `baseUrl + path`), apply content negotiation and any async per-request headers,
|
|
1200
1504
|
* then drive the transport. A 2xx with a body parses to a {@link Document}; an empty /
|
|
1201
1505
|
* 204 body resolves to `undefined`. A non-2xx throws a {@link JsonApiError} carrying the
|
|
1202
1506
|
* parsed error objects (tolerating a non-JSON error body with an empty list).
|
|
@@ -1270,5 +1574,5 @@ declare function assertJsonApiDocument(body: unknown): asserts body is Document;
|
|
|
1270
1574
|
*/
|
|
1271
1575
|
declare function validateDocument(body: Document, validator: ResolvedValidator): void;
|
|
1272
1576
|
//#endregion
|
|
1273
|
-
export {
|
|
1274
|
-
//# sourceMappingURL=validate-
|
|
1577
|
+
export { RelationshipAccessor as $, ActionDescriptor as $t, DefaultActionTypes as A, DocumentEnvelope as At, IdentifierMember as B, relatedPaginatorKind as Bt, fetchTransport as C, ATOMIC_MEDIA_TYPE as Ct, Collection as D, ResourceIdentifier as Dt, Client as E, MediaTypeParams as Et, FieldSelectionMap as F, RelationView as Ft, MemberName as G, PageLinks as Gt, IncludedRelations as H, ExactPageParams as Ht, FieldsMap as I, ResourceEnvelope as It, ReadableRelationName as J, PageParams as Jt, PivotInput as K, PageMeta as Kt, FilterKey as L, ResourceLinks as Lt, DefaultWriteAttributes as M, EdgeEnvelope as Mt, EdgeMembers as N, MaterialiseContext as Nt, CountToken as O, mediaType as Ot, ErrorCodeOf as P, NavigateOptions as Pt, RelationReadQuery as Q, resolvePaginator as Qt, FilterQuery as R, materialise as Rt, TransportResponse as S, ATOMIC_EXT as St, AtomicResultOf as T, LocalIdentifier as Tt, LinkageRef as U, Page as Ut, IncludePath as V, CANONICAL_PAGE_KEYS as Vt, ListPageParams as W, PageKeys as Wt, RelationPageParams as X, ResolvedPaginator as Xt, RelationName as Y, PageParamsOf as Yt, RelationPaginator as Z, TypedPage as Zt, HANDLE_RESERVED as _, StrategyDescriptor as _n, AtomicResult as _t, Validator as a, Cardinality as an, SortToken as at, JsonApiTransport as b, AtomicUpdateInput as bt, resolveValidator as c, MenuDescriptor as cn, TypedReadQuery as ct, JsonApiContext as d, PaginatorRole as dn, AtomicCreateHandle as dt, ActionInput as en, ResourceAccessors as et, JsonApiRequest as f, PaginatorStrategy as fn, AtomicCreateInput as ft, ClientOptions as g, ResourceDescriptor as gn, AtomicRef as gt, serializeQuery as h, RelationMutations as hn, AtomicRecorder as ht, ValidationOption as i, AtomicDescriptor as in, SortQuery as it, DefaultAttributes as j, Edge as jt, CreateInput as k, ArrayEnvelope as kt, validateDocument as l, PaginatorDescriptor as ln, UpdateInput as lt, execute as m, RelationDescriptor as mn, AtomicHandle as mt, SchemaMap as n, ActionScope as nn, ResourceObjectView as nt, WireResource as o, ClientIdPolicy as on, TypeAccessor as ot, ReadQuery as p, PivotDescriptor as pn, AtomicDeleteHandle as pt, ReadResult as q, PageOf as qt, ValidationConfig as r, ApiDescriptor as rn, SingleReadQuery as rt, assertJsonApiDocument as s, CountableDescriptor as sn, TypeName as st, ResolvedValidator as t, ActionOutput as tn, ResourceHandle as tt, Document as u, PaginatorKind as un, WriteOptions as ut, HeadersInit as v, AtomicResults as vt, ActionsAccessor as w, JSON_API_MEDIA_TYPE as wt, TransportRequest as x, runAtomic as xt, createClient as y, AtomicUpdateHandle as yt, HydratedMember as z, relatedPaginator as zt };
|
|
1578
|
+
//# sourceMappingURL=validate-CJPivkqT.d.cts.map
|