@foir/sdk 0.4.0 → 0.7.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 (82) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/README.md +63 -0
  3. package/dist/auth/auth.d.ts +140 -0
  4. package/dist/auth/hosted.d.ts +40 -0
  5. package/dist/auth/index.d.ts +15 -0
  6. package/dist/auth/operations.d.ts +123 -0
  7. package/dist/auth/session-client.d.ts +76 -0
  8. package/dist/auth-session.js +137 -0
  9. package/dist/auth.js +238 -26
  10. package/dist/client/platform-client.d.ts +29 -0
  11. package/dist/client.js +290 -32
  12. package/dist/codegen/block-registry.d.ts +4 -8
  13. package/dist/codegen/block-scaffold.d.ts +4 -8
  14. package/dist/codegen/index.d.ts +8 -62
  15. package/dist/codegen/preset.d.ts +60 -0
  16. package/dist/codegen/shared.d.ts +25 -0
  17. package/dist/components/EditorProvider.d.ts +36 -0
  18. package/dist/content/content-renderer.d.ts +47 -0
  19. package/dist/content/index.d.ts +12 -0
  20. package/dist/content/richtext.d.ts +70 -0
  21. package/dist/data/builtins.d.ts +296 -0
  22. package/dist/data/client.d.ts +94 -0
  23. package/dist/data/index.d.ts +54 -0
  24. package/dist/data/manifest.d.ts +161 -0
  25. package/dist/data/schema.d.ts +243 -0
  26. package/dist/data/selection.d.ts +330 -0
  27. package/dist/data/transport.d.ts +107 -0
  28. package/dist/data.js +309 -0
  29. package/dist/graphql/cdn-route.d.ts +42 -0
  30. package/dist/graphql/client.d.ts +30 -0
  31. package/dist/graphql/content-client.d.ts +66 -0
  32. package/dist/graphql/foir-request.d.ts +47 -0
  33. package/dist/hooks/useAutoResize.d.ts +22 -0
  34. package/dist/hooks/useEditor.d.ts +16 -0
  35. package/dist/index.d.ts +10 -394
  36. package/dist/media/BlurhashCanvas.d.ts +10 -0
  37. package/dist/media/ProgressiveMedia.d.ts +56 -0
  38. package/dist/media/index.d.ts +14 -0
  39. package/dist/media/media.d.ts +139 -0
  40. package/dist/media/useProgressiveLoad.d.ts +23 -0
  41. package/dist/media.js +5 -0
  42. package/dist/next/cookieAdapter.d.ts +26 -0
  43. package/dist/next/index.d.ts +15 -0
  44. package/dist/next/middleware.d.ts +49 -0
  45. package/dist/next/session.d.ts +53 -0
  46. package/dist/next.js +230 -26
  47. package/dist/protocol/constants.d.ts +6 -0
  48. package/dist/protocol/messages.d.ts +155 -0
  49. package/dist/protocol/validation.d.ts +8 -0
  50. package/dist/push/index.d.ts +14 -0
  51. package/dist/push/operations.d.ts +22 -0
  52. package/dist/push/web-push.d.ts +39 -0
  53. package/dist/push.js +230 -26
  54. package/dist/react/FoirProvider.d.ts +116 -0
  55. package/dist/react/OAuthButtons.d.ts +20 -0
  56. package/dist/react/SignIn.d.ts +22 -0
  57. package/dist/react/UserButton.d.ts +13 -0
  58. package/dist/react/index.d.ts +19 -0
  59. package/dist/react/localStorageAdapter.d.ts +10 -0
  60. package/dist/react.js +230 -26
  61. package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
  62. package/dist/server/callback-client.d.ts +92 -0
  63. package/dist/server/index.d.ts +8 -0
  64. package/dist/server/operation-types.d.ts +134 -0
  65. package/dist/server/require-scoped-token.d.ts +76 -0
  66. package/dist/server/server-client.d.ts +34 -0
  67. package/dist/server/types.d.ts +54 -0
  68. package/dist/server/verify-scoped-token.d.ts +100 -0
  69. package/dist/server/webhook-helpers.d.ts +36 -0
  70. package/dist/transport/foir-http.d.ts +217 -0
  71. package/dist/transport/retry.d.ts +61 -0
  72. package/dist/types/index.d.ts +124 -0
  73. package/package.json +23 -13
  74. package/dist/auth.d.ts +0 -382
  75. package/dist/client.d.ts +0 -83
  76. package/dist/content.d.ts +0 -96
  77. package/dist/media.d.ts +0 -234
  78. package/dist/next.d.ts +0 -178
  79. package/dist/push.d.ts +0 -109
  80. package/dist/react.d.ts +0 -319
  81. package/dist/server.d.ts +0 -534
  82. package/dist/shared-DkWlEd2Z.d.ts +0 -18
