@foir/sdk 0.7.0 → 0.8.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/CHANGELOG.md +805 -4
- package/README.md +46 -15
- package/dist/auth/auth.d.ts +104 -1
- package/dist/auth/index.d.ts +12 -6
- package/dist/auth/operations.d.ts +85 -64
- package/dist/auth/ops.d.ts +99 -0
- package/dist/auth.js +775 -106
- package/dist/chunk-IGNLWT3H.js +35 -0
- package/dist/chunk-OHYVKTGB.js +45 -0
- package/dist/chunk-TAL5H6VA.js +39 -0
- package/dist/client.js +25 -31
- package/dist/codegen/block-registry.d.ts +13 -19
- package/dist/codegen/block-registry.js +3 -31
- package/dist/codegen/block-scaffold.d.ts +12 -21
- package/dist/codegen/block-scaffold.js +3 -41
- package/dist/codegen/index.d.ts +9 -4
- package/dist/codegen/index.js +7 -11
- package/dist/codegen/item-registry.d.ts +28 -0
- package/dist/codegen/item-registry.js +7 -0
- package/dist/codegen/item-scaffold.d.ts +29 -0
- package/dist/codegen/item-scaffold.js +7 -0
- package/dist/codegen/preset.d.ts +14 -7
- package/dist/codegen/shared.d.ts +20 -9
- package/dist/content/content-renderer.d.ts +90 -24
- package/dist/content/index.d.ts +13 -7
- package/dist/content/richtext.d.ts +66 -18
- package/dist/content.js +227 -80
- package/dist/data/builtins.d.ts +302 -92
- package/dist/data/client.d.ts +97 -25
- package/dist/data/document.d.ts +251 -0
- package/dist/data/index.d.ts +38 -19
- package/dist/data/manifest.d.ts +266 -3
- package/dist/data/schema.d.ts +78 -8
- package/dist/data/selection.d.ts +231 -37
- package/dist/data/subjects.d.ts +221 -0
- package/dist/data/subscription-document.d.ts +75 -0
- package/dist/data/subscriptions.d.ts +181 -0
- package/dist/data/transport.d.ts +255 -37
- package/dist/data/ws-transport.d.ts +197 -0
- package/dist/data.js +1419 -45
- package/dist/graphql/cdn-route.d.ts +12 -28
- package/dist/media/index.d.ts +6 -2
- package/dist/media/media.d.ts +2 -2
- package/dist/media/screen-capture.d.ts +81 -0
- package/dist/media.js +97 -0
- package/dist/next.js +663 -182
- package/dist/push.js +700 -90
- package/dist/react/FoirFeedback.d.ts +62 -0
- package/dist/react/feedback-notes.d.ts +101 -0
- package/dist/react/index.d.ts +7 -1
- package/dist/react.js +1272 -194
- package/dist/realtime.js +1 -1
- package/dist/server/events-client.d.ts +279 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server.js +298 -0
- package/dist/transport/foir-http.d.ts +23 -6
- package/dist/transport/persisted-query.d.ts +68 -0
- package/dist/transport/retry.d.ts +1 -1
- package/package.json +13 -1
- package/dist/chunk-ADO5FJNM.js +0 -30
package/dist/data/schema.d.ts
CHANGED
|
@@ -11,9 +11,20 @@
|
|
|
11
11
|
* What the generator emits
|
|
12
12
|
* ---------------------------------------------------------------------------
|
|
13
13
|
*
|
|
14
|
-
* A single module, `foir.generated.ts
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* A single module, `foir.generated.ts`, PASSED WHOLE:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import * as generated from './foir.generated';
|
|
18
|
+
* const foir = createFoirClient({ apiUrl, apiKey, generated });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* A `.ts` and not a `.d.ts` because the file has a runtime half — the selection
|
|
22
|
+
* trees, `foirSchemaVersion`, and `foirSchemaType`, which is what carries
|
|
23
|
+
* `FoirSchema` across the type/value boundary so the client can infer it (a
|
|
24
|
+
* namespace import carries values; an interface erases). `FoirGeneratedModule`
|
|
25
|
+
* in `transport.ts` is the shape those exports have to satisfy, and it requires
|
|
26
|
+
* all of them: there is no case where a subset is wanted, and accepting one
|
|
27
|
+
* quietly meant a client that fetched the manifest on every cold start instead.
|
|
17
28
|
*
|
|
18
29
|
* ```ts
|
|
19
30
|
* // foir.generated.ts — DO NOT EDIT. Generated by `foir types`.
|
|
@@ -23,14 +34,26 @@
|
|
|
23
34
|
* export const foirSchemaVersion = 317;
|
|
24
35
|
* export const foirSchemaChannel = 'published';
|
|
25
36
|
*
|
|
26
|
-
*
|
|
37
|
+
* // `<D>` ONLY on the types between the record and a union stopping point —
|
|
38
|
+
* // see rule 10. Everything else is emitted without it.
|
|
39
|
+
* export interface TillyBlock<D extends FoirDepthLevel = 1> {
|
|
27
40
|
* _id: string;
|
|
28
41
|
* _createdAt: string;
|
|
29
42
|
* text: string | null;
|
|
30
43
|
* // Composite/polymorphic fields are emitted as a STATIC discriminated
|
|
31
44
|
* // union on `__typename`. The client never infers these; it only has to
|
|
32
45
|
* // avoid destroying them. See `selection.ts`.
|
|
33
|
-
* body: Array<HeroBlock | TextBlock> | null;
|
|
46
|
+
* body: Array<HeroBlock<D> | TextBlock> | null;
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* export interface HeroBlock<D extends FoirDepthLevel = 1> {
|
|
50
|
+
* __typename: 'HeroBlock';
|
|
51
|
+
* headline: string;
|
|
52
|
+
* // The stopping point: a union reached from inside a union member is
|
|
53
|
+
* // already on the op compiler's path, so it degrades — unless the caller
|
|
54
|
+
* // bought a level. Mirrors the printer's `depth - 1` at this position.
|
|
55
|
+
* body: Array<(D extends 1 ? TillyBlockBodyDegraded
|
|
56
|
+
* : TillyBlockBody<FoirDepthBelow[D]>) | null> | null;
|
|
34
57
|
* }
|
|
35
58
|
*
|
|
36
59
|
* export interface TillyBlockFindUniqueArgs {
|
|
@@ -54,6 +77,8 @@
|
|
|
54
77
|
* tilly_block: {
|
|
55
78
|
* typeName: 'TillyBlock';
|
|
56
79
|
* fields: TillyBlock;
|
|
80
|
+
* // Rule 10. Absent on a model with no stopping point, which is most.
|
|
81
|
+
* atDepth: { 2: TillyBlock<2>; 3: TillyBlock<3>; 4: TillyBlock<4> };
|
|
57
82
|
* relations: {
|
|
58
83
|
* parentBlockId: { target: 'tilly_block'; list: false; nullable: true };
|
|
59
84
|
* };
|
|
@@ -80,6 +105,10 @@
|
|
|
80
105
|
* };
|
|
81
106
|
* };
|
|
82
107
|
* }
|
|
108
|
+
*
|
|
109
|
+
* // The type carrier. `FoirSchema` is an interface and erases, so this is the
|
|
110
|
+
* // only thing a namespace import can infer it from. Runtime value: undefined.
|
|
111
|
+
* export const foirSchemaType = undefined as unknown as FoirSchema;
|
|
83
112
|
* ```
|
|
84
113
|
*
|
|
85
114
|
* ---------------------------------------------------------------------------
|
|
@@ -87,9 +116,9 @@
|
|
|
87
116
|
* ---------------------------------------------------------------------------
|
|
88
117
|
*
|
|
89
118
|
* 1. **Model keys are the manifest's model keys verbatim** (`tilly_block`, not
|
|
90
|
-
* `tillyBlock`). They are
|
|
91
|
-
*
|
|
92
|
-
* for aesthetics breaks the
|
|
119
|
+
* `tillyBlock`). They are the key the client looks the model's selection
|
|
120
|
+
* tree up by, and the key a refusal names when there is no tree to look up.
|
|
121
|
+
* Renaming them for aesthetics breaks the lookup.
|
|
93
122
|
*
|
|
94
123
|
* 2. **Op names are the manifest's op names verbatim** (`findUnique`,
|
|
95
124
|
* `findMany`, `search`, …) — the canonical names, NOT the GraphQL root
|
|
@@ -144,6 +173,27 @@
|
|
|
144
173
|
* (as the sketch above does) is the safe shape; if the generator ever
|
|
145
174
|
* factors these out for readability, they have to be `type X = {…}`.
|
|
146
175
|
* `__tests__/fixture-schema.ts` is the compiling reference.
|
|
176
|
+
*
|
|
177
|
+
* 10. **The `<D>` level parameter goes on the path to a stopping point, and
|
|
178
|
+
* nowhere else.** A union reached from inside a union member is already on
|
|
179
|
+
* the op compiler's path, so it degrades to `__typename` — and `depth: 2`
|
|
180
|
+
* buys one such level back, on the wire AND in the type. The parameter is
|
|
181
|
+
* what carries the caller's level from the record down to that position,
|
|
182
|
+
* so it belongs on exactly the interfaces, union aliases and member
|
|
183
|
+
* projections between the two, and the model names its shape at each level
|
|
184
|
+
* under `atDepth`.
|
|
185
|
+
*
|
|
186
|
+
* Both halves of "and nowhere else" matter. A parameter on a type that
|
|
187
|
+
* reaches no stopping point is a generic instantiation at every reference
|
|
188
|
+
* that can only ever resolve to the default — measured at 7 interfaces of
|
|
189
|
+
* 50 on the live dev project, which is why the change costs +1.6% types
|
|
190
|
+
* rather than a multiple. And `= 1` on every one of them is what makes a
|
|
191
|
+
* call passing no `depth` resolve to the type it always did; a parameter
|
|
192
|
+
* without a default would be a breaking change to every generated name.
|
|
193
|
+
*
|
|
194
|
+
* The generator computes the set (`depthReach`); it is not a judgement
|
|
195
|
+
* call. See `builtins.ts` for `FoirDepthLevel` / `FoirDepthBelow`, and
|
|
196
|
+
* `selection.ts` for the single lookup that reads `atDepth`.
|
|
147
197
|
*/
|
|
148
198
|
/**
|
|
149
199
|
* A model's complete scalar shape. Deliberately loose — the generator writes a
|
|
@@ -191,6 +241,26 @@ export interface FoirModelDefinition {
|
|
|
191
241
|
readonly typeName: string;
|
|
192
242
|
/** Complete scalar shape. See contract rule 4. */
|
|
193
243
|
readonly fields: FoirFieldsShape;
|
|
244
|
+
/**
|
|
245
|
+
* The same shape at a depth ABOVE the default, keyed by the level a caller
|
|
246
|
+
* would pass as `depth`.
|
|
247
|
+
*
|
|
248
|
+
* Present only on models whose tree reaches a union stopping point — a
|
|
249
|
+
* position the op compiler refuses to expand because the union is already on
|
|
250
|
+
* its path. `depth: 2` expands one such level on the wire, and the entry
|
|
251
|
+
* under `2` is the model type describing what then arrives.
|
|
252
|
+
*
|
|
253
|
+
* OPTIONAL, and absent is the common case. A model with no stopping point
|
|
254
|
+
* reads identically at every depth, so generating an entry for it would emit
|
|
255
|
+
* a second name for one type. A LEVEL with no entry — a depth past what the
|
|
256
|
+
* generator emitted, or a `depth` that is not a literal at the call site —
|
|
257
|
+
* falls back to `fields`, which under-promises: the caller gets the values
|
|
258
|
+
* and narrows by hand, exactly as they did before this key existed. That is
|
|
259
|
+
* the safe direction, and the reason absence never has to be an error.
|
|
260
|
+
*
|
|
261
|
+
* Contract rule 9 applies: emit it as an object type LITERAL.
|
|
262
|
+
*/
|
|
263
|
+
readonly atDepth?: Readonly<Record<number, FoirFieldsShape>>;
|
|
194
264
|
readonly relations: Readonly<Record<string, FoirRelationDefinition>>;
|
|
195
265
|
readonly ops: Readonly<Record<string, FoirOpDefinition>>;
|
|
196
266
|
}
|
package/dist/data/selection.d.ts
CHANGED
|
@@ -50,14 +50,39 @@
|
|
|
50
50
|
* the pipeline to do it reds all six union tests plus the rest-operator
|
|
51
51
|
* one by name.
|
|
52
52
|
*
|
|
53
|
-
* So: never reach inside a field's value type
|
|
53
|
+
* So: never reach inside a field's value type WITH A MAPPED TYPE OVER THE UNION.
|
|
54
54
|
*
|
|
55
55
|
* Nested selection (`pick: { seo: { jsonLd: true } }`) does reach one level in,
|
|
56
56
|
* and is safe only because `Descendable` refuses unions before it gets there —
|
|
57
|
-
* see the guard below and the two tests that pin it.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
57
|
+
* see the guard below and the two tests that pin it.
|
|
58
|
+
*
|
|
59
|
+
* ---------------------------------------------------------------------------
|
|
60
|
+
* Per-variant selection, and why it is not a hole in the above
|
|
61
|
+
* ---------------------------------------------------------------------------
|
|
62
|
+
*
|
|
63
|
+
* `pick: { blocks: { on: { ImageBlock: ['height'] } } }` DOES narrow inside a
|
|
64
|
+
* polymorphic union, and it is safe for a reason the rule above does not cover:
|
|
65
|
+
* it distributes FIRST. `U extends { __typename: infer T } ? … : never` on a
|
|
66
|
+
* naked type parameter splits the union into its members, and every `Pick` or
|
|
67
|
+
* `Omit` below that line applies to ONE member. The union is reassembled from
|
|
68
|
+
* the branches, never mapped over whole, so `keyof (A | B | C)` is never taken
|
|
69
|
+
* and there is nothing to collapse.
|
|
70
|
+
*
|
|
71
|
+
* The distinction is the whole design, and it is worth stating as a rule
|
|
72
|
+
* because the wrong version of this change is a one-line widening of
|
|
73
|
+
* `Descendable`:
|
|
74
|
+
*
|
|
75
|
+
* - reaching into a union with the VERB is Sanity's failure mode and stays
|
|
76
|
+
* refused — `Descendable` still returns false for every union, and it
|
|
77
|
+
* checks `IsUnion` rather than `IsPolymorphic` so that widening the
|
|
78
|
+
* polymorphic branch cannot widen this one by accident;
|
|
79
|
+
* - reaching into it through `on`, per member, keyed on `__typename`, is the
|
|
80
|
+
* supported form.
|
|
81
|
+
*
|
|
82
|
+
* What DID change is that a LIST of composites is now descendable. Refusing
|
|
83
|
+
* lists was never about union safety — a union in a list is caught by the union
|
|
84
|
+
* check either way — and it made `pageContent.sections.blocks` untypeable end to
|
|
85
|
+
* end, since a list is the only thing between a page and its blocks.
|
|
61
86
|
*/
|
|
62
87
|
import type { FoirConnection, FoirModelDefinition, FoirNamespaceOpDefinition, FoirOpDefinition, FoirRelationDefinition, FoirSchemaDefinition } from './schema';
|
|
63
88
|
/**
|
|
@@ -104,7 +129,7 @@ export type FoirOmittableKey<M extends FoirModelDefinition> = Exclude<Extract<ke
|
|
|
104
129
|
type OmittedKeys<Q> = Q extends {
|
|
105
130
|
omit: readonly (infer K)[];
|
|
106
131
|
} ? Extract<K, string> : never;
|
|
107
|
-
type ApplyOmit<
|
|
132
|
+
type ApplyOmit<F, Q> = [OmittedKeys<Q>] extends [never] ? F : Omit<F, OmittedKeys<Q>>;
|
|
108
133
|
/**
|
|
109
134
|
* Every scalar key, including `_id`.
|
|
110
135
|
*
|
|
@@ -126,7 +151,32 @@ type PickedKeys<Q> = Q extends {
|
|
|
126
151
|
* This is the one place `pick` is not literally what was asked for, which is
|
|
127
152
|
* why it is stated in the docs rather than left to be discovered.
|
|
128
153
|
*/
|
|
129
|
-
type ApplyPick<
|
|
154
|
+
type ApplyPick<F, Q> = Pick<F, Extract<PickedKeys<Q> | '_id', keyof F>>;
|
|
155
|
+
/**
|
|
156
|
+
* The level a call asked for. `1` — the complete-by-default document — when
|
|
157
|
+
* `depth` is absent, which is what makes the default result type reach
|
|
158
|
+
* `M['fields']` unchanged.
|
|
159
|
+
*
|
|
160
|
+
* A non-literal `depth` (a variable typed `number`) infers `D` as `number`,
|
|
161
|
+
* which matches no key below and falls back to the default shape. That
|
|
162
|
+
* under-promises rather than guessing, and is why `depth` can stay `number` at
|
|
163
|
+
* the call site instead of a literal union that would have to be regenerated.
|
|
164
|
+
*/
|
|
165
|
+
type DepthOf<Q> = Q extends {
|
|
166
|
+
depth: infer D extends number;
|
|
167
|
+
} ? D : 1;
|
|
168
|
+
/**
|
|
169
|
+
* The model's field shape at this call's depth.
|
|
170
|
+
*
|
|
171
|
+
* `M['fields']` BY IDENTITY whenever the model publishes no shape for the
|
|
172
|
+
* level — the default, every model without a stopping point, and any depth
|
|
173
|
+
* past what the generator emitted. `FoirRecordResult`'s identity property
|
|
174
|
+
* (a no-argument call hovers as `Product`, not as an anonymous object) is that
|
|
175
|
+
* fallback, so it holds for exactly the calls it held for before.
|
|
176
|
+
*/
|
|
177
|
+
type FieldsAtDepth<M extends FoirModelDefinition, Depth extends number> = M extends {
|
|
178
|
+
atDepth: infer A;
|
|
179
|
+
} ? Depth extends keyof A ? A[Depth & keyof A] : M['fields'] : M['fields'];
|
|
130
180
|
/**
|
|
131
181
|
* One narrowing step for both verbs.
|
|
132
182
|
*
|
|
@@ -134,7 +184,16 @@ type ApplyPick<M extends FoirModelDefinition, Q> = Pick<M['fields'], Extract<Pic
|
|
|
134
184
|
* to decide which is present — `pick` first, and `omit` is then reached exactly
|
|
135
185
|
* when `pick` is absent.
|
|
136
186
|
*/
|
|
137
|
-
type ApplySelection<M extends FoirModelDefinition, Q
|
|
187
|
+
type ApplySelection<M extends FoirModelDefinition, Q, Depth extends number> = ApplySelectionOn<FieldsAtDepth<M, Depth>, Q>;
|
|
188
|
+
/**
|
|
189
|
+
* The same two verbs, over an already-resolved field shape.
|
|
190
|
+
*
|
|
191
|
+
* Split out from `ApplySelection` only so the depth lookup is written ONCE. A
|
|
192
|
+
* conditional type has no `let`, so the alternative was `FieldsAtDepth<M, Depth>`
|
|
193
|
+
* spelled out in all four branches — four places for the two to drift apart,
|
|
194
|
+
* and the verbs stop being about `M` at all once the shape is chosen.
|
|
195
|
+
*/
|
|
196
|
+
type ApplySelectionOn<F, Q> = [PickedKeys<Q>] extends [never] ? [SelectorOf<Q, 'pick'>] extends [never] ? [SelectorOf<Q, 'omit'>] extends [never] ? ApplyOmit<F, Q> : ApplyOmitSelector<F, SelectorOf<Q, 'omit'>> : ApplyPickSelector<F, SelectorOf<Q, 'pick'>> : ApplyPick<F, Q>;
|
|
138
197
|
/**
|
|
139
198
|
* True when `T` is a union.
|
|
140
199
|
*
|
|
@@ -145,21 +204,111 @@ type ApplySelection<M extends FoirModelDefinition, Q> = [PickedKeys<Q>] extends
|
|
|
145
204
|
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
146
205
|
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
|
|
147
206
|
/**
|
|
148
|
-
*
|
|
207
|
+
* A field's value with its list wrapper and nullability stripped — the thing a
|
|
208
|
+
* selection actually addresses.
|
|
209
|
+
*
|
|
210
|
+
* A block field is `Array<Block | null> | null`; what a caller narrows is
|
|
211
|
+
* `Block`. Every classifier below asks about this rather than about `V`, so a
|
|
212
|
+
* union in a list and a union on its own reach the same answer instead of the
|
|
213
|
+
* list check shadowing the union check.
|
|
214
|
+
*/
|
|
215
|
+
type ElementOf<V> = NonNullable<V> extends readonly (infer E)[] ? NonNullable<E> : NonNullable<V>;
|
|
216
|
+
/**
|
|
217
|
+
* Put back the wrapper `ElementOf` took off.
|
|
218
|
+
*
|
|
219
|
+
* Narrowing changes the ELEMENT and must not change the shape around it: a
|
|
220
|
+
* pick inside `Array<Block | null> | null` still arrives as an array, still
|
|
221
|
+
* nullable, still with nullable elements. Reconstructing it here is what keeps
|
|
222
|
+
* the result type describing the response rather than describing the part of it
|
|
223
|
+
* the narrowing was about.
|
|
224
|
+
*/
|
|
225
|
+
type Rewrap<V, T> = NonNullable<V> extends readonly (infer E)[] ? null extends E ? null extends V ? Array<T | null> | null : Array<T | null> : null extends V ? T[] | null : T[] : null extends V ? T | null : T;
|
|
226
|
+
/**
|
|
227
|
+
* A `__typename`-discriminated union — a polymorphic block field, in practice.
|
|
228
|
+
*
|
|
229
|
+
* This is the ONE union shape that can be narrowed safely, because
|
|
230
|
+
* `__typename` gives a closed set of names to key the narrowing on and
|
|
231
|
+
* `Extract<U, { __typename: T }>` recovers a member without ever reaching
|
|
232
|
+
* inside the union itself. Any other union stays undescendable; see
|
|
233
|
+
* `Descendable`.
|
|
234
|
+
*/
|
|
235
|
+
type IsPolymorphic<V> = ElementOf<V> extends {
|
|
236
|
+
__typename: string;
|
|
237
|
+
} ? IsUnion<ElementOf<V>> : false;
|
|
238
|
+
/**
|
|
239
|
+
* Whether a field's value may be descended into with the verb.
|
|
240
|
+
*
|
|
241
|
+
* Objects yes — including a LIST of them, which this used to refuse. Refusing
|
|
242
|
+
* lists made `pageContent.sections.blocks` untypeable end to end even though the
|
|
243
|
+
* wire had always accepted it: a list is the only thing between a page and its
|
|
244
|
+
* blocks, so a list that could not be descended meant no block union could be
|
|
245
|
+
* reached at all.
|
|
246
|
+
*
|
|
247
|
+
* **Unions still no**, and the exclusion is unchanged in force even though the
|
|
248
|
+
* shape moved. `Pick` and `Omit` compile to `{ [P in K]: … }`, which is not
|
|
249
|
+
* homomorphic and does not distribute, and `keyof (A | B | C)` is the
|
|
250
|
+
* INTERSECTION of their keys — so narrowing inside a polymorphic block union
|
|
251
|
+
* with the verb collapses it to `{ __typename: 'A' | 'B' | 'C' }` and every
|
|
252
|
+
* branch's own fields vanish. That is Sanity's documented failure mode, and the
|
|
253
|
+
* union tests in `__tests__/selection.test-d.ts` go red the moment anything
|
|
254
|
+
* reaches inside a field's value type WITH A MAPPED TYPE OVER THE UNION.
|
|
255
|
+
*
|
|
256
|
+
* What changed is that a polymorphic union now has somewhere else to go: the
|
|
257
|
+
* `on` branch in `FoirSelector`, which distributes first and picks per member,
|
|
258
|
+
* so no mapped type ever sees the union whole. A union that is NOT
|
|
259
|
+
* `__typename`-discriminated has no such handle and is still `true`-only — the
|
|
260
|
+
* check here is `IsUnion`, not `IsPolymorphic`, precisely so that widening the
|
|
261
|
+
* polymorphic branch can never widen this one.
|
|
262
|
+
*/
|
|
263
|
+
type Descendable<V> = IsUnion<ElementOf<V>> extends true ? false : ElementOf<V> extends object ? true : false;
|
|
264
|
+
/** The `__typename` literals of a polymorphic union. Closed, and generated. */
|
|
265
|
+
type MemberNames<U> = U extends {
|
|
266
|
+
__typename: infer T extends string;
|
|
267
|
+
} ? T : never;
|
|
268
|
+
/** The one member of `U` whose `__typename` is `T`. */
|
|
269
|
+
type MemberOf<U, T> = Extract<U, {
|
|
270
|
+
__typename: T;
|
|
271
|
+
}>;
|
|
272
|
+
/**
|
|
273
|
+
* The keys of one member a caller may name.
|
|
149
274
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
275
|
+
* `__typename` is excluded because it is not optional: the printer emits it at
|
|
276
|
+
* the union level whatever the selection says, so offering it would suggest a
|
|
277
|
+
* choice that does not exist. Block-meta keys (`_id`, `_key`, `_type`,
|
|
278
|
+
* `_label`) ARE offered — unlike a record's `_id` the server does not force
|
|
279
|
+
* them inside a fragment, so naming them is the only way to keep them.
|
|
280
|
+
*/
|
|
281
|
+
type MemberKeys<U, T> = Exclude<Extract<keyof MemberOf<U, T>, string>, '__typename'>;
|
|
282
|
+
/**
|
|
283
|
+
* The per-variant selector: name members, and for each either its keys or
|
|
284
|
+
* `true` for the whole member.
|
|
158
285
|
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
286
|
+
* Every member is OPTIONAL, and what an absent one means is the verb's to
|
|
287
|
+
* decide — `pick` drops it, `omit` leaves it alone — matching what the printer
|
|
288
|
+
* does with a member the trie does not name.
|
|
161
289
|
*/
|
|
162
|
-
type
|
|
290
|
+
export type FoirVariantSelector<U> = {
|
|
291
|
+
[T in MemberNames<U>]?: true | readonly MemberKeys<U, T>[];
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Apply a per-variant `pick`.
|
|
295
|
+
*
|
|
296
|
+
* A member the selector does not name arrives as `{ __typename }` alone rather
|
|
297
|
+
* than disappearing: the printer drops its FRAGMENT, but `__typename` is
|
|
298
|
+
* selected once at the union level, so the element is still there and still
|
|
299
|
+
* discriminates. Typing it as absent would describe a response with fewer
|
|
300
|
+
* elements than the server sends.
|
|
301
|
+
*/
|
|
302
|
+
type ApplyVariantPick<U, S> = U extends {
|
|
303
|
+
__typename: infer T;
|
|
304
|
+
} ? T extends keyof S ? S[T] extends true ? U : S[T] extends readonly (infer K)[] ? Pick<U, Extract<K | '__typename', keyof U>> : Pick<U, Extract<'__typename', keyof U>> : Pick<U, Extract<'__typename', keyof U>> : never;
|
|
305
|
+
/**
|
|
306
|
+
* Apply a per-variant `omit`. The mirror of the above: an unnamed member is
|
|
307
|
+
* untouched, and `true` drops the whole fragment, leaving `{ __typename }`.
|
|
308
|
+
*/
|
|
309
|
+
type ApplyVariantOmit<U, S> = U extends {
|
|
310
|
+
__typename: infer T;
|
|
311
|
+
} ? T extends keyof S ? S[T] extends true ? Pick<U, Extract<'__typename', keyof U>> : S[T] extends readonly (infer K)[] ? Omit<U, Extract<K, keyof U>> : U : U : never;
|
|
163
312
|
/**
|
|
164
313
|
* The nested selector: `true` for a leaf, or the verb again to descend.
|
|
165
314
|
*
|
|
@@ -178,13 +327,15 @@ type Descendable<V> = IsUnion<NonNullable<V>> extends true ? false : NonNullable
|
|
|
178
327
|
* an immediate "Type instantiation is excessively deep and possibly infinite".
|
|
179
328
|
*/
|
|
180
329
|
export type FoirSelector<T, Verb extends 'pick' | 'omit', D extends number = SelectorDepth> = {
|
|
181
|
-
[K in keyof T]?:
|
|
182
|
-
|
|
330
|
+
[K in keyof T]?: IsPolymorphic<T[K]> extends true ? true | {
|
|
331
|
+
on: FoirVariantSelector<ElementOf<T[K]>>;
|
|
332
|
+
} : Descendable<T[K]> extends true ? D extends 0 ? true : true | {
|
|
333
|
+
[P in Verb]: FoirSelector<ElementOf<T[K]>, Verb, Decrement[D]>;
|
|
183
334
|
} : true;
|
|
184
335
|
};
|
|
185
336
|
/**
|
|
186
337
|
* How deep a selector may nest. Three, matching `IncludeDepth`, and comfortably
|
|
187
|
-
* past what real composites do — `
|
|
338
|
+
* past what real composites do — `Image.variants.thumbnail` is the deepest
|
|
188
339
|
* shape on any live model at two.
|
|
189
340
|
*/
|
|
190
341
|
type SelectorDepth = 3;
|
|
@@ -195,9 +346,7 @@ type SelectorOf<Q, Verb extends 'pick' | 'omit'> = Q extends Record<Verb, infer
|
|
|
195
346
|
* reason it does in the flat form — the server always emits it.
|
|
196
347
|
*/
|
|
197
348
|
type ApplyPickSelector<F, S> = {
|
|
198
|
-
[K in Extract<keyof S, keyof F> | Extract<'_id', keyof F>]: K extends keyof S ? S[K]
|
|
199
|
-
pick: infer Inner;
|
|
200
|
-
} ? PickSelectorDeep<NonNullable<F[K]>, Inner> : F[K] : F[K];
|
|
349
|
+
[K in Extract<keyof S, keyof F> | Extract<'_id', keyof F>]: K extends keyof S ? NarrowPick<F[K], S[K]> : F[K];
|
|
201
350
|
};
|
|
202
351
|
/**
|
|
203
352
|
* The same walk one level in. Separate from the root because `_id` is forced at
|
|
@@ -205,10 +354,28 @@ type ApplyPickSelector<F, S> = {
|
|
|
205
354
|
* response shape the server never sends.
|
|
206
355
|
*/
|
|
207
356
|
type PickSelectorDeep<F, S> = {
|
|
208
|
-
[K in Extract<keyof S, keyof F>]: S[K]
|
|
209
|
-
pick: infer Inner;
|
|
210
|
-
} ? PickSelectorDeep<NonNullable<F[K]>, Inner> : F[K];
|
|
357
|
+
[K in Extract<keyof S, keyof F>]: NarrowPick<F[K], S[K]>;
|
|
211
358
|
};
|
|
359
|
+
/**
|
|
360
|
+
* One field's value under a `pick` selector: split a union, descend an object
|
|
361
|
+
* or a list of them, or leave a leaf alone.
|
|
362
|
+
*
|
|
363
|
+
* `Rewrap` is what makes the list case honest. Descending `sections` narrows
|
|
364
|
+
* each Section; the caller still receives the array, still nullable, with
|
|
365
|
+
* still-nullable elements, and reconstructing that is the difference between
|
|
366
|
+
* typing the response and typing the interesting part of it.
|
|
367
|
+
*/
|
|
368
|
+
type NarrowPick<V, Sel> = Sel extends {
|
|
369
|
+
on: infer Variants;
|
|
370
|
+
} ? Rewrap<V, ApplyVariantPick<ElementOf<V>, Variants>> : Sel extends {
|
|
371
|
+
pick: infer Inner;
|
|
372
|
+
} ? Rewrap<V, PickSelectorDeep<ElementOf<V>, Inner>> : V;
|
|
373
|
+
/** The `omit` half of `NarrowPick`. */
|
|
374
|
+
type NarrowOmit<V, Sel> = Sel extends {
|
|
375
|
+
on: infer Variants;
|
|
376
|
+
} ? Rewrap<V, ApplyVariantOmit<ElementOf<V>, Variants>> : Sel extends {
|
|
377
|
+
omit: infer Inner;
|
|
378
|
+
} ? Rewrap<V, ApplyOmitSelector<ElementOf<V>, Inner>> : V;
|
|
212
379
|
/**
|
|
213
380
|
* Apply a nested `omit` selector.
|
|
214
381
|
*
|
|
@@ -217,9 +384,7 @@ type PickSelectorDeep<F, S> = {
|
|
|
217
384
|
* able to shed one richtext encoding of three without losing the field.
|
|
218
385
|
*/
|
|
219
386
|
type ApplyOmitSelector<F, S> = {
|
|
220
|
-
[K in keyof F as K extends keyof S ? (S[K] extends true ? never : K) : K]: K extends keyof S ? S[K]
|
|
221
|
-
omit: infer Inner;
|
|
222
|
-
} ? ApplyOmitSelector<NonNullable<F[K]>, Inner> : F[K] : F[K];
|
|
387
|
+
[K in keyof F as K extends keyof S ? (S[K] extends true ? never : K) : K]: K extends keyof S ? NarrowOmit<F[K], S[K]> : F[K];
|
|
223
388
|
};
|
|
224
389
|
/**
|
|
225
390
|
* The `include` argument for a model: every relation key, opt-in, optionally
|
|
@@ -235,8 +400,8 @@ export type FoirInclude<S extends FoirSchemaDefinition, M extends FoirModelDefin
|
|
|
235
400
|
};
|
|
236
401
|
};
|
|
237
402
|
/** The relation keys a call asked for, shaped by list/nullable and nesting. */
|
|
238
|
-
type IncludedRelations<S extends FoirSchemaDefinition, M extends FoirModelDefinition, I, D extends number> = {
|
|
239
|
-
[K in Extract<keyof I, keyof M['relations']>]: WrapRelation<M['relations'][K], FoirRecordResult<S, TargetModel<S, M['relations'][K]>, I[K], Decrement[D]>>;
|
|
403
|
+
type IncludedRelations<S extends FoirSchemaDefinition, M extends FoirModelDefinition, I, D extends number, Depth extends number> = {
|
|
404
|
+
[K in Extract<keyof I, keyof M['relations']>]: WrapRelation<M['relations'][K], FoirRecordResult<S, TargetModel<S, M['relations'][K]>, I[K], Decrement[D], Depth>>;
|
|
240
405
|
};
|
|
241
406
|
type WrapRelation<R extends FoirRelationDefinition, T> = R['list'] extends true ? R['nullable'] extends true ? T[] | null : T[] : R['nullable'] extends true ? T | null : T;
|
|
242
407
|
/**
|
|
@@ -253,10 +418,16 @@ type WrapRelation<R extends FoirRelationDefinition, T> = R['list'] extends true
|
|
|
253
418
|
* reads well and names the generated interface — and, more to the point, it
|
|
254
419
|
* means the only mapped type anywhere near a result is `Omit`. One less thing
|
|
255
420
|
* between the generator's union and the call site.
|
|
421
|
+
*
|
|
422
|
+
* `Depth` is the call's union-level budget, defaulted FROM `Q` so every
|
|
423
|
+
* existing reference keeps working unchanged. It is passed to the relations
|
|
424
|
+
* UNDECREMENTED because the printer does the same: `printIncludes` carries the
|
|
425
|
+
* caller's depth into a relation's tree without spending any of it, so an
|
|
426
|
+
* included record's stopping points expand at exactly the level the root's do.
|
|
256
427
|
*/
|
|
257
|
-
export type FoirRecordResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, Q, D extends number = IncludeDepth
|
|
428
|
+
export type FoirRecordResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, Q, D extends number = IncludeDepth, Depth extends number = DepthOf<Q>> = Q extends {
|
|
258
429
|
include: infer I;
|
|
259
|
-
} ? ApplySelection<M, Q> & IncludedRelations<S, M, I, D> : ApplySelection<M, Q>;
|
|
430
|
+
} ? ApplySelection<M, Q, Depth> & IncludedRelations<S, M, I, D, Depth> : ApplySelection<M, Q, Depth>;
|
|
260
431
|
/** Apply an op's return wrapper to the shaped record. */
|
|
261
432
|
export type FoirOpResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, O extends FoirOpDefinition, Q> = O['returns'] extends {
|
|
262
433
|
kind: 'opaque';
|
|
@@ -270,6 +441,29 @@ export type FoirOpResult<S extends FoirSchemaDefinition, M extends FoirModelDefi
|
|
|
270
441
|
kind: 'record';
|
|
271
442
|
nullable: infer N;
|
|
272
443
|
} ? N extends true ? FoirRecordResult<S, M, Q> | null : FoirRecordResult<S, M, Q> : never;
|
|
444
|
+
/**
|
|
445
|
+
* The depth budget, as a call option.
|
|
446
|
+
*
|
|
447
|
+
* A sibling of `include`/`omit`/`pick` rather than an argument: it shapes the
|
|
448
|
+
* DOCUMENT the client prints, and no root field declares an argument by that
|
|
449
|
+
* name. `number` rather than a literal union of the levels that fit, because
|
|
450
|
+
* what fits is a property of the PROJECT — the same level is comfortable on one
|
|
451
|
+
* model and over the ceiling on another — so the bound is a runtime refusal
|
|
452
|
+
* carrying the arithmetic, not a type that would have to be regenerated to stay
|
|
453
|
+
* true and would be wrong between runs.
|
|
454
|
+
*
|
|
455
|
+
* The RESULT type follows it. A stopping point the level reaches is typed as
|
|
456
|
+
* the expanded union rather than `FoirDegraded`, so `depth: 2` reads a nested
|
|
457
|
+
* member's own field with a `__typename` narrowing and no cast. The generator
|
|
458
|
+
* threads a level parameter through the composites on the path and names the
|
|
459
|
+
* instantiations under `FoirModelDefinition.atDepth`; `FieldsAtDepth` picks
|
|
460
|
+
* one. A LITERAL is what makes that lookup work — a `depth` read from a
|
|
461
|
+
* variable is `number`, matches no level, and falls back to the default shape,
|
|
462
|
+
* which under-promises rather than guessing.
|
|
463
|
+
*/
|
|
464
|
+
interface FoirDepthOption {
|
|
465
|
+
depth?: number;
|
|
466
|
+
}
|
|
273
467
|
/**
|
|
274
468
|
* What a model op accepts: the op's own args with `include`/`omit` as siblings.
|
|
275
469
|
* They are split back apart onto the wire (`{ model, op, args, include, omit }`)
|
|
@@ -281,7 +475,7 @@ export type FoirOpResult<S extends FoirSchemaDefinition, M extends FoirModelDefi
|
|
|
281
475
|
*/
|
|
282
476
|
export type FoirQuery<S extends FoirSchemaDefinition, M extends FoirModelDefinition, O extends FoirOpDefinition> = O['returns'] extends {
|
|
283
477
|
kind: 'opaque';
|
|
284
|
-
} ? O['args'] : O['args'] & ({
|
|
478
|
+
} ? O['args'] : O['args'] & FoirDepthOption & ({
|
|
285
479
|
include?: FoirInclude<S, M>;
|
|
286
480
|
omit?: readonly FoirOmittableKey<M>[] | FoirSelector<M['fields'], 'omit'>;
|
|
287
481
|
/**
|