@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.
Files changed (107) hide show
  1. package/CHANGELOG.md +1126 -0
  2. package/README.md +94 -0
  3. package/dist/auth/auth.d.ts +243 -0
  4. package/dist/auth/hosted.d.ts +40 -0
  5. package/dist/auth/index.d.ts +21 -0
  6. package/dist/auth/operations.d.ts +144 -0
  7. package/dist/auth/ops.d.ts +99 -0
  8. package/dist/auth/session-client.d.ts +76 -0
  9. package/dist/auth-session.js +137 -0
  10. package/dist/auth.js +973 -92
  11. package/dist/chunk-IGNLWT3H.js +35 -0
  12. package/dist/chunk-OHYVKTGB.js +45 -0
  13. package/dist/chunk-TAL5H6VA.js +39 -0
  14. package/dist/client/platform-client.d.ts +29 -0
  15. package/dist/client.js +275 -37
  16. package/dist/codegen/block-registry.d.ts +13 -23
  17. package/dist/codegen/block-registry.js +3 -31
  18. package/dist/codegen/block-scaffold.d.ts +12 -25
  19. package/dist/codegen/block-scaffold.js +3 -41
  20. package/dist/codegen/index.d.ts +12 -61
  21. package/dist/codegen/index.js +7 -11
  22. package/dist/codegen/item-registry.d.ts +28 -0
  23. package/dist/codegen/item-registry.js +7 -0
  24. package/dist/codegen/item-scaffold.d.ts +29 -0
  25. package/dist/codegen/item-scaffold.js +7 -0
  26. package/dist/codegen/preset.d.ts +67 -0
  27. package/dist/codegen/shared.d.ts +36 -0
  28. package/dist/components/EditorProvider.d.ts +36 -0
  29. package/dist/content/content-renderer.d.ts +113 -0
  30. package/dist/content/index.d.ts +18 -0
  31. package/dist/content/richtext.d.ts +118 -0
  32. package/dist/content.js +227 -80
  33. package/dist/data/builtins.d.ts +506 -0
  34. package/dist/data/client.d.ts +166 -0
  35. package/dist/data/document.d.ts +251 -0
  36. package/dist/data/index.d.ts +73 -0
  37. package/dist/data/manifest.d.ts +424 -0
  38. package/dist/data/schema.d.ts +313 -0
  39. package/dist/data/selection.d.ts +524 -0
  40. package/dist/data/subjects.d.ts +221 -0
  41. package/dist/data/subscription-document.d.ts +75 -0
  42. package/dist/data/subscriptions.d.ts +181 -0
  43. package/dist/data/transport.d.ts +325 -0
  44. package/dist/data/ws-transport.d.ts +197 -0
  45. package/dist/data.js +1683 -0
  46. package/dist/graphql/cdn-route.d.ts +26 -0
  47. package/dist/graphql/client.d.ts +30 -0
  48. package/dist/graphql/content-client.d.ts +66 -0
  49. package/dist/graphql/foir-request.d.ts +47 -0
  50. package/dist/hooks/useAutoResize.d.ts +22 -0
  51. package/dist/hooks/useEditor.d.ts +16 -0
  52. package/dist/index.d.ts +10 -394
  53. package/dist/media/BlurhashCanvas.d.ts +10 -0
  54. package/dist/media/ProgressiveMedia.d.ts +56 -0
  55. package/dist/media/index.d.ts +18 -0
  56. package/dist/media/media.d.ts +139 -0
  57. package/dist/media/screen-capture.d.ts +81 -0
  58. package/dist/media/useProgressiveLoad.d.ts +23 -0
  59. package/dist/media.js +102 -0
  60. package/dist/next/cookieAdapter.d.ts +26 -0
  61. package/dist/next/index.d.ts +15 -0
  62. package/dist/next/middleware.d.ts +49 -0
  63. package/dist/next/session.d.ts +53 -0
  64. package/dist/next.js +763 -78
  65. package/dist/protocol/constants.d.ts +6 -0
  66. package/dist/protocol/messages.d.ts +155 -0
  67. package/dist/protocol/validation.d.ts +8 -0
  68. package/dist/push/index.d.ts +14 -0
  69. package/dist/push/operations.d.ts +22 -0
  70. package/dist/push/web-push.d.ts +39 -0
  71. package/dist/push.js +892 -78
  72. package/dist/react/FoirFeedback.d.ts +62 -0
  73. package/dist/react/FoirProvider.d.ts +116 -0
  74. package/dist/react/OAuthButtons.d.ts +20 -0
  75. package/dist/react/SignIn.d.ts +22 -0
  76. package/dist/react/UserButton.d.ts +13 -0
  77. package/dist/react/feedback-notes.d.ts +101 -0
  78. package/dist/react/index.d.ts +25 -0
  79. package/dist/react/localStorageAdapter.d.ts +10 -0
  80. package/dist/react.js +1373 -91
  81. package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
  82. package/dist/realtime.js +1 -1
  83. package/dist/server/callback-client.d.ts +92 -0
  84. package/dist/server/events-client.d.ts +279 -0
  85. package/dist/server/index.d.ts +12 -0
  86. package/dist/server/operation-types.d.ts +134 -0
  87. package/dist/server/require-scoped-token.d.ts +76 -0
  88. package/dist/server/server-client.d.ts +34 -0
  89. package/dist/server/types.d.ts +54 -0
  90. package/dist/server/verify-scoped-token.d.ts +100 -0
  91. package/dist/server/webhook-helpers.d.ts +36 -0
  92. package/dist/server.js +298 -0
  93. package/dist/transport/foir-http.d.ts +234 -0
  94. package/dist/transport/persisted-query.d.ts +68 -0
  95. package/dist/transport/retry.d.ts +61 -0
  96. package/dist/types/index.d.ts +124 -0
  97. package/package.json +35 -13
  98. package/dist/auth.d.ts +0 -382
  99. package/dist/chunk-ADO5FJNM.js +0 -30
  100. package/dist/client.d.ts +0 -135
  101. package/dist/content.d.ts +0 -96
  102. package/dist/media.d.ts +0 -234
  103. package/dist/next.d.ts +0 -178
  104. package/dist/push.d.ts +0 -109
  105. package/dist/react.d.ts +0 -319
  106. package/dist/server.d.ts +0 -534
  107. 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 {};