@@ -0,0 +1,70 @@
1
+ /**
2
+ * RichTextRenderer — render a Foir richtext field (a Lexical EditorState) with
3
+ * per-paragraph typography tokens applied as inline styles.
4
+ *
5
+ * The design-token dependency is INJECTED, not imported from an app: pass the
6
+ * resolved `typography` token array as a prop, or provide it once via
7
+ * `<RichTextTypographyProvider>`. With no tokens the structure still renders
8
+ * (fallback tag, no inline typography), and a doc-less value falls back to its
9
+ * `html` projection. No `@foir`-app imports.
10
+ *
11
+ * Token semantics:
12
+ * - `token.tag` (paragraph / h1–h6 / blockquote) controls the rendered HTML
13
+ * element and wins over the Lexical heading `tag` when both are present.
14
+ * - Paragraph `format` ('left'|'center'|'right'|'justify') → text-align.
15
+ * - Text-node `format` bits → inline emphasis: bold(1) italic(2)
16
+ * strikethrough(4) underline(8) code(16).
17
+ */
18
+ import { type ReactNode, type JSX } from 'react';
19
+ /**
20
+ * safeHref allow-lists link schemes so stored richtext can't smuggle a
21
+ * `javascript:` (or `data:`/`vbscript:`) URL that executes on click (EID-207).
22
+ * Relative URLs, anchors, and the http(s)/mailto/tel schemes pass; anything
23
+ * else collapses to '#'. Editors are not fully trusted, so this runs on the
24
+ * structured render path, not only the html fallback.
25
+ */
26
+ export declare function safeHref(url: string | undefined): string;
27
+ /** A resolved typography token from the project's design tokens. */
28
+ export interface TypographyToken {
29
+ /** Token key referenced by a Lexical node's `style`. */
30
+ name: string;
31
+ /** Rendered HTML element (paragraph / h1–h6 / blockquote). */
32
+ tag?: string;
33
+ value: {
34
+ fontFamily: string[];
35
+ fontSize: string | number;
36
+ fontWeight: string | number;
37
+ lineHeight: string | number;
38
+ letterSpacing: string | number;
39
+ textTransform?: string;
40
+ };
41
+ }
42
+ /**
43
+ * Provide the project's resolved typography tokens to every nested
44
+ * `<RichTextRenderer>` so callers don't thread them through each usage. A
45
+ * `tokens` prop on the renderer still takes precedence.
46
+ */
47
+ export declare function RichTextTypographyProvider({ tokens, children, }: {
48
+ tokens: TypographyToken[];
49
+ children: ReactNode;
50
+ }): import("react/jsx-runtime").JSX.Element;
51
+ /** Read the injected typography tokens (null when no provider is mounted). */
52
+ export declare function useRichTextTypography(): TypographyToken[] | null;
53
+ interface RichtextValueLike {
54
+ html?: string | null;
55
+ json?: unknown;
56
+ }
57
+ export interface RichTextRendererProps {
58
+ value: RichtextValueLike | null | undefined;
59
+ className?: string;
60
+ /**
61
+ * Resolved typography tokens. Overrides any provided via
62
+ * `<RichTextTypographyProvider>`. When neither is set, paragraphs render
63
+ * with `fallbackTag` and no inline typography.
64
+ */
65
+ tokens?: TypographyToken[];
66
+ /** Tag used for any paragraph with no token style and no Lexical tag. */
67
+ fallbackTag?: keyof JSX.IntrinsicElements;
68
+ }
69
+ export declare function RichTextRenderer({ value, className, tokens, fallbackTag, }: RichTextRendererProps): import("react/jsx-runtime").JSX.Element | null;
70
+ export {};
@@ -0,0 +1,296 @@
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 eight of the names it can use are platform-owned
11
+ * objects rather than leaves: `ImageValue`, `VideoValue`, `FileValue`,
12
+ * `MediaValue`, `RichtextValue`, `GeoPointValue`, `ReferenceValue`,
13
+ * `CollabInfo`. They are structurally nested but behave as leaves — always
14
+ * present, never lazily hydrated, never `include`-able.
15
+ *
16
+ * 2. **Block primitives.** A block union's members are *model keys*, except
17
+ * for eight lowercase tokens — `text`, `image`, `video`, `file`,
18
+ * `richtext`, `media`, `reference`, `flexible` — which are members of the
19
+ * real GraphQL union but have **no entry in `models`**. On the live dev
20
+ * Marketing project, 7 of `tilly_page.blocks`'s 29 members are these. A
21
+ * generator that resolved every member to `models[key]` would skip them
22
+ * (an inexhaustive union, silently wrong at runtime) or emit `never`.
23
+ *
24
+ * Both sets are the manifest's own prescription, not this file's invention: the
25
+ * payload reference says of the value objects that a client "ships static types
26
+ * for them rather than generating them per project", and names the eight block
27
+ * tokens as having no `models` entry by design.
28
+ *
29
+ * ---------------------------------------------------------------------------
30
+ * The obligation that comes with a static table
31
+ * ---------------------------------------------------------------------------
32
+ *
33
+ * This IS a mirror of a platform-side table, and mirrors drift. What keeps it
34
+ * honest is that the generator refuses to guess: a union member key that is
35
+ * neither in `models` nor in this table makes `foir types` fail loudly naming
36
+ * the member, rather than emitting a union that silently lacks a branch. So the
37
+ * failure mode of drift is a red build with the missing name in it.
38
+ *
39
+ * `apps/cli/src/lib/__tests__/types-codegen.test.ts` asserts that against both
40
+ * committed live-captured manifests.
41
+ *
42
+ * Field shapes verified against deployed dev's introspection on 2026-07-28 and
43
+ * against `api-public/internal/schema/field_mapping.go`.
44
+ */
45
+ /** A `JSON` scalar. Deliberately not `any`: reading through it needs a check. */
46
+ export type FoirJsonValue = string | number | boolean | null | FoirJsonValue[] | {
47
+ [key: string]: FoirJsonValue;
48
+ };
49
+ export type ImageFormat = 'WEBP' | 'AVIF' | 'JPEG' | 'PNG';
50
+ /** One rendition of an image at a named tier, in a single format. */
51
+ export interface ImageVariant {
52
+ size: string;
53
+ format: ImageFormat;
54
+ url: string;
55
+ width: number | null;
56
+ height: number | null;
57
+ }
58
+ /**
59
+ * The curated tier set for one format. `_2xl`/`_3xl`/`_4xl` carry the
60
+ * underscore because a GraphQL identifier cannot start with a digit.
61
+ */
62
+ export interface ImageVariantSet {
63
+ thumbnail: ImageVariant | null;
64
+ small: ImageVariant | null;
65
+ medium: ImageVariant | null;
66
+ large: ImageVariant | null;
67
+ xlarge: ImageVariant | null;
68
+ _2xl: ImageVariant | null;
69
+ _3xl: ImageVariant | null;
70
+ _4xl: ImageVariant | null;
71
+ }
72
+ export interface ImageValue {
73
+ url: string;
74
+ width: number | null;
75
+ height: number | null;
76
+ alt: string | null;
77
+ blurhash: string | null;
78
+ dominantColor: string | null;
79
+ mimeType: string | null;
80
+ fileSize: number | null;
81
+ focalPoint: FoirJsonValue | null;
82
+ crop: FoirJsonValue | null;
83
+ variants: ImageVariantSet | null;
84
+ /** WebP tier set (universal support). Pair with `avif` in a `<picture>`. */
85
+ webp: ImageVariantSet | null;
86
+ /** AVIF tier set (smaller, narrower support). The preferred `<picture>` source. */
87
+ avif: ImageVariantSet | null;
88
+ }
89
+ export interface VideoValue {
90
+ url: string;
91
+ thumbnailUrl: string | null;
92
+ previewUrl: string | null;
93
+ hlsManifestUrl: string | null;
94
+ width: number | null;
95
+ height: number | null;
96
+ duration: number | null;
97
+ blurhash: string | null;
98
+ dominantColor: string | null;
99
+ mimeType: string | null;
100
+ fileSize: number | null;
101
+ }
102
+ export interface FileValue {
103
+ url: string | null;
104
+ filename: string | null;
105
+ mimeType: string | null;
106
+ size: number | null;
107
+ }
108
+ export interface GeoPointValue {
109
+ lat: number | null;
110
+ lng: number | null;
111
+ }
112
+ export interface RichtextValue {
113
+ html: string | null;
114
+ json: FoirJsonValue | null;
115
+ markdown: string | null;
116
+ }
117
+ /**
118
+ * A foreign key, and the reason a consumer never writes `include` to get one:
119
+ * this arrives with the record, complete, under the complete-by-default
120
+ * selection model. `post.author._id` is always there and always typed present.
121
+ */
122
+ export interface ReferenceValue {
123
+ _id: string | null;
124
+ _type: string | null;
125
+ _schema: string | null;
126
+ naturalKey: string | null;
127
+ _preview: FoirJsonValue | null;
128
+ }
129
+ /** Inline media embedded in a content field — polymorphic per `mediaType`. */
130
+ export interface MediaValue {
131
+ mediaType: string | null;
132
+ url: string | null;
133
+ mimeType: string | null;
134
+ fileId: string | null;
135
+ fileSize: number | null;
136
+ filename: string | null;
137
+ width: number | null;
138
+ height: number | null;
139
+ blurhash: string | null;
140
+ dominantColor: string | null;
141
+ duration: number | null;
142
+ hlsManifestUrl: string | null;
143
+ previewUrl: string | null;
144
+ thumbnailUrl: string | null;
145
+ variants: FoirJsonValue | null;
146
+ crop: FoirJsonValue | null;
147
+ focalPoint: FoirJsonValue | null;
148
+ }
149
+ /** Collaborative-editing discovery. */
150
+ export interface CollabInfo {
151
+ enabled: boolean;
152
+ wsUrl: string;
153
+ roomName: string;
154
+ }
155
+ /**
156
+ * The metadata every block member carries, and the two discriminants an
157
+ * exhaustive `switch` narrows on.
158
+ *
159
+ * `__typename` is the one to prefer. The op compiler emits it on the union's
160
+ * own selection set for every union it expands, and it is the only field that
161
+ * cannot be `omit`ed (no omit path can name it). `_type` is the foir block-type
162
+ * token — the model key for a model-derived member, the lowercase primitive
163
+ * token otherwise — and is deliberately NOT protected from `omit` by the
164
+ * server, so a caller who omits it loses the second discriminant while keeping
165
+ * the first.
166
+ */
167
+ export interface FoirBlockMeta<TType extends string, TTypename extends string> {
168
+ __typename: TTypename;
169
+ _type: TType;
170
+ _id: string | null;
171
+ _key: string | null;
172
+ _label: string | null;
173
+ }
174
+ export interface RichtextBlock extends FoirBlockMeta<'richtext', 'RichtextBlock'>, RichtextValue {
175
+ }
176
+ export interface ImageBlock extends FoirBlockMeta<'image', 'ImageBlock'>, Omit<ImageValue, 'url'> {
177
+ /**
178
+ * Nullable here, `String!` on `ImageValue`. Wrapping a value type as a block
179
+ * drops NonNull (`wrapValueFieldsAsBlock`), and reporting it non-null would
180
+ * be a lie a consumer's generated types cannot recover from.
181
+ */
182
+ url: string | null;
183
+ }
184
+ export interface VideoBlock extends FoirBlockMeta<'video', 'VideoBlock'>, Omit<VideoValue, 'url'> {
185
+ /** Nullable here, `String!` on `VideoValue` — see `ImageBlock.url`. */
186
+ url: string | null;
187
+ }
188
+ export interface FileBlock extends FoirBlockMeta<'file', 'FileBlock'>, FileValue {
189
+ }
190
+ export interface MediaBlock extends FoirBlockMeta<'media', 'MediaBlock'>, MediaValue {
191
+ }
192
+ export interface TextBlock extends FoirBlockMeta<'text', 'TextBlock'> {
193
+ value: string | null;
194
+ }
195
+ /**
196
+ * A reference to another record.
197
+ *
198
+ * `record` is the resolved target, over the project's `Record` union — every
199
+ * model in the project. It is left as a type parameter so the generated file
200
+ * can name that union; the default is the honest answer for a consumer who has
201
+ * not narrowed it.
202
+ */
203
+ export interface ReferenceBlock<TRecord = FoirUnexpandedRecord> extends FoirBlockMeta<'reference', 'ReferenceBlock'>, Omit<ReferenceValue, '_id' | '_type'> {
204
+ record: TRecord | null;
205
+ }
206
+ /**
207
+ * The fallback member, for a `flexible` field in a project with no block types
208
+ * to offer. Its `_type` is whatever the stored value carried, so — uniquely
209
+ * among the primitives — it is not a literal.
210
+ */
211
+ export interface FlexibleBlock {
212
+ __typename: 'FlexibleBlock';
213
+ _type: string | null;
214
+ _id: string | null;
215
+ _key: string | null;
216
+ _label: string | null;
217
+ value: FoirJsonValue | null;
218
+ }
219
+ /**
220
+ * What a nested union or a re-entered object resolves to.
221
+ *
222
+ * The op compiler enforces a termination rule — *never expand a type, object or
223
+ * union, that is already on the current path; degrade it to `__typename`
224
+ * instead* — because `Block` contains members that contain `Block`, and a
225
+ * `tilly_page.blocks → TillyPageBlock.blocks` cycle is reachable in two hops
226
+ * with no `include` and no caller input at all. At a degraded position the
227
+ * response carries `__typename` and nothing else, so that is what the generated
228
+ * type says. Reach the rest with `foir.raw()`.
229
+ */
230
+ export interface FoirDegraded<TTypename extends string = string> {
231
+ __typename: TTypename;
232
+ }
233
+ /** A `Record`-union value the compiler did not expand. See `FoirDegraded`. */
234
+ export type FoirUnexpandedRecord = FoirDegraded;
235
+ /**
236
+ * The results of the ops that do NOT return the model.
237
+ *
238
+ * `foir types` classifies these as `opaque`, so the client offers no
239
+ * `include` / `omit` for them and uses the type below verbatim. They are here
240
+ * for the same reason the value objects are: platform-owned, fixed, and
241
+ * identical in every project.
242
+ */
243
+ export type FoirVisibility = 'PRIVATE' | 'PUBLIC';
244
+ export type FoirAccessLevel = 'READ' | 'WRITE' | 'DELETE';
245
+ export type FoirAccessSource = 'PUBLIC' | 'OWNER' | 'GRANT' | 'ROLE';
246
+ export type FoirGranteeKind = 'CUSTOMER' | 'ADMIN';
247
+ /** `myAccess` — this principal's effective access to one record. */
248
+ export interface FoirMyAccess {
249
+ access: FoirAccessLevel;
250
+ source: FoirAccessSource;
251
+ }
252
+ export interface FoirAccessGrant {
253
+ access: FoirAccessLevel;
254
+ createdAt: string;
255
+ granteeId: string;
256
+ granteeKind: FoirGranteeKind;
257
+ }
258
+ export interface FoirAccessor {
259
+ access: FoirAccessLevel;
260
+ granteeId: string;
261
+ granteeKind: FoirGranteeKind;
262
+ source: FoirAccessSource;
263
+ }
264
+ /** `access` — the full ACL of one record. */
265
+ export interface FoirResourceAccess {
266
+ accessors: FoirAccessor[];
267
+ grants: FoirAccessGrant[];
268
+ public: boolean;
269
+ visibility: FoirVisibility;
270
+ }
271
+ /** `updateMany` — how many rows moved, and which. */
272
+ export interface FoirUpdateManyPayload {
273
+ count: number;
274
+ ids: string[];
275
+ }
276
+ export type FoirDeviceType = 'DESKTOP' | 'MOBILE' | 'TABLET';
277
+ export type FoirPlatformType = 'IOS' | 'ANDROID' | 'WEB';
278
+ /**
279
+ * The `contexts` argument on every typed read.
280
+ *
281
+ * **It is an object, not a list**, despite the plural name — `ResolveContextInput`
282
+ * on the wire, verified against deployed dev's introspection on both projects:
283
+ * `contexts: ResolveContextInput` with no LIST wrapper, four optional fields.
284
+ *
285
+ * Worth the paragraph because the plural reads like `string[]` and typing it
286
+ * that way compiles fine and then sends a body the server rejects. The
287
+ * `FoirSchemaDefinition` sketch in `schema.ts` had exactly that error, and it
288
+ * was found by the docs lane running the call rather than by anyone reading the
289
+ * contract.
290
+ */
291
+ export interface FoirResolveContext {
292
+ device?: FoirDeviceType | null;
293
+ locale?: string | null;
294
+ platform?: FoirPlatformType | null;
295
+ preview?: boolean | null;
296
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `createFoirClient` — the typed data client.
3
+ *
4
+ * ```ts
5
+ * import { createFoirClient } from '@foir/sdk/data';
6
+ * import { type FoirSchema, foirSchemaVersion } from './foir.generated';
7
+ *
8
+ * const foir = createFoirClient<FoirSchema>({
9
+ * apiUrl: process.env.PLATFORM_API_URL!,
10
+ * apiKey: process.env.PLATFORM_API_KEY!,
11
+ * schemaVersion: foirSchemaVersion,
12
+ * });
13
+ *
14
+ * const block = await foir.tilly_block.findUnique({
15
+ * id: 'a3pr8x0j10rigx8u6t1xthbr',
16
+ * include: { noteId: true },
17
+ * omit: ['aiSummary'],
18
+ * });
19
+ * ```
20
+ *
21
+ * ---------------------------------------------------------------------------
22
+ * Why this is a Proxy
23
+ * ---------------------------------------------------------------------------
24
+ *
25
+ * `FoirSchema` is a TYPE. `foir types` emits interfaces, and interfaces do not
26
+ * exist at runtime, so the client is handed the full shape of the API at
27
+ * compile time and nothing at all at run time. There is no model list to build
28
+ * an object from — `foir.tilly_block.findMany` has to be resolved when it is
29
+ * reached. A two-level `Proxy` does that in a few lines and costs one property
30
+ * lookup per call.
31
+ *
32
+ * The alternative — fetching `/v1/manifest` at construction — would put a
33
+ * round trip in front of the first query on every cold edge invocation, to
34
+ * learn something the types already know.
35
+ *
36
+ * ---------------------------------------------------------------------------
37
+ * Why namespaces live under `.ns`
38
+ * ---------------------------------------------------------------------------
39
+ *
40
+ * `POST /v1/query` takes `model` OR `namespace`, never both, and refuses the
41
+ * request if you send both. Since there is no runtime schema (above), the
42
+ * client cannot tell which one `foir.search` means. So the addressing carries
43
+ * it: `foir.<model>.<op>()` sends `model`, `foir.ns.<namespace>.<op>()` sends
44
+ * `namespace`. `ns` and `raw` are therefore reserved top-level names.
45
+ */
46
+ import type { FoirModelDefinition, FoirNamespaceOpDefinition, FoirOpDefinition, FoirSchemaDefinition } from './schema';
47
+ import type { FoirModelOperation, FoirNamespaceOperation } from './selection';
48
+ import { type FoirEnv } from '../transport/foir-http';
49
+ import { type FoirClientConfig, type FoirRawOp } from './transport';
50
+ /** The ops available on one model for this credential. */
51
+ export type FoirModelClient<S extends FoirSchemaDefinition, M extends FoirModelDefinition> = {
52
+ [OK in keyof M['ops']]: M['ops'][OK] extends FoirOpDefinition ? FoirModelOperation<S, M, M['ops'][OK]> : never;
53
+ };
54
+ export type FoirNamespaceClient<N extends {
55
+ ops: object;
56
+ }> = {
57
+ [OK in keyof N['ops']]: N['ops'][OK] extends FoirNamespaceOpDefinition ? FoirNamespaceOperation<N['ops'][OK]> : never;
58
+ };
59
+ export interface FoirClientCore<S extends FoirSchemaDefinition> {
60
+ /**
61
+ * Non-model root namespaces (`search`, `customers`, `notifications`).
62
+ * Separate accessor because the wire distinguishes `model` from `namespace`
63
+ * and the client has no runtime schema to disambiguate with.
64
+ */
65
+ readonly ns: {
66
+ [NK in keyof S['namespaces']]: FoirNamespaceClient<S['namespaces'][NK]>;
67
+ };
68
+ /**
69
+ * Untyped passthrough to `POST /v1/query`.
70
+ *
71
+ * Every generated-type system needs an escape hatch; shipping one
72
+ * deliberately is better than people forking the client when the generated
73
+ * types cannot express something. Returns the single root field, same as a
74
+ * typed call, and throws the same `FoirRequestError`.
75
+ */
76
+ raw<T = unknown>(op: FoirRawOp): Promise<T>;
77
+ }
78
+ /**
79
+ * The client, shaped by the credential's manifest. An op that is not in
80
+ * `FoirSchema` is not on this type, so `foir.tilly_block.create(...)` under a
81
+ * read-only `pk_` key is a compile error rather than a 400.
82
+ */
83
+ export type FoirClient<S extends FoirSchemaDefinition> = FoirClientCore<S> & {
84
+ [MK in keyof S['models']]: FoirModelClient<S, S['models'][MK]>;
85
+ };
86
+ export declare function createFoirClient<S extends FoirSchemaDefinition>(config: FoirClientConfig): FoirClient<S>;
87
+ /**
88
+ * `createFoirClient` from the `PLATFORM_API_URL` / `PLATFORM_API_KEY` /
89
+ * `FOIR_PREVIEW` convention `@foir/sdk/client` already uses, so a storefront
90
+ * that has an env wired for GraphQL can add the data client without a second
91
+ * set of variables. Preview picks the `sk_` `PLATFORM_PREVIEW_API_KEY` and the
92
+ * draft schema channel, exactly as `foirRequest` does.
93
+ */
94
+ export declare function createFoirClientFromEnv<S extends FoirSchemaDefinition>(env: FoirEnv, overrides?: Omit<FoirClientConfig, 'apiUrl' | 'apiKey' | 'channel'>): FoirClient<S>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `@foir/sdk/data` — the typed data client.
3
+ *
4
+ * A second app-runtime transport alongside `@foir/sdk/client`'s GraphQL one,
5
+ * shaped by the credential you hand it. `POST /v1/query` takes a JSON op and
6
+ * the server compiles it to a GraphQL document, so both surfaces run through
7
+ * the same executor, the same scope checks, the same response cache — the SDK
8
+ * surface is a projection of `SchemaForCaller(scopes)`, never a parallel
9
+ * construction.
10
+ *
11
+ * What you get over the GraphQL path: no `.graphql` documents, no
12
+ * `graphql-codegen` step, and **no `graphql` dependency at all**. This subpath
13
+ * imports neither `graphql` nor `@graphql-typed-document-node/core`; the import
14
+ * graph is asserted graphql-free by `__tests__/data.test.ts`
15
+ * ("`@foir/sdk/data` has zero graphql dependency").
16
+ *
17
+ * `./data` and not `./client` because `./client` is the editor-iframe/GraphQL
18
+ * transport and is already published.
19
+ *
20
+ * ```ts
21
+ * import { createFoirClient } from '@foir/sdk/data';
22
+ * import { type FoirSchema, foirSchemaVersion } from './foir.generated';
23
+ *
24
+ * const foir = createFoirClient<FoirSchema>({
25
+ * apiUrl: 'https://api.foir.dev',
26
+ * apiKey: process.env.PLATFORM_API_KEY!,
27
+ * schemaVersion: foirSchemaVersion,
28
+ * });
29
+ *
30
+ * const page = await foir.page.findUnique({
31
+ * naturalKey: 'home',
32
+ * include: { author: true },
33
+ * omit: ['bodyRichtext'],
34
+ * });
35
+ *
36
+ * const results = await foir.ns.search.searchRecords({ query: 'shoes' });
37
+ * const anything = await foir.raw({ model: 'page', op: 'findMany', args: { first: 5 } });
38
+ * ```
39
+ *
40
+ * `FoirSchema` comes from `foir types` (TTS-875). The contract it generates
41
+ * against is written down in `schema.ts` — read that file before changing
42
+ * anything here, because a generator is written against it.
43
+ */
44
+ export { createFoirClient, createFoirClientFromEnv } from './client';
45
+ export type { FoirClient, FoirClientCore, FoirModelClient, FoirNamespaceClient } from './client';
46
+ export { fetchFoirManifest } from './manifest';
47
+ export type { FoirManifest, FoirManifestComposite, FoirManifestModel, FoirManifestNamespace, FoirManifestOp, FoirManifestRelation, FoirManifestScalar, FoirManifestUnclassifiedField, } from './manifest';
48
+ export type { CollabInfo, FileBlock, FileValue, FlexibleBlock, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirBlockMeta, FoirDegraded, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirPlatformType, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPointValue, ImageBlock, ImageFormat, ImageValue, ImageVariant, ImageVariantSet, MediaBlock, MediaValue, ReferenceBlock, ReferenceValue, RichtextBlock, RichtextValue, TextBlock, VideoBlock, VideoValue, } from './builtins';
49
+ export { foirDataEndpoint } from './transport';
50
+ export type { FoirClientConfig, FoirRawOp, FoirSchemaDrift } from './transport';
51
+ export type { FoirConnection, FoirFieldsShape, FoirModelDefinition, FoirNamespaceDefinition, FoirNamespaceOpDefinition, FoirOpDefinition, FoirRelationDefinition, FoirReturnDefinition, FoirSchemaDefinition, } from './schema';
52
+ export type { FoirInclude, FoirModelOperation, FoirNamespaceOperation, FoirOmittableKey, FoirOpResult, FoirQuery, FoirRecordResult, } from './selection';
53
+ export { FoirRequestError } from '../transport/foir-http';
54
+ export type { FoirEnv, FoirGraphQLErrorItem, FoirTokenProvider, } from '../transport/foir-http';
@@ -0,0 +1,161 @@
1
+ /**
2
+ * `GET /v1/manifest` — the per-caller surface descriptor, as a runtime type.
3
+ *
4
+ * This is the *runtime* mirror of `schema.ts`'s compile-time contract: what the
5
+ * server actually sends, verbatim, so `foir types` (TTS-875) has a typed thing
6
+ * to read and the drift check has a version to compare. The manifest is already
7
+ * the projection of `CachedSchema.SchemaForCaller(scopes)`, so what it lists is
8
+ * exactly what this credential may do — no client-side filtering anywhere.
9
+ *
10
+ * Shape verified against deployed dev on 2026-07-28, against two live keys
11
+ * (an unnarrowed one over 7 models, and one narrowed to a single model whose
12
+ * `tilly_page.blocks` union carries 29 members). The buckets below are the
13
+ * platform's `Manifest` struct
14
+ * (`api-public/internal/schema/manifest.go`) field for field; where the two
15
+ * disagree the platform is right and this file is the bug.
16
+ */
17
+ import { type FoirClientConfig } from './transport';
18
+ export interface FoirManifestScalar {
19
+ key: string;
20
+ /**
21
+ * GraphQL named type: a scalar (`String`, `Float`, `Boolean`, `ID`,
22
+ * `DateTime`, `JSON`), a generated enum (then `enumValues` is set), or one of
23
+ * the closed set of platform-owned value objects (`ImageValue`, `VideoValue`,
24
+ * `FileValue`, `MediaValue`, `RichtextValue`, `GeoPointValue`,
25
+ * `ReferenceValue`, `CollabInfo`) — see `builtins.ts`.
26
+ */
27
+ type: string;
28
+ /**
29
+ * Mirrors the EXECUTABLE schema, NOT the model's `required` flag. The schema
30
+ * generator deliberately never emits NonNull for a user field, so a
31
+ * `required: true` field still reports `nullable: true`. Reporting otherwise
32
+ * would be the lie a consumer's generated types cannot recover from.
33
+ */
34
+ nullable: boolean;
35
+ list: boolean;
36
+ /** Enum value NAMES (what the wire carries). Set only when `type` names an enum. */
37
+ enumValues?: string[];
38
+ /**
39
+ * A soft-deleted field, still in the executable schema for its 30-day grace
40
+ * window and always resolving null. Reported rather than dropped so an
41
+ * in-flight client query still type-checks.
42
+ */
43
+ deprecated?: boolean;
44
+ }
45
+ export interface FoirManifestRelation {
46
+ key: string;
47
+ /** Model key of the relation's target. Absent for a polymorphic reference. */
48
+ target?: string;
49
+ /** Member model keys, for a polymorphic reference (a `Ref` union over ≥2 models). */
50
+ targets?: string[];
51
+ list: boolean;
52
+ /** A Relay connection (the association-inverse shape) rather than a plain list. */
53
+ connection?: boolean;
54
+ nullable: boolean;
55
+ deprecated?: boolean;
56
+ }
57
+ /**
58
+ * An embedded (inline) model field: `seo`, `page_content`, `section`.
59
+ *
60
+ * Its own bucket because neither of the other two fits — always returned like
61
+ * a scalar, structurally nested like a relation, and never `include`-able.
62
+ */
63
+ export interface FoirManifestComposite {
64
+ key: string;
65
+ /** The embedded model's key. Its shape is in `models`. */
66
+ target: string;
67
+ list: boolean;
68
+ nullable: boolean;
69
+ deprecated?: boolean;
70
+ }
71
+ export interface FoirManifestOp {
72
+ /** GraphQL root field this op compiles to, e.g. `tillyBlocks`. */
73
+ field: string;
74
+ args?: string[];
75
+ /** True when the field lives on the Mutation root. */
76
+ mutation?: boolean;
77
+ }
78
+ /** A built field the projection has no bucket for. Reported rather than dropped. */
79
+ export interface FoirManifestUnclassifiedField {
80
+ key: string;
81
+ type: string;
82
+ list: boolean;
83
+ }
84
+ export interface FoirManifestModel {
85
+ typeName: string;
86
+ /**
87
+ * `record` for a record-bearing model, `embedded` for an inline model.
88
+ * Embedded models never carry `ops` — they exist in the payload only because
89
+ * a composite or a block union names them.
90
+ */
91
+ kind: string;
92
+ /**
93
+ * Present ⇔ the corresponding root field is in this caller's variant.
94
+ * ABSENT ENTIRELY on an embedded model, and on a record model no op of which
95
+ * survived pruning — hence optional, not `{}`.
96
+ */
97
+ ops?: Record<string, FoirManifestOp>;
98
+ scalars: FoirManifestScalar[];
99
+ relations: FoirManifestRelation[];
100
+ composites: FoirManifestComposite[];
101
+ /**
102
+ * Block-union field key → member model keys. Members that are built-in block
103
+ * primitives appear under their lowercase token (`text`, `image`, `video`,
104
+ * `file`, `richtext`, `media`, `reference`, `flexible`) and have NO entry in
105
+ * `models` — they are platform-owned, identical in every project, and typed
106
+ * statically by `builtins.ts`.
107
+ */
108
+ unions?: Record<string, string[]>;
109
+ /** Op names of this model's typed lookups. Each also appears in `ops`. */
110
+ lookups?: string[];
111
+ unclassifiedFields?: FoirManifestUnclassifiedField[];
112
+ cacheControl?: {
113
+ scope: string;
114
+ maxAge?: number;
115
+ };
116
+ }
117
+ /**
118
+ * One built-in non-model surface.
119
+ *
120
+ * `queries` / `mutations` hold ROOT FIELD NAMES, and for every namespace but
121
+ * one the op name a caller sends IS that root field name — the server matches
122
+ * on it and constructs nothing (`resolveNamespaceRoot`).
123
+ *
124
+ * `operations` is the exception, and it is why `execute` / `query` are on this
125
+ * interface rather than a second one: there the op name is `execute.<key>` or
126
+ * `query.<key>` built from an operation KEY, while `queries` / `mutations`
127
+ * still hold plain bookkeeping field names.
128
+ */
129
+ export interface FoirManifestNamespace {
130
+ queries?: string[];
131
+ mutations?: string[];
132
+ /** `operations` only: operation keys reachable as `execute.<key>`. */
133
+ execute?: string[];
134
+ /** `operations` only: operation keys reachable as `query.<key>`. */
135
+ query?: string[];
136
+ }
137
+ export interface FoirManifest {
138
+ schemaVersion: number;
139
+ channel: 'published' | 'draft';
140
+ models: Record<string, FoirManifestModel>;
141
+ /**
142
+ * Keyed by namespace name (`auth`, `customers`, `files`, `notifications`,
143
+ * `operations`, `schedules`, `search`, `secrets`). Deliberately an open
144
+ * record rather than the eight names: a namespace the platform adds must
145
+ * reach the generator, and a closed union would silently drop it.
146
+ */
147
+ namespaces: Record<string, FoirManifestNamespace>;
148
+ /** Root fields reachable by this credential that no model/namespace claims. */
149
+ unclassified?: {
150
+ queries?: string[];
151
+ mutations?: string[];
152
+ };
153
+ }
154
+ /**
155
+ * Fetch the manifest for the credential in `config`.
156
+ *
157
+ * Exported because two things outside this file need it: `foir types` reads it
158
+ * to generate `FoirSchema`, and the client's drift check reads its
159
+ * `schemaVersion`. Both would otherwise hand-roll the same fetch.
160
+ */
161
+ export declare function fetchFoirManifest(config: FoirClientConfig): Promise<FoirManifest>;