@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,506 @@
1
+ /**
2
+ * The platform-owned types `foir types` does NOT generate.
3
+ *
4
+ * Two closed sets live here, and they are here rather than in the generated
5
+ * file for the same reason: they are identical in every project, so generating
6
+ * them per project would emit the same 200 lines into every consumer's repo and
7
+ * give four projects four chances to disagree about `ImageValue`.
8
+ *
9
+ * 1. **Value objects.** `GET /v1/manifest` reports a scalar's `type` as a
10
+ * GraphQL named type, and several of the names it can use are
11
+ * platform-owned objects rather than leaves: `Image`, `Video`, `FileValue`,
12
+ * `Richtext`, `GeoPoint`, `Reference`, `CollabInfo`. They are structurally
13
+ * nested but behave as leaves — always present, never lazily hydrated,
14
+ * never `include`-able.
15
+ *
16
+ * 2. **Primitive members.** A flexible field's union members are *model keys*,
17
+ * except for six lowercase tokens — `richtext`, `image`, `video`, `file`,
18
+ * `reference`, `geopoint` — which are members of the real GraphQL union but
19
+ * have **no entry in `models`**. A generator that resolved every member to
20
+ * `models[key]` would skip them (an inexhaustive union, silently wrong at
21
+ * runtime) or emit `never`.
22
+ *
23
+ * # These are ONE set, not two (TTS-1391)
24
+ *
25
+ * They used to be two: a value object for a plain field, and a separate
26
+ * `<Name>Block` wrapper for the same thing inside a flexible field. TTS-1391
27
+ * removed the wrapper — an item IS its value — so a plain `image` field and an
28
+ * `image` inside a flexible field are the SAME GraphQL type, `Image`. There is
29
+ * no `ImageValue` in a real schema and never will be again; the only difference
30
+ * between the two positions is whether `__typename` was selected.
31
+ *
32
+ * ---------------------------------------------------------------------------
33
+ * The obligation that comes with a static table
34
+ * ---------------------------------------------------------------------------
35
+ *
36
+ * This IS a mirror of a platform-side table, and mirrors drift. What keeps it
37
+ * honest is that the generator refuses to guess: a union member key that is
38
+ * neither in `models` nor in this table makes `foir types` fail loudly naming
39
+ * the member, rather than emitting a union that silently lacks a branch. So the
40
+ * failure mode of drift is a red build with the missing name in it.
41
+ *
42
+ * `apps/cli/src/lib/__tests__/types-codegen.test.ts` asserts that against both
43
+ * committed live-captured manifests.
44
+ *
45
+ * Field shapes verified against deployed dev's introspection on 2026-07-28 and
46
+ * against `api-public/internal/schema/field_mapping.go`.
47
+ */
48
+ import type { FoirConnection } from './schema';
49
+ /** A `JSON` scalar. Deliberately not `any`: reading through it needs a check. */
50
+ export type FoirJsonValue = string | number | boolean | null | FoirJsonValue[] | {
51
+ [key: string]: FoirJsonValue;
52
+ };
53
+ export type ImageFormat = 'WEBP' | 'AVIF' | 'JPEG' | 'PNG';
54
+ /** One rendition of an image at a named tier, in a single format. */
55
+ export interface ImageVariant {
56
+ size: string;
57
+ format: ImageFormat;
58
+ url: string;
59
+ width: number | null;
60
+ height: number | null;
61
+ }
62
+ /**
63
+ * The curated tier set for one format. `_2xl`/`_3xl`/`_4xl` carry the
64
+ * underscore because a GraphQL identifier cannot start with a digit.
65
+ */
66
+ export interface ImageVariantSet {
67
+ thumbnail: ImageVariant | null;
68
+ small: ImageVariant | null;
69
+ medium: ImageVariant | null;
70
+ large: ImageVariant | null;
71
+ xlarge: ImageVariant | null;
72
+ _2xl: ImageVariant | null;
73
+ _3xl: ImageVariant | null;
74
+ _4xl: ImageVariant | null;
75
+ }
76
+ export interface Image {
77
+ /**
78
+ * `String` on the wire, not `String!`.
79
+ *
80
+ * It was non-null here and nullable on the flexible-field member until
81
+ * TTS-1391, which is a §5.3.2 response-shape conflict the moment both are one
82
+ * type. The platform relaxed rather than tightened, because nullable is the
83
+ * honest form — its own public `File` asset type already declares
84
+ * `url: String` — so this follows.
85
+ */
86
+ __typename?: 'Image';
87
+ url: string | null;
88
+ width: number | null;
89
+ height: number | null;
90
+ alt: string | null;
91
+ blurhash: string | null;
92
+ dominantColor: string | null;
93
+ mimeType: string | null;
94
+ fileSize: number | null;
95
+ focalPoint: FoirJsonValue | null;
96
+ crop: FoirJsonValue | null;
97
+ variants: ImageVariantSet | null;
98
+ /** WebP tier set (universal support). Pair with `avif` in a `<picture>`. */
99
+ webp: ImageVariantSet | null;
100
+ /** AVIF tier set (smaller, narrower support). The preferred `<picture>` source. */
101
+ avif: ImageVariantSet | null;
102
+ }
103
+ export interface Video {
104
+ __typename?: 'Video';
105
+ url: string | null;
106
+ thumbnailUrl: string | null;
107
+ previewUrl: string | null;
108
+ hlsManifestUrl: string | null;
109
+ width: number | null;
110
+ height: number | null;
111
+ duration: number | null;
112
+ blurhash: string | null;
113
+ dominantColor: string | null;
114
+ mimeType: string | null;
115
+ fileSize: number | null;
116
+ }
117
+ export interface FileValue {
118
+ __typename?: 'FileValue';
119
+ url: string | null;
120
+ filename: string | null;
121
+ mimeType: string | null;
122
+ size: number | null;
123
+ }
124
+ export interface GeoPoint {
125
+ __typename?: 'GeoPoint';
126
+ lat: number | null;
127
+ lng: number | null;
128
+ }
129
+ export interface Richtext {
130
+ __typename?: 'Richtext';
131
+ html: string | null;
132
+ json: FoirJsonValue | null;
133
+ markdown: string | null;
134
+ }
135
+ /**
136
+ * A reference whose target model is not pinned by the field's declaration.
137
+ *
138
+ * A reference field that declares ONE `referenceTypes` entry resolves to that
139
+ * model's own type, and one declaring several resolves to a `<Members>Ref`
140
+ * union — so this type is what an undeclared reference resolves to, and what a
141
+ * flexible field's `reference` member is.
142
+ *
143
+ * # What makes it followable
144
+ *
145
+ * `_targetModelKey` names the model and `_targetId` OR `_targetNaturalKey`
146
+ * identifies the row within it: `foir[_targetModelKey].findUnique({ where: { _id:
147
+ * _targetId } })`. Both identifier shapes are live — on the dev database 101 of
148
+ * 193 stored reference values carry an id and 92 carry a natural key and no id
149
+ * — so a consumer must handle either.
150
+ *
151
+ * `_id` and `_type` are NOT here, and their absence is the TTS-1391 correction
152
+ * rather than an omission. They were the wrapper's own metadata: `_id` was the
153
+ * ITEM's id (never the target's, which is why `_targetId` exists separately) and
154
+ * `_type` was the union discriminator, reading `"reference"` on every reference
155
+ * ever stored. The wrapper is gone, so neither field is in the schema, and a
156
+ * consumer selecting them got `undefined` while the types promised a string.
157
+ */
158
+ export interface Reference<TRecord = FoirUnexpandedRecord> {
159
+ __typename?: 'Reference';
160
+ /** The model to fetch the target from. Null when the target no longer exists. */
161
+ _targetModelKey: string | null;
162
+ /** The target's id, when the reference stored one. */
163
+ _targetId: string | null;
164
+ /** The target's natural key, when the reference names its target that way. */
165
+ _targetNaturalKey: string | null;
166
+ /**
167
+ * The resolved target, over the project's `Record` union — every model in the
168
+ * project. Left as a type parameter so the generated file can name that
169
+ * union; the default is the honest answer for a consumer who has not narrowed
170
+ * it.
171
+ */
172
+ record: TRecord | null;
173
+ /** A stored SPELLING of the model. Read as an input to `_targetModelKey`. */
174
+ _schema: string | null;
175
+ naturalKey: string | null;
176
+ _preview: FoirJsonValue | null;
177
+ }
178
+ /** Inline media embedded in a content field — polymorphic per `mediaType`. */
179
+ export interface MediaValue {
180
+ mediaType: string | null;
181
+ url: string | null;
182
+ mimeType: string | null;
183
+ fileId: string | null;
184
+ fileSize: number | null;
185
+ filename: string | null;
186
+ width: number | null;
187
+ height: number | null;
188
+ blurhash: string | null;
189
+ dominantColor: string | null;
190
+ duration: number | null;
191
+ hlsManifestUrl: string | null;
192
+ previewUrl: string | null;
193
+ thumbnailUrl: string | null;
194
+ variants: FoirJsonValue | null;
195
+ crop: FoirJsonValue | null;
196
+ focalPoint: FoirJsonValue | null;
197
+ }
198
+ /** Collaborative-editing discovery. */
199
+ export interface CollabInfo {
200
+ enabled: boolean;
201
+ wsUrl: string;
202
+ roomName: string;
203
+ }
204
+ /**
205
+ * `Image`, `Video`, `FileValue`, `Richtext`, `GeoPoint` and `Reference` above
206
+ * ARE the members. There is no separate item type, because an item is its
207
+ * value: `... on Image { url }` means the same thing inside a flexible field
208
+ * and at a plain `image` field, and one fragment reads it in both positions.
209
+ *
210
+ * `__typename` is optional on each, matching what graphql-codegen emits: it is
211
+ * present whenever selected, and the op compiler always selects it on a union's
212
+ * own selection set — it is the one field no `omit` path can name. Narrowing on
213
+ * it is exact regardless, because the literal types are disjoint.
214
+ *
215
+ * `_type` was the second discriminant and is gone with the envelope. It was
216
+ * never protected from `omit` anyway, so `__typename` was always the one to
217
+ * prefer.
218
+ *
219
+ * The three wrappers below have no replacement, because the things they
220
+ * described no longer exist:
221
+ *
222
+ * - `MediaBlock` — `media` stopped being a primitive at TTS-1391. It was
223
+ * never in the proto FieldType enum, and it is the one primitive name a
224
+ * project had also used as a model key.
225
+ * - `TextBlock` — TTS-1406 removed it. GraphQL requires a union member to be
226
+ * an object type and a text item's value is a bare scalar, which is the
227
+ * whole reason a wrapper existed. An inline model with a text field is how
228
+ * a text slot is authored.
229
+ * - `FlexibleBlock` — there is no fallback member. An item whose `_type`
230
+ * names nothing is nulled out of its list and reported, which is what a
231
+ * stored null already read as.
232
+ */
233
+ /** @deprecated Renamed to {@link Image}. The wrapper is gone; this IS the image. */
234
+ export type ImageBlock = Image;
235
+ /** @deprecated Renamed to {@link Video}. */
236
+ export type VideoBlock = Video;
237
+ /** @deprecated Renamed to {@link FileValue}. */
238
+ export type FileBlock = FileValue;
239
+ /** @deprecated Renamed to {@link Richtext}. */
240
+ export type RichtextBlock = Richtext;
241
+ /** @deprecated Renamed to {@link GeoPoint}. */
242
+ export type GeoPointValue = GeoPoint;
243
+ /** @deprecated Renamed to {@link Image}. */
244
+ export type ImageValue = Image;
245
+ /** @deprecated Renamed to {@link Video}. */
246
+ export type VideoValue = Video;
247
+ /** @deprecated Renamed to {@link Richtext}. */
248
+ export type RichtextValue = Richtext;
249
+ /** @deprecated Renamed to {@link Reference}. */
250
+ export type ReferenceValue<TRecord = FoirUnexpandedRecord> = Reference<TRecord>;
251
+ /** @deprecated Renamed to {@link Reference}. */
252
+ export type ReferenceBlock<TRecord = FoirUnexpandedRecord> = Reference<TRecord>;
253
+ /**
254
+ * What a nested union or a re-entered object resolves to.
255
+ *
256
+ * The op compiler enforces a termination rule — *never expand a type, object or
257
+ * union, that is already on the current path; degrade it to `__typename`
258
+ * instead* — because `Block` contains members that contain `Block`, and a
259
+ * `tilly_page.blocks → TillyPageBlock.blocks` cycle is reachable in two hops
260
+ * with no `include` and no caller input at all. At a degraded position the
261
+ * response carries `__typename` and nothing else, so that is what the generated
262
+ * type says.
263
+ *
264
+ * # Reading past one
265
+ *
266
+ * A stopping point in front of a UNION can be spent a level on:
267
+ * `foir.product.findMany({ depth: 2 })` expands one more, and the response then
268
+ * carries that union's members here rather than `__typename` alone. A level
269
+ * costs one more expansion at each stopping point inside the expansion — so it
270
+ * compounds where there are several, and costs nothing per union MEMBER — and
271
+ * the call is refused client-side, with the arithmetic, when it would cross the
272
+ * server's field ceiling. See `FoirNamespaceCall.depth`.
273
+ *
274
+ * The TYPE follows the level. A generated composite on the path to a stopping
275
+ * point takes a `<D>` level parameter (see `FoirDepthLevel`), so at `depth: 2`
276
+ * the position resolves to the expanded union — `<Field>Blocks`, the type
277
+ * exported beside the `<Field>BlocksDegraded` named here — rather than to this
278
+ * one. The caller narrows on `__typename` and reads the member's own field; no
279
+ * cast, and no hand-written widening.
280
+ *
281
+ * `D` defaults to `1`, so a call passing no `depth` reaches THIS type at the
282
+ * stopping point exactly as it always did.
283
+ *
284
+ * A stopping point that is a composite CYCLE rather than a union — `menu.items
285
+ * .children` — has no union to expand and stays `__typename` at every depth.
286
+ */
287
+ export interface FoirDegraded<TTypename extends string = string> {
288
+ __typename: TTypename;
289
+ }
290
+ /**
291
+ * The union levels the generated types describe — the type-level half of
292
+ * `depth`.
293
+ *
294
+ * A generated composite on the path to a stopping point takes one of these as
295
+ * a parameter, defaulted to `1`, and the model's shape at each level is named
296
+ * under `FoirModelDefinition.atDepth`. `1` is the complete-by-default document
297
+ * and is what every existing reference to a generated interface resolves to,
298
+ * which is why adding the parameter changed no result type.
299
+ *
300
+ * Four because depth is a budget against the server's 10,000-field ceiling and
301
+ * four is past what a generated module usefully describes: on the live dev
302
+ * project `product.findUnique` measures 318 fields at the default, 468 at 2 and
303
+ * 618 at 3. Those levels all fit since TTS-1448 — they read 1,203 / 4,308 /
304
+ * 13,623 when a union position was charged for every member — so the bound here
305
+ * is about how much generated type surface is worth emitting, not about what
306
+ * the server will run. A call
307
+ * asking for more is not an error: `atDepth` simply has no entry, the result
308
+ * falls back to the default shape, and the caller narrows by hand exactly as
309
+ * they did before. Under-promising is the safe direction, and the wire is
310
+ * unaffected — the printer's budget is a runtime number with no cap here.
311
+ */
312
+ export type FoirDepthLevel = 1 | 2 | 3 | 4;
313
+ /**
314
+ * `FoirDepthBelow[3] === 2` — one union level spent.
315
+ *
316
+ * Mirrors the printer's `depth - 1` at an expanded stopping point
317
+ * (`document.ts`, `printFields`), and lives beside `FoirDepthLevel` so the two
318
+ * cannot disagree about how many levels exist.
319
+ */
320
+ export type FoirDepthBelow = [never, never, 1, 2, 3];
321
+ /** A `Record`-union value the compiler did not expand. See `FoirDegraded`. */
322
+ export type FoirUnexpandedRecord = FoirDegraded;
323
+ /**
324
+ * The results of the ops that do NOT return the model.
325
+ *
326
+ * `foir types` classifies these as `opaque`, so the client offers no
327
+ * `include` / `omit` for them and uses the type below verbatim. They are here
328
+ * for the same reason the value objects are: platform-owned, fixed, and
329
+ * identical in every project.
330
+ */
331
+ export type FoirVisibility = 'PRIVATE' | 'PUBLIC';
332
+ export type FoirAccessLevel = 'READ' | 'WRITE' | 'DELETE';
333
+ export type FoirAccessSource = 'PUBLIC' | 'OWNER' | 'GRANT' | 'ROLE';
334
+ export type FoirGranteeKind = 'CUSTOMER' | 'ADMIN';
335
+ /** `myAccess` — this principal's effective access to one record. */
336
+ export interface FoirMyAccess {
337
+ access: FoirAccessLevel;
338
+ source: FoirAccessSource;
339
+ }
340
+ export interface FoirAccessGrant {
341
+ access: FoirAccessLevel;
342
+ createdAt: string;
343
+ granteeId: string;
344
+ granteeKind: FoirGranteeKind;
345
+ }
346
+ export interface FoirAccessor {
347
+ access: FoirAccessLevel;
348
+ granteeId: string;
349
+ granteeKind: FoirGranteeKind;
350
+ source: FoirAccessSource;
351
+ }
352
+ /** `access` — the full ACL of one record. */
353
+ export interface FoirResourceAccess {
354
+ accessors: FoirAccessor[];
355
+ grants: FoirAccessGrant[];
356
+ public: boolean;
357
+ visibility: FoirVisibility;
358
+ }
359
+ /** `updateMany` — how many rows moved, and which. */
360
+ export interface FoirUpdateManyPayload {
361
+ count: number;
362
+ ids: string[];
363
+ }
364
+ /**
365
+ * What a note is ABOUT — the closed set the platform's write path enforces.
366
+ *
367
+ * On the wire it is `String!`, not a GraphQL enum, so the manifest publishes no
368
+ * `enumValues` for it and `foir types` cannot generate this union. It is here
369
+ * for the same reason the value objects are: platform-owned, identical in every
370
+ * project, and otherwise hand-written once per consumer.
371
+ *
372
+ * It is never a MODEL KEY. Model keys are the project's to choose, so a project
373
+ * with a model keyed `file` would make one value mean two things; a note on a
374
+ * record is `'record'` plus the model key on `subjectModelKey`.
375
+ *
376
+ * This mirrors a platform-side set, and the obligation that comes with a mirror
377
+ * (see the file header) applies: a kind added there widens this union in a
378
+ * minor release, and until then a value outside it is a row no writer could
379
+ * have produced.
380
+ */
381
+ export type FoirNoteSubjectType = 'record' | 'file' | 'model' | 'operation' | 'schedule' | 'customer';
382
+ /**
383
+ * One file the customer submitted with their note.
384
+ *
385
+ * The file's own facts, and nothing about the platform's handling of it: when
386
+ * the link row was written and which retention clock it starts are not the
387
+ * author's business. What is here is enough to render "you sent this".
388
+ */
389
+ export interface FoirCustomerNoteAttachment {
390
+ /** The file's id — the same one `createMyNote` was given. */
391
+ fileId: string;
392
+ filename: string;
393
+ mimeType: string;
394
+ /** Size in bytes, as stored. Authoritative; not the number the uploader declared. */
395
+ size: number;
396
+ url: string;
397
+ }
398
+ /**
399
+ * One note on the customer plane — `foir.ns.notes.myNotes` and
400
+ * `foir.ns.notes.createMyNote`.
401
+ *
402
+ * Deliberately narrower than the note your team sees in the dashboard. There is
403
+ * no visibility, no resolution note, no mentions and no metadata: those are the
404
+ * project's view of a thread, not the author's, and the customer-facing type
405
+ * does not merely omit them from the document — the surface does not carry them.
406
+ */
407
+ export interface FoirCustomerNote {
408
+ id: string;
409
+ /** The note's prose. */
410
+ body: string | null;
411
+ subjectType: FoirNoteSubjectType;
412
+ subjectId: string;
413
+ /** The record's model key. Present only on a `record` subject. */
414
+ subjectModelKey: string | null;
415
+ /** Set when this note is a reply within a thread. */
416
+ parentNoteId: string | null;
417
+ threadRootId: string | null;
418
+ isResolved: boolean;
419
+ /**
420
+ * The files submitted with this note. Empty when there are none.
421
+ *
422
+ * The one place the customer surface is WIDER than "the author's own prose",
423
+ * and deliberately: an author who attached a screenshot has to be able to see
424
+ * that it arrived, or the only readings left to them are that it failed
425
+ * silently or that it was taken away.
426
+ */
427
+ attachments: FoirCustomerNoteAttachment[];
428
+ /** ISO-8601. */
429
+ createdAt: string;
430
+ }
431
+ /**
432
+ * What a note surface has to know about the project before it renders anything
433
+ * — `foir.ns.notes.noteConfig`.
434
+ *
435
+ * Charged `notes:write` rather than `notes:read`, and refused to a caller with
436
+ * no customer session, because the only caller it is useful to is one that can
437
+ * submit. So a composer can read it as its single readiness probe: an answer
438
+ * means this visitor may write, and a refusal names which half is missing.
439
+ */
440
+ export interface FoirNoteConfig {
441
+ /**
442
+ * Whether this project's note surfaces may OFFER to capture a screenshot.
443
+ *
444
+ * It gates the offer, not the storing. Capture stays opt-in per submission —
445
+ * the browser's own picker is the consent gate and it cannot be bypassed.
446
+ */
447
+ screenshotsEnabled: boolean;
448
+ }
449
+ /** `myNotes` arguments. Forward-only cursor pagination, like every list read. */
450
+ export interface FoirMyNotesArgs {
451
+ first?: number;
452
+ after?: string;
453
+ /** Narrow to one kind of subject. */
454
+ subjectType?: FoirNoteSubjectType;
455
+ /** Narrow to one subject — a record id, a file id, a model key. */
456
+ subjectId?: string;
457
+ /** Read one thread. Returns only the notes in it this caller can see. */
458
+ threadRootId?: string;
459
+ }
460
+ /**
461
+ * `myNotes` — the notes this customer wrote, plus the replies your team
462
+ * deliberately sent them. Never another customer's, and never an operator's
463
+ * internal ones.
464
+ */
465
+ export type FoirMyNotesResult = FoirConnection<FoirCustomerNote>;
466
+ /** `createMyNote` arguments. */
467
+ export interface FoirCreateMyNoteArgs {
468
+ subjectType: FoirNoteSubjectType;
469
+ subjectId: string;
470
+ /** Required when `subjectType` is `'record'`, refused otherwise. */
471
+ subjectModelKey?: string;
472
+ body: string;
473
+ /** Reply within an existing thread. */
474
+ parentNoteId?: string;
475
+ /**
476
+ * Files to submit with this note, already uploaded through
477
+ * `createFileUpload` / `confirmFileUpload`.
478
+ *
479
+ * A file this caller cannot read is REFUSED and the note is not written. That
480
+ * is not a rough edge to code around: a create that accepted the note and
481
+ * dropped the attachment would leave the author believing they sent a
482
+ * screenshot nobody has.
483
+ */
484
+ attachmentFileIds?: string[];
485
+ }
486
+ export type FoirDeviceType = 'DESKTOP' | 'MOBILE' | 'TABLET';
487
+ export type FoirPlatformType = 'IOS' | 'ANDROID' | 'WEB';
488
+ /**
489
+ * The `contexts` argument on every typed read.
490
+ *
491
+ * **It is an object, not a list**, despite the plural name — `ResolveContextInput`
492
+ * on the wire, verified against deployed dev's introspection on both projects:
493
+ * `contexts: ResolveContextInput` with no LIST wrapper, four optional fields.
494
+ *
495
+ * Worth the paragraph because the plural reads like `string[]` and typing it
496
+ * that way compiles fine and then sends a body the server rejects. The
497
+ * `FoirSchemaDefinition` sketch in `schema.ts` had exactly that error, and it
498
+ * was found by the docs lane running the call rather than by anyone reading the
499
+ * contract.
500
+ */
501
+ export interface FoirResolveContext {
502
+ device?: FoirDeviceType | null;
503
+ locale?: string | null;
504
+ platform?: FoirPlatformType | null;
505
+ preview?: boolean | null;
506
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * `createFoirClient` — the typed data client.
3
+ *
4
+ * ```ts
5
+ * import { createFoirClient } from '@foir/sdk/data';
6
+ * import * as generated from './foir.generated';
7
+ *
8
+ * const foir = createFoirClient({
9
+ * apiUrl: process.env.PLATFORM_API_URL!,
10
+ * apiKey: process.env.PLATFORM_API_KEY!,
11
+ * generated,
12
+ * });
13
+ *
14
+ * const block = await foir.tilly_block.findUnique({
15
+ * id: 'a3pr8x0j10rigx8u6t1xthbr',
16
+ * include: { noteId: true },
17
+ * omit: ['aiSummary'],
18
+ * });
19
+ * ```
20
+ *
21
+ * One import and one call. `FoirSchema` is inferred from the module rather than
22
+ * restated, and the four runtime exports the client reads are read off it rather
23
+ * than named one at a time at every call site — see `resolveGeneratedConfig`
24
+ * below for why that indirection is worth having.
25
+ *
26
+ * The individual fields (`schemaVersion`, `selections`, `namespaceSelections`,
27
+ * `selectionUnions`) still work, and still win where both are given.
28
+ *
29
+ * The call is the same call it always was. What changed underneath is what goes
30
+ * on the wire: the client loads that op's selection tree, applies
31
+ * `omit`/`pick`/`include`, prints a GraphQL document, hashes it and sends the
32
+ * hash. See `document.ts` for the printer and `transport.ts` for the routing.
33
+ *
34
+ * ---------------------------------------------------------------------------
35
+ * Why this is a Proxy
36
+ * ---------------------------------------------------------------------------
37
+ *
38
+ * `FoirSchema` is a TYPE. `foir types` emits interfaces, and interfaces do not
39
+ * exist at runtime, so the client is handed the full shape of the API at
40
+ * compile time and nothing at all at run time. There is no model list to build
41
+ * an object from — `foir.tilly_block.findMany` has to be resolved when it is
42
+ * reached. A two-level `Proxy` does that in a few lines and costs one property
43
+ * lookup per call.
44
+ *
45
+ * ---------------------------------------------------------------------------
46
+ * Why `selections` is a config field and not a fetch
47
+ * ---------------------------------------------------------------------------
48
+ *
49
+ * The trees ARE runtime data — that is the one thing the generated module has to
50
+ * carry across the type/value boundary, alongside `foirSchemaVersion`. Passing
51
+ * them in costs nothing and is the intended path.
52
+ *
53
+ * When they are absent the client fetches `GET /v1/manifest` once, at first use,
54
+ * and reads the same trees off it. That is a FALLBACK and never the default,
55
+ * for the reason this file has always given: it puts a round trip in front of
56
+ * the first query on every cold edge invocation, to learn something the
57
+ * generated file already knows. What it buys is that `@foir/sdk/data` still
58
+ * works with zero codegen, which keeps the getting-started path honest.
59
+ *
60
+ * It has to stay reachable, and it must not be reachable by ACCIDENT. Those
61
+ * pull in opposite directions and the split is: a client built with no generated
62
+ * module and no `selections` has chosen the fallback, and says so once; a client
63
+ * built with a `generated` module that cannot serve the printer is refused where
64
+ * it was constructed. There is no input that quietly produces the round trip.
65
+ *
66
+ * ---------------------------------------------------------------------------
67
+ * Why namespaces live under `.ns`
68
+ * ---------------------------------------------------------------------------
69
+ *
70
+ * Since there is no runtime schema (above), the client cannot tell whether
71
+ * `foir.search` means a model or a namespace. So the addressing carries it:
72
+ * `foir.<model>.<op>()` addresses a model, `foir.ns.<namespace>.<op>()` a
73
+ * namespace. `ns` and `raw` are therefore reserved top-level names.
74
+ */
75
+ import type { FoirModelDefinition, FoirNamespaceOpDefinition, FoirOpDefinition, FoirSchemaDefinition } from './schema';
76
+ import type { FoirModelOperation, FoirNamespaceOperation } from './selection';
77
+ import { type FoirEnv } from '../transport/foir-http';
78
+ import { type FoirClientConfig, type FoirGeneratedModule } from './transport';
79
+ import { type FoirModelSubscriptions, type FoirNamespaceSubscriptions } from './subscriptions';
80
+ /**
81
+ * The ops available on one model for this credential, plus its subscriptions.
82
+ *
83
+ * `onChange` is on every model rather than gated on a generated flag, and that
84
+ * is correct rather than lazy: `recordChanged` charges
85
+ * `records:read:<modelKey>`, which is the SAME scope that put the model in
86
+ * `FoirSchema` at all. A model this credential cannot watch is a model it
87
+ * cannot read, so it is already absent from `S['models']` and the accessor
88
+ * cannot be reached.
89
+ */
90
+ export type FoirModelClient<S extends FoirSchemaDefinition, M extends FoirModelDefinition> = {
91
+ [OK in keyof M['ops']]: M['ops'][OK] extends FoirOpDefinition ? FoirModelOperation<S, M, M['ops'][OK]> : never;
92
+ } & FoirModelSubscriptions;
93
+ export type FoirNamespaceClient<N extends {
94
+ ops: object;
95
+ }> = {
96
+ [OK in keyof N['ops']]: N['ops'][OK] extends FoirNamespaceOpDefinition ? FoirNamespaceOperation<N['ops'][OK]> : never;
97
+ };
98
+ export interface FoirClientCore<S extends FoirSchemaDefinition> {
99
+ /**
100
+ * Non-model root namespaces (`search`, `customers`, `notifications`).
101
+ * Separate accessor because the addressing has to distinguish a model from a
102
+ * namespace and the client has no runtime schema to disambiguate with.
103
+ */
104
+ readonly ns: {
105
+ [NK in keyof S['namespaces']]: FoirNamespaceClient<S['namespaces'][NK]> & FoirNamespaceSubscriptions<NK>;
106
+ };
107
+ /**
108
+ * Untyped passthrough — a raw GraphQL document, executed on this credential.
109
+ *
110
+ * It changed meaning with the transport, and deliberately so. It used to take
111
+ * a JSON op because that was the wire; now the wire is GraphQL, and an escape
112
+ * hatch that could only say what the JSON op could say would be an escape from
113
+ * nothing. As a document it reaches everything the credential can reach —
114
+ * including the root fields no model or namespace claims (`designTokens`,
115
+ * `record`, `locales`, `resolvedLocale`, the whole customer-auth mutation
116
+ * set), which the JSON op could not address at all.
117
+ *
118
+ * Returns the whole `data` object rather than a single root field, because a
119
+ * hand-written document may legitimately have several. Routing, the persisted
120
+ * -query handshake and `FoirRequestError` are exactly as on the typed path.
121
+ *
122
+ * ```ts
123
+ * const { pages } = await foir.raw<{ pages: { totalCount: number } }>(
124
+ * 'query Recent($n: Int) { pages(first: $n, orderBy: { createdAt: DESC }) { totalCount } }',
125
+ * { n: 5 },
126
+ * );
127
+ * ```
128
+ */
129
+ raw<T = unknown>(document: string, variables?: Record<string, unknown>): Promise<T>;
130
+ /**
131
+ * Close the subscription socket and drop every live subscription.
132
+ *
133
+ * Only needed by a long-lived process that discards a client — a browser tab
134
+ * or an edge invocation just goes away. The socket is lazy and self-closing
135
+ * (it opens on the first subscription and closes when the last is cancelled),
136
+ * so a client that only queries never has one to dispose of.
137
+ */
138
+ dispose(): void;
139
+ }
140
+ /**
141
+ * The client, shaped by the credential's manifest. An op that is not in
142
+ * `FoirSchema` is not on this type, so `foir.tilly_block.create(...)` under a
143
+ * read-only `pk_` key is a compile error rather than a 400.
144
+ */
145
+ export type FoirClient<S extends FoirSchemaDefinition> = FoirClientCore<S> & {
146
+ [MK in keyof S['models']]: FoirModelClient<S, S['models'][MK]>;
147
+ };
148
+ export declare function createFoirClient<S extends FoirSchemaDefinition = FoirSchemaDefinition>(clientConfig: FoirClientConfig<S>): FoirClient<S>;
149
+ /**
150
+ * `createFoirClient` from the `PLATFORM_API_URL` / `PLATFORM_API_KEY` /
151
+ * `FOIR_PREVIEW` convention `@foir/sdk/client` already uses, so a storefront
152
+ * that has an env wired for GraphQL can add the data client without a second
153
+ * set of variables. Preview picks the `sk_` `PLATFORM_PREVIEW_API_KEY` and the
154
+ * draft schema channel, exactly as `foirRequest` does.
155
+ *
156
+ * ```ts
157
+ * import * as generated from './foir.generated';
158
+ * const foir = createFoirClientFromEnv(env, { generated });
159
+ * ```
160
+ *
161
+ * `generated` is spelled out in the overrides type rather than left inside the
162
+ * `Omit<…>` so `S` is inferred from it here exactly as it is on `createFoirClient`.
163
+ */
164
+ export declare function createFoirClientFromEnv<S extends FoirSchemaDefinition = FoirSchemaDefinition>(env: FoirEnv, overrides?: Omit<FoirClientConfig, 'apiUrl' | 'apiKey' | 'channel' | 'generated'> & {
165
+ generated?: FoirGeneratedModule<S>;
166
+ }): FoirClient<S>;