@foir/sdk 0.5.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 +1126 -0
- package/README.md +94 -0
- package/dist/auth/auth.d.ts +243 -0
- package/dist/auth/hosted.d.ts +40 -0
- package/dist/auth/index.d.ts +21 -0
- package/dist/auth/operations.d.ts +144 -0
- package/dist/auth/ops.d.ts +99 -0
- package/dist/auth/session-client.d.ts +76 -0
- package/dist/auth-session.js +137 -0
- package/dist/auth.js +973 -92
- package/dist/chunk-IGNLWT3H.js +35 -0
- package/dist/chunk-OHYVKTGB.js +45 -0
- package/dist/chunk-TAL5H6VA.js +39 -0
- package/dist/client/platform-client.d.ts +29 -0
- package/dist/client.js +275 -37
- package/dist/codegen/block-registry.d.ts +13 -23
- package/dist/codegen/block-registry.js +3 -31
- package/dist/codegen/block-scaffold.d.ts +12 -25
- package/dist/codegen/block-scaffold.js +3 -41
- package/dist/codegen/index.d.ts +12 -61
- 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 +67 -0
- package/dist/codegen/shared.d.ts +36 -0
- package/dist/components/EditorProvider.d.ts +36 -0
- package/dist/content/content-renderer.d.ts +113 -0
- package/dist/content/index.d.ts +18 -0
- package/dist/content/richtext.d.ts +118 -0
- package/dist/content.js +227 -80
- package/dist/data/builtins.d.ts +506 -0
- package/dist/data/client.d.ts +166 -0
- package/dist/data/document.d.ts +251 -0
- package/dist/data/index.d.ts +73 -0
- package/dist/data/manifest.d.ts +424 -0
- package/dist/data/schema.d.ts +313 -0
- package/dist/data/selection.d.ts +524 -0
- 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 +325 -0
- package/dist/data/ws-transport.d.ts +197 -0
- package/dist/data.js +1683 -0
- package/dist/graphql/cdn-route.d.ts +26 -0
- package/dist/graphql/client.d.ts +30 -0
- package/dist/graphql/content-client.d.ts +66 -0
- package/dist/graphql/foir-request.d.ts +47 -0
- package/dist/hooks/useAutoResize.d.ts +22 -0
- package/dist/hooks/useEditor.d.ts +16 -0
- package/dist/index.d.ts +10 -394
- package/dist/media/BlurhashCanvas.d.ts +10 -0
- package/dist/media/ProgressiveMedia.d.ts +56 -0
- package/dist/media/index.d.ts +18 -0
- package/dist/media/media.d.ts +139 -0
- package/dist/media/screen-capture.d.ts +81 -0
- package/dist/media/useProgressiveLoad.d.ts +23 -0
- package/dist/media.js +102 -0
- package/dist/next/cookieAdapter.d.ts +26 -0
- package/dist/next/index.d.ts +15 -0
- package/dist/next/middleware.d.ts +49 -0
- package/dist/next/session.d.ts +53 -0
- package/dist/next.js +763 -78
- package/dist/protocol/constants.d.ts +6 -0
- package/dist/protocol/messages.d.ts +155 -0
- package/dist/protocol/validation.d.ts +8 -0
- package/dist/push/index.d.ts +14 -0
- package/dist/push/operations.d.ts +22 -0
- package/dist/push/web-push.d.ts +39 -0
- package/dist/push.js +892 -78
- package/dist/react/FoirFeedback.d.ts +62 -0
- package/dist/react/FoirProvider.d.ts +116 -0
- package/dist/react/OAuthButtons.d.ts +20 -0
- package/dist/react/SignIn.d.ts +22 -0
- package/dist/react/UserButton.d.ts +13 -0
- package/dist/react/feedback-notes.d.ts +101 -0
- package/dist/react/index.d.ts +25 -0
- package/dist/react/localStorageAdapter.d.ts +10 -0
- package/dist/react.js +1373 -91
- package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
- package/dist/realtime.js +1 -1
- package/dist/server/callback-client.d.ts +92 -0
- package/dist/server/events-client.d.ts +279 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/operation-types.d.ts +134 -0
- package/dist/server/require-scoped-token.d.ts +76 -0
- package/dist/server/server-client.d.ts +34 -0
- package/dist/server/types.d.ts +54 -0
- package/dist/server/verify-scoped-token.d.ts +100 -0
- package/dist/server/webhook-helpers.d.ts +36 -0
- package/dist/server.js +298 -0
- package/dist/transport/foir-http.d.ts +234 -0
- package/dist/transport/persisted-query.d.ts +68 -0
- package/dist/transport/retry.d.ts +61 -0
- package/dist/types/index.d.ts +124 -0
- package/package.json +35 -13
- package/dist/auth.d.ts +0 -382
- package/dist/chunk-ADO5FJNM.js +0 -30
- package/dist/client.d.ts +0 -135
- package/dist/content.d.ts +0 -96
- package/dist/media.d.ts +0 -234
- package/dist/next.d.ts +0 -178
- package/dist/push.d.ts +0 -109
- package/dist/react.d.ts +0 -319
- package/dist/server.d.ts +0 -534
- package/dist/shared-DkWlEd2Z.d.ts +0 -18
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type-level selection machinery — deliberately shallow.
|
|
3
|
+
*
|
|
4
|
+
* Two operations and nothing else:
|
|
5
|
+
*
|
|
6
|
+
* 1. `Omit<Fields, K>` for `omit`.
|
|
7
|
+
* 2. One conditional per `include`d relation key.
|
|
8
|
+
*
|
|
9
|
+
* No template-literal parsing (Supabase's approach, and the source of its
|
|
10
|
+
* `tsc` perf complaints), no deep recursive mapped types over field values, no
|
|
11
|
+
* build-time query parser. The complete-by-default selection model is what
|
|
12
|
+
* makes that enough: scalars are ALWAYS returned, so the model type is
|
|
13
|
+
* unconditionally true and there is nothing to narrow. If this file ever
|
|
14
|
+
* misbehaves the failure mode is a missing relation key, not a mis-parsed
|
|
15
|
+
* string.
|
|
16
|
+
*
|
|
17
|
+
* ---------------------------------------------------------------------------
|
|
18
|
+
* Why unions survive
|
|
19
|
+
* ---------------------------------------------------------------------------
|
|
20
|
+
*
|
|
21
|
+
* Sanity's typegen — a mature, shipped implementation of the same
|
|
22
|
+
* architecture — is documented to lose discriminated-union information on
|
|
23
|
+
* rest-operator array projections. That is the fragile part of any generated
|
|
24
|
+
* type system, so it is worth being explicit about why it cannot happen here:
|
|
25
|
+
*
|
|
26
|
+
* every transformation below operates on the OUTER object's keys only. `Omit`,
|
|
27
|
+
* `Simplify` and the include-shape mapped type all iterate `keyof Fields` and
|
|
28
|
+
* copy `Fields[K]` through untouched. A polymorphic field's value type — a
|
|
29
|
+
* union of `__typename`-discriminated members, written out statically by the
|
|
30
|
+
* generator — is a property VALUE. It is never mapped over, never distributed,
|
|
31
|
+
* never re-inferred, so it arrives at the call site byte-identical to what the
|
|
32
|
+
* generator emitted and narrows under `switch` exactly as it would have.
|
|
33
|
+
*
|
|
34
|
+
* `__tests__/selection.test-d.ts` pins this, including the rest-operator and
|
|
35
|
+
* `.map()` shapes that break Sanity.
|
|
36
|
+
*
|
|
37
|
+
* Two notes for whoever edits this file next, both found by mutating it and
|
|
38
|
+
* watching what the tests did:
|
|
39
|
+
*
|
|
40
|
+
* - A homomorphic mapped type over a NAKED type parameter — the classic
|
|
41
|
+
* `type Simplify<T> = { [K in keyof T]: T[K] }` applied to an array's
|
|
42
|
+
* element type — is SAFE. It distributes over unions, so the discriminated
|
|
43
|
+
* union comes out the other side intact. Mutating the pipeline to do this
|
|
44
|
+
* left all six union tests green.
|
|
45
|
+
* - `Omit<U, …>` or `Pick<U, …>` on that element type is NOT safe. Both
|
|
46
|
+
* compile to `{ [P in K]: … }`, which is not homomorphic and does not
|
|
47
|
+
* distribute, and `keyof (A | B | C)` is the INTERSECTION of their keys —
|
|
48
|
+
* so the union collapses to `{ __typename: 'A' | 'B' | 'C' }` and every
|
|
49
|
+
* branch's own fields vanish. That is Sanity's failure mode, and mutating
|
|
50
|
+
* the pipeline to do it reds all six union tests plus the rest-operator
|
|
51
|
+
* one by name.
|
|
52
|
+
*
|
|
53
|
+
* So: never reach inside a field's value type WITH A MAPPED TYPE OVER THE UNION.
|
|
54
|
+
*
|
|
55
|
+
* Nested selection (`pick: { seo: { jsonLd: true } }`) does reach one level in,
|
|
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
|
+
* 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.
|
|
86
|
+
*/
|
|
87
|
+
import type { FoirConnection, FoirModelDefinition, FoirNamespaceOpDefinition, FoirOpDefinition, FoirRelationDefinition, FoirSchemaDefinition } from './schema';
|
|
88
|
+
/**
|
|
89
|
+
* How deep `include` may nest. Finite by necessity, not by taste: relations are
|
|
90
|
+
* routinely self-referential (`tilly_block.parentBlockId -> tilly_block`), and
|
|
91
|
+
* an uncapped recursive mapped type over that is an immediate
|
|
92
|
+
* "Type instantiation is excessively deep and possibly infinite".
|
|
93
|
+
*/
|
|
94
|
+
type IncludeDepth = 3;
|
|
95
|
+
/** `Decrement[3] === 2`. */
|
|
96
|
+
type Decrement = [never, 0, 1, 2, 3];
|
|
97
|
+
/**
|
|
98
|
+
* The model a relation resolves to. Falls back to a relation-free, opaque model
|
|
99
|
+
* when the generated schema names a target that is not in `models` — a
|
|
100
|
+
* malformed generated file should surface as a useless-but-quiet type, not as a
|
|
101
|
+
* mapped type over `keyof never`.
|
|
102
|
+
*/
|
|
103
|
+
type TargetModel<S extends FoirSchemaDefinition, R extends FoirRelationDefinition> = R['target'] extends keyof S['models'] ? S['models'][R['target']] : OpaqueModel;
|
|
104
|
+
type OpaqueModel = {
|
|
105
|
+
typeName: string;
|
|
106
|
+
fields: Record<string, unknown>;
|
|
107
|
+
relations: Record<never, FoirRelationDefinition>;
|
|
108
|
+
ops: Record<never, FoirOpDefinition>;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* The keys a caller may `omit`.
|
|
112
|
+
*
|
|
113
|
+
* `_id` is excluded because the server refuses it — verified against deployed
|
|
114
|
+
* dev: `omit "_id": "_id" is required for correctness and cannot be removed`
|
|
115
|
+
* (HTTP 400). Excluding it here turns a runtime 400 into a compile error.
|
|
116
|
+
*
|
|
117
|
+
* Relations are absent by construction: they are not in `fields`, and omitting
|
|
118
|
+
* something you never asked for is meaningless.
|
|
119
|
+
*/
|
|
120
|
+
export type FoirOmittableKey<M extends FoirModelDefinition> = Exclude<Extract<keyof M['fields'], string>, '_id'>;
|
|
121
|
+
/**
|
|
122
|
+
* The `omit` entries actually present in a call.
|
|
123
|
+
*
|
|
124
|
+
* `never` when `omit` was not passed. That matters: an OPTIONAL `omit?:` on the
|
|
125
|
+
* inferred query type does not satisfy the required-property check below, so
|
|
126
|
+
* the no-argument case (where `Q` falls back to its own constraint) correctly
|
|
127
|
+
* omits nothing rather than omitting every omittable key.
|
|
128
|
+
*/
|
|
129
|
+
type OmittedKeys<Q> = Q extends {
|
|
130
|
+
omit: readonly (infer K)[];
|
|
131
|
+
} ? Extract<K, string> : never;
|
|
132
|
+
type ApplyOmit<F, Q> = [OmittedKeys<Q>] extends [never] ? F : Omit<F, OmittedKeys<Q>>;
|
|
133
|
+
/**
|
|
134
|
+
* Every scalar key, including `_id`.
|
|
135
|
+
*
|
|
136
|
+
* Unlike `FoirOmittableKey` this does NOT exclude `_id`: naming it is harmless
|
|
137
|
+
* because it survives either way, and excluding it would make the obvious
|
|
138
|
+
* `pick: ['_id', 'title']` a type error for no reason.
|
|
139
|
+
*/
|
|
140
|
+
export type FoirPickableKey<M extends FoirModelDefinition> = Extract<keyof M['fields'], string>;
|
|
141
|
+
/** The `pick` entries actually present in a call. `never` when absent — see OmittedKeys. */
|
|
142
|
+
type PickedKeys<Q> = Q extends {
|
|
143
|
+
pick: readonly (infer K)[];
|
|
144
|
+
} ? Extract<K, string> : never;
|
|
145
|
+
/**
|
|
146
|
+
* `_id` is added back unconditionally, because the server emits it whatever the
|
|
147
|
+
* caller asked for (`protectedSelectionFields`). Typing the result without it
|
|
148
|
+
* would describe a response the server does not send, and a caller would lose
|
|
149
|
+
* the ability to address the record they just read.
|
|
150
|
+
*
|
|
151
|
+
* This is the one place `pick` is not literally what was asked for, which is
|
|
152
|
+
* why it is stated in the docs rather than left to be discovered.
|
|
153
|
+
*/
|
|
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'];
|
|
180
|
+
/**
|
|
181
|
+
* One narrowing step for both verbs.
|
|
182
|
+
*
|
|
183
|
+
* They are mutually exclusive on the wire and in `FoirQuery`, so this only has
|
|
184
|
+
* to decide which is present — `pick` first, and `omit` is then reached exactly
|
|
185
|
+
* when `pick` is absent.
|
|
186
|
+
*/
|
|
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>;
|
|
197
|
+
/**
|
|
198
|
+
* True when `T` is a union.
|
|
199
|
+
*
|
|
200
|
+
* Load-bearing for the guard below rather than a curiosity: a union distributed
|
|
201
|
+
* through `(U extends any ? (k: U) => void : never)` intersects to something
|
|
202
|
+
* that is not assignable back to the original, which a single type always is.
|
|
203
|
+
*/
|
|
204
|
+
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
205
|
+
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
|
|
206
|
+
/**
|
|
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.
|
|
274
|
+
*
|
|
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.
|
|
285
|
+
*
|
|
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.
|
|
289
|
+
*/
|
|
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;
|
|
312
|
+
/**
|
|
313
|
+
* The nested selector: `true` for a leaf, or the verb again to descend.
|
|
314
|
+
*
|
|
315
|
+
* The verb repeats at each level — `pick: { seo: { pick: { jsonLd: true } } }` —
|
|
316
|
+
* which is Prisma's shape and, more to the point here, the shape `include`
|
|
317
|
+
* already uses (`include: { author: { include: { tags: true } } }`). One idiom
|
|
318
|
+
* in the call rather than two.
|
|
319
|
+
*
|
|
320
|
+
* Repeating the keyword also leaves a slot: a future `include: { author: { pick:
|
|
321
|
+
* {…} } }` has somewhere to live. A bare child map — the first cut of this —
|
|
322
|
+
* had nowhere to put it and, worse, could not express more than one level, so
|
|
323
|
+
* `image.variants.thumbnail` was reachable on the wire and untypeable here.
|
|
324
|
+
*
|
|
325
|
+
* `D` bounds the recursion for the same reason `IncludeDepth` does: composites
|
|
326
|
+
* can reference each other, and an uncapped recursive mapped type over that is
|
|
327
|
+
* an immediate "Type instantiation is excessively deep and possibly infinite".
|
|
328
|
+
*/
|
|
329
|
+
export type FoirSelector<T, Verb extends 'pick' | 'omit', D extends number = SelectorDepth> = {
|
|
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]>;
|
|
334
|
+
} : true;
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* How deep a selector may nest. Three, matching `IncludeDepth`, and comfortably
|
|
338
|
+
* past what real composites do — `Image.variants.thumbnail` is the deepest
|
|
339
|
+
* shape on any live model at two.
|
|
340
|
+
*/
|
|
341
|
+
type SelectorDepth = 3;
|
|
342
|
+
/** The selector actually present under `verb`, or `never` when absent or an array. */
|
|
343
|
+
type SelectorOf<Q, Verb extends 'pick' | 'omit'> = Q extends Record<Verb, infer S> ? S extends readonly unknown[] ? never : S : never;
|
|
344
|
+
/**
|
|
345
|
+
* Apply a nested `pick` selector. `_id` survives regardless, for the same
|
|
346
|
+
* reason it does in the flat form — the server always emits it.
|
|
347
|
+
*/
|
|
348
|
+
type ApplyPickSelector<F, S> = {
|
|
349
|
+
[K in Extract<keyof S, keyof F> | Extract<'_id', keyof F>]: K extends keyof S ? NarrowPick<F[K], S[K]> : F[K];
|
|
350
|
+
};
|
|
351
|
+
/**
|
|
352
|
+
* The same walk one level in. Separate from the root because `_id` is forced at
|
|
353
|
+
* the ROOT only — a composite has no `_id`, and adding one would describe a
|
|
354
|
+
* response shape the server never sends.
|
|
355
|
+
*/
|
|
356
|
+
type PickSelectorDeep<F, S> = {
|
|
357
|
+
[K in Extract<keyof S, keyof F>]: NarrowPick<F[K], S[K]>;
|
|
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;
|
|
379
|
+
/**
|
|
380
|
+
* Apply a nested `omit` selector.
|
|
381
|
+
*
|
|
382
|
+
* `true` at a key drops the whole field. An object at a key drops only the
|
|
383
|
+
* named children, which is what makes `omit: { description: { markdown: true } }`
|
|
384
|
+
* able to shed one richtext encoding of three without losing the field.
|
|
385
|
+
*/
|
|
386
|
+
type ApplyOmitSelector<F, S> = {
|
|
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];
|
|
388
|
+
};
|
|
389
|
+
/**
|
|
390
|
+
* The `include` argument for a model: every relation key, opt-in, optionally
|
|
391
|
+
* carrying a nested `include` for the relation's own relations.
|
|
392
|
+
*
|
|
393
|
+
* `true` only (not `boolean`) — a `false` would go on the wire and the server
|
|
394
|
+
* validates `include` keys strictly, so a conditional include has to be built
|
|
395
|
+
* by conditionally spreading the key, not by passing `false`.
|
|
396
|
+
*/
|
|
397
|
+
export type FoirInclude<S extends FoirSchemaDefinition, M extends FoirModelDefinition, D extends number = IncludeDepth> = {
|
|
398
|
+
[K in keyof M['relations']]?: D extends 0 ? true : true | {
|
|
399
|
+
include?: FoirInclude<S, TargetModel<S, M['relations'][K]>, Decrement[D]>;
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
/** The relation keys a call asked for, shaped by list/nullable and nesting. */
|
|
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>>;
|
|
405
|
+
};
|
|
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;
|
|
407
|
+
/**
|
|
408
|
+
* A model as this call will receive it: the complete scalar shape, minus
|
|
409
|
+
* `omit`, plus the `include`d relations.
|
|
410
|
+
*
|
|
411
|
+
* When neither was passed this resolves to `M['fields']` BY IDENTITY — not a
|
|
412
|
+
* structurally-equal copy — so `foir.tilly_block.findUnique({ id })` hovers as
|
|
413
|
+
* `TillyBlock` rather than as an anonymous 40-property object literal.
|
|
414
|
+
*
|
|
415
|
+
* `include` produces an INTERSECTION rather than a flattened object, on
|
|
416
|
+
* purpose. A `{ [K in keyof T]: T[K] }` flattening pass would read a little
|
|
417
|
+
* tidier in a hover, but `TillyBlock & { noteId: TillyNote | null }` already
|
|
418
|
+
* reads well and names the generated interface — and, more to the point, it
|
|
419
|
+
* means the only mapped type anywhere near a result is `Omit`. One less thing
|
|
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.
|
|
427
|
+
*/
|
|
428
|
+
export type FoirRecordResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, Q, D extends number = IncludeDepth, Depth extends number = DepthOf<Q>> = Q extends {
|
|
429
|
+
include: infer I;
|
|
430
|
+
} ? ApplySelection<M, Q, Depth> & IncludedRelations<S, M, I, D, Depth> : ApplySelection<M, Q, Depth>;
|
|
431
|
+
/** Apply an op's return wrapper to the shaped record. */
|
|
432
|
+
export type FoirOpResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, O extends FoirOpDefinition, Q> = O['returns'] extends {
|
|
433
|
+
kind: 'opaque';
|
|
434
|
+
type: infer T;
|
|
435
|
+
} ? T : O['returns'] extends {
|
|
436
|
+
kind: 'connection';
|
|
437
|
+
} ? FoirConnection<FoirRecordResult<S, M, Q>> : O['returns'] extends {
|
|
438
|
+
kind: 'list';
|
|
439
|
+
nullable: infer N;
|
|
440
|
+
} ? N extends true ? Array<FoirRecordResult<S, M, Q>> | null : Array<FoirRecordResult<S, M, Q>> : O['returns'] extends {
|
|
441
|
+
kind: 'record';
|
|
442
|
+
nullable: infer N;
|
|
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
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* What a model op accepts: the op's own args with `include`/`omit` as siblings.
|
|
469
|
+
* They are split back apart onto the wire (`{ model, op, args, include, omit }`)
|
|
470
|
+
* — `include`/`omit` are selection, not arguments — but at the call site a
|
|
471
|
+
* single flat object is what reads well.
|
|
472
|
+
*
|
|
473
|
+
* An `opaque` op returns something that is not the model, so selection does not
|
|
474
|
+
* apply and is not offered.
|
|
475
|
+
*/
|
|
476
|
+
export type FoirQuery<S extends FoirSchemaDefinition, M extends FoirModelDefinition, O extends FoirOpDefinition> = O['returns'] extends {
|
|
477
|
+
kind: 'opaque';
|
|
478
|
+
} ? O['args'] : O['args'] & FoirDepthOption & ({
|
|
479
|
+
include?: FoirInclude<S, M>;
|
|
480
|
+
omit?: readonly FoirOmittableKey<M>[] | FoirSelector<M['fields'], 'omit'>;
|
|
481
|
+
/**
|
|
482
|
+
* Mutually exclusive with `omit`, mirroring the server's 400. Typed as
|
|
483
|
+
* `never?` on this branch rather than left out, because omitting the key
|
|
484
|
+
* would let an object carrying BOTH satisfy this branch and reach the
|
|
485
|
+
* wire — the excess-property check does not fire on a value inferred
|
|
486
|
+
* into a generic parameter, which is the same hole `NoExtraKeys` exists
|
|
487
|
+
* to close.
|
|
488
|
+
*/
|
|
489
|
+
pick?: never;
|
|
490
|
+
} | {
|
|
491
|
+
include?: FoirInclude<S, M>;
|
|
492
|
+
pick?: readonly FoirPickableKey<M>[] | FoirSelector<M['fields'], 'pick'>;
|
|
493
|
+
omit?: never;
|
|
494
|
+
});
|
|
495
|
+
/**
|
|
496
|
+
* Reject keys the op does not declare.
|
|
497
|
+
*
|
|
498
|
+
* TypeScript's excess-property check does NOT fire when an object literal is
|
|
499
|
+
* inferred into a generic type parameter — `Q` is inferred as the literal's own
|
|
500
|
+
* shape and then merely has to be *assignable* to its constraint, which an
|
|
501
|
+
* object with extra keys is. So `findUnique({ id, first: 10 })` compiled
|
|
502
|
+
* silently and sent `first` to a server that does not take it. Typing every
|
|
503
|
+
* undeclared key as `never` puts the check back: `first: 10` is not assignable
|
|
504
|
+
* to `never`, and the error lands on the offending key.
|
|
505
|
+
*
|
|
506
|
+
* Pinned by "rejects an arg the op does not declare" in
|
|
507
|
+
* `__tests__/selection.test-d.ts`.
|
|
508
|
+
*/
|
|
509
|
+
type NoExtraKeys<Q, T> = Record<Exclude<keyof Q, keyof T>, never>;
|
|
510
|
+
/**
|
|
511
|
+
* One model operation.
|
|
512
|
+
*
|
|
513
|
+
* `const Q` is what makes `omit: ['bodyRichtext']` infer as the tuple
|
|
514
|
+
* `readonly ['bodyRichtext']` instead of `string[]`; without it `Omit` would
|
|
515
|
+
* have nothing to remove. It also pins `include: { author: true }` to the
|
|
516
|
+
* literal `true`.
|
|
517
|
+
*/
|
|
518
|
+
export type FoirModelOperation<S extends FoirSchemaDefinition, M extends FoirModelDefinition, O extends FoirOpDefinition> = <const Q extends FoirQuery<S, M, O>>(query?: Q & NoExtraKeys<Q, FoirQuery<S, M, O>>) => Promise<FoirOpResult<S, M, O, Q>>;
|
|
519
|
+
/**
|
|
520
|
+
* One namespace operation. No model behind it, so no `include`/`omit` and the
|
|
521
|
+
* generated result type is used verbatim.
|
|
522
|
+
*/
|
|
523
|
+
export type FoirNamespaceOperation<O extends FoirNamespaceOpDefinition> = (args?: O['args']) => Promise<O['result']>;
|
|
524
|
+
export {};
|