@foir/sdk 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +719 -27
- package/README.md +121 -2
- package/dist/auth/auth.d.ts +72 -1
- package/dist/auth/hosted.d.ts +71 -4
- package/dist/auth/index.d.ts +3 -3
- package/dist/auth/operations.d.ts +84 -0
- package/dist/auth/ops.d.ts +18 -0
- package/dist/auth.js +209 -47
- package/dist/client.js +101 -13
- package/dist/codegen/block-registry.d.ts +1 -1
- package/dist/codegen/block-scaffold.d.ts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/codegen/item-registry.d.ts +1 -1
- package/dist/codegen/item-scaffold.d.ts +1 -1
- package/dist/codegen/preset.d.ts +3 -2
- package/dist/content/content-renderer.d.ts +4 -5
- package/dist/content/index.d.ts +1 -1
- package/dist/content/richtext.d.ts +1 -1
- package/dist/data/builtins.d.ts +81 -13
- package/dist/data/client.d.ts +92 -9
- package/dist/data/decimal.d.ts +212 -0
- package/dist/data/document.d.ts +14 -1
- package/dist/data/index.d.ts +22 -10
- package/dist/data/manifest.d.ts +40 -4
- package/dist/data/schema.d.ts +18 -11
- package/dist/data/selection.d.ts +15 -0
- package/dist/data/subscriptions.d.ts +36 -10
- package/dist/data/transport.d.ts +363 -24
- package/dist/data/ws-transport.d.ts +8 -2
- package/dist/data.js +591 -64
- package/dist/graphql/cdn-route.d.ts +1 -1
- package/dist/graphql/foir-request.d.ts +1 -1
- package/dist/index.js +0 -9
- package/dist/live/coalesce.d.ts +57 -0
- package/dist/live/drift.d.ts +58 -0
- package/dist/live/events.d.ts +13 -0
- package/dist/live/headers.d.ts +53 -0
- package/dist/live/index.d.ts +54 -0
- package/dist/live/keys.d.ts +72 -0
- package/dist/live/manager.d.ts +77 -0
- package/dist/live/react/index.d.ts +143 -0
- package/dist/live/reconcile.d.ts +82 -0
- package/dist/live/resync.d.ts +59 -0
- package/dist/live/store.d.ts +134 -0
- package/dist/live/tagIndex.d.ts +29 -0
- package/dist/live/tanstack.d.ts +127 -0
- package/dist/live/writetoken.d.ts +86 -0
- package/dist/live-react.js +395 -0
- package/dist/live.js +845 -0
- package/dist/next/cacheTags.d.ts +245 -0
- package/dist/next/index.d.ts +7 -0
- package/dist/next.js +260 -34
- package/dist/push.js +163 -36
- package/dist/react.js +157 -33
- package/dist/realtime/index.d.ts +4 -5
- package/dist/server/events-client.d.ts +54 -4
- package/dist/server/index.d.ts +4 -2
- package/dist/server/operation-types.d.ts +86 -58
- package/dist/server/read-dispatch.d.ts +53 -0
- package/dist/server/require-scoped-token.d.ts +20 -1
- package/dist/server.js +602 -10
- package/dist/transport/foir-http.d.ts +168 -15
- package/package.json +10 -2
|
@@ -30,16 +30,17 @@ export interface FoirEnv {
|
|
|
30
30
|
* Returns true when `FOIR_PREVIEW=true` is set in the environment.
|
|
31
31
|
*
|
|
32
32
|
* Preview is selected at the environment level, not per-request: load a
|
|
33
|
-
* preview env file that sets `FOIR_PREVIEW=true` +
|
|
34
|
-
* `
|
|
33
|
+
* preview env file that sets `FOIR_PREVIEW=true` + the preview environment's
|
|
34
|
+
* own `pk_…` key holding `drafts:read` as `PLATFORM_PREVIEW_API_KEY`, instead
|
|
35
|
+
* of the default env.
|
|
35
36
|
* The site-wide mode keeps draft semantics consistent — every loader sees
|
|
36
37
|
* the same preview/published view of the world. Intended for non-production
|
|
37
38
|
* surfaces only.
|
|
38
39
|
*/
|
|
39
40
|
export declare function isFoirPreview(env: FoirEnv): boolean;
|
|
40
41
|
/**
|
|
41
|
-
* The api key a request should carry for this env: the
|
|
42
|
-
* `FOIR_PREVIEW=true` (it is the one with `drafts:read`), the
|
|
42
|
+
* The api key a request should carry for this env: the preview key when
|
|
43
|
+
* `FOIR_PREVIEW=true` (it is the one with `drafts:read`), the production key
|
|
43
44
|
* otherwise. Returns `undefined` when neither is configured, so the caller can
|
|
44
45
|
* raise the "add them to .env" error with its own wording.
|
|
45
46
|
*/
|
|
@@ -69,6 +70,42 @@ export interface FoirGraphQLErrorItem {
|
|
|
69
70
|
path?: Array<string | number>;
|
|
70
71
|
extensions?: FoirErrorExtensions;
|
|
71
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* One selection the server stripped from the document before executing it.
|
|
75
|
+
*
|
|
76
|
+
* The data plane PRUNES rather than refuses: a selection that does not match
|
|
77
|
+
* the project's current schema is removed and the rest of the query runs, so an
|
|
78
|
+
* editor-driven model change cannot fail a deployed client's query mid-flight.
|
|
79
|
+
* The report arrives in `extensions.pruned` and deliberately NOT in `errors`,
|
|
80
|
+
* because clients treat any `errors` entry as a failed response.
|
|
81
|
+
*
|
|
82
|
+
* Which is why this type exists here. Pruning is the ONLY signal for the
|
|
83
|
+
* commonest way to get a quietly incomplete object — asking for a field the
|
|
84
|
+
* type does not have — and it produces no error for `strict` to catch.
|
|
85
|
+
*/
|
|
86
|
+
export interface FoirPrunedSelection {
|
|
87
|
+
/** Dotted path to the stripped selection, e.g. `pageBySlug.logo.id`. */
|
|
88
|
+
path: string;
|
|
89
|
+
/** Why it was stripped. See `MALFORMED_PRUNE_REASONS`. */
|
|
90
|
+
reason: string;
|
|
91
|
+
/** The parent type the selection was made on, when the server named one. */
|
|
92
|
+
type?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The prune reasons that mean THE DOCUMENT IS WRONG, as opposed to the schema
|
|
96
|
+
* having moved underneath a document that was right when it shipped.
|
|
97
|
+
*
|
|
98
|
+
* A field the type does not have, a subselection on a scalar, or a composite
|
|
99
|
+
* asked for without one: each is a query that could never have been served as
|
|
100
|
+
* written. They are the shape that silently returns a plausible object with a
|
|
101
|
+
* key missing, which is how an empty file id became a `setNull` and would have
|
|
102
|
+
* deleted a logo on the next save.
|
|
103
|
+
*
|
|
104
|
+
* The rest — dead type conditions, dead fragment spreads, unreferenced
|
|
105
|
+
* fragments, unused variables — are what the pruner exists to absorb, and they
|
|
106
|
+
* stay a warning.
|
|
107
|
+
*/
|
|
108
|
+
export declare const MALFORMED_PRUNE_REASONS: ReadonlySet<string>;
|
|
72
109
|
/** What the `X-RateLimit-*` headers said on the response that carried them. */
|
|
73
110
|
export interface FoirRateLimitSnapshot {
|
|
74
111
|
/** Requests allowed per window for this credential. */
|
|
@@ -83,11 +120,27 @@ export declare class FoirRequestError extends Error {
|
|
|
83
120
|
readonly status?: number;
|
|
84
121
|
/** GraphQL errors, when the request failed with a no-data error envelope. */
|
|
85
122
|
readonly graphQLErrors?: ReadonlyArray<FoirGraphQLErrorItem>;
|
|
123
|
+
/**
|
|
124
|
+
* The `data` the server did return, when a STRICT read refused a partial
|
|
125
|
+
* response. Present only on that path: without it, opting into strictness
|
|
126
|
+
* would trade a silent wrong answer for a thrown one that discards the answer,
|
|
127
|
+
* and a caller that wants to log what it nearly rendered could not.
|
|
128
|
+
*/
|
|
129
|
+
readonly partialData?: unknown;
|
|
130
|
+
/**
|
|
131
|
+
* The selections the server stripped, when the refusal was a pruned document
|
|
132
|
+
* rather than a partial error. Same purpose as `partialData`: a caller that
|
|
133
|
+
* wants to log what it nearly rendered, or name the offending path in a
|
|
134
|
+
* developer-facing message, has it without re-parsing anything.
|
|
135
|
+
*/
|
|
136
|
+
readonly prunedSelections?: ReadonlyArray<FoirPrunedSelection>;
|
|
86
137
|
readonly cause?: unknown;
|
|
87
138
|
constructor(message: string, options?: {
|
|
88
139
|
cause?: unknown;
|
|
89
140
|
status?: number;
|
|
90
141
|
graphQLErrors?: ReadonlyArray<FoirGraphQLErrorItem>;
|
|
142
|
+
partialData?: unknown;
|
|
143
|
+
prunedSelections?: ReadonlyArray<FoirPrunedSelection>;
|
|
91
144
|
});
|
|
92
145
|
/**
|
|
93
146
|
* The code of the first error, if the response carried one.
|
|
@@ -158,6 +211,32 @@ export interface FoirRequestOptions {
|
|
|
158
211
|
* (e.g. `myProfile`). Used by `@foir/sdk/auth`'s post-login profile fetch.
|
|
159
212
|
*/
|
|
160
213
|
bearerToken?: string;
|
|
214
|
+
/**
|
|
215
|
+
* The organization this request acts in, sent as `X-Org-Id`. For a
|
|
216
|
+
* project-scoped key: the server resolves it to an active organization of
|
|
217
|
+
* the key's own project (else `NOT_FOUND`) and binds the request to it — a
|
|
218
|
+
* key already confined to an organization may only name that one (else
|
|
219
|
+
* `PERMISSION_DENIED`). See `FoirClientConfig.orgId` for the full contract.
|
|
220
|
+
*
|
|
221
|
+
* PER CALL, which is the narrower and riskier of the two spellings. The
|
|
222
|
+
* client-level option is the one to reach for: set it once where the
|
|
223
|
+
* organization is resolved and every request from that client carries it,
|
|
224
|
+
* so there is no call left to forget. This one is for the odd request that
|
|
225
|
+
* has to act somewhere else — and a request that omits it is not refused,
|
|
226
|
+
* it reads the project-shared tier, which is a smaller answer rather than an
|
|
227
|
+
* error.
|
|
228
|
+
*
|
|
229
|
+
* ⚠️ It names a SUBTREE. Organizations nest and reach runs downward, so a
|
|
230
|
+
* reseller's id covers every client beneath it. Send the narrowest id that
|
|
231
|
+
* covers what the request is about.
|
|
232
|
+
*/
|
|
233
|
+
orgId?: string;
|
|
234
|
+
/**
|
|
235
|
+
* The organization this request acts in, named by the address of the page it
|
|
236
|
+
* is for, sent as `X-Org-Url`. See `FoirClientConfig.orgUrl` for the full
|
|
237
|
+
* contract. With `orgId` as well, the two must name the same organization.
|
|
238
|
+
*/
|
|
239
|
+
orgUrl?: string;
|
|
161
240
|
/**
|
|
162
241
|
* Called with the `X-RateLimit-*` headers of every response, successful or
|
|
163
242
|
* not, and with `undefined` when a response carried none.
|
|
@@ -180,7 +259,8 @@ export interface FoirRequestOptions {
|
|
|
180
259
|
* they would end up accepting subtly different things.
|
|
181
260
|
*/
|
|
182
261
|
export type FoirTokenProvider = () => string | null | undefined | Promise<string | null | undefined>;
|
|
183
|
-
|
|
262
|
+
/** A request made with an api key, optionally for a signed-in customer. */
|
|
263
|
+
export interface FoirKeyHeaderInput {
|
|
184
264
|
apiKey: string;
|
|
185
265
|
/**
|
|
186
266
|
* Opt into the draft *schema* channel (`X-Foir-Schema-Channel: draft`) so
|
|
@@ -192,14 +272,62 @@ export interface FoirHeaderInput {
|
|
|
192
272
|
*/
|
|
193
273
|
draftChannel?: boolean;
|
|
194
274
|
bearerToken?: string;
|
|
275
|
+
/**
|
|
276
|
+
* The organization the request acts in (`X-Org-Id`). Set from
|
|
277
|
+
* `FoirClientConfig.orgId` / `FoirRequestOptions.orgId`; a caller who
|
|
278
|
+
* spells the header in `extra` still wins, because `extra` is applied last.
|
|
279
|
+
*/
|
|
280
|
+
orgId?: string;
|
|
281
|
+
/**
|
|
282
|
+
* The organization the request acts in, named by a page URL (`X-Org-Url`).
|
|
283
|
+
* Set from `FoirClientConfig.orgUrl` / `FoirRequestOptions.orgUrl`.
|
|
284
|
+
*/
|
|
285
|
+
orgUrl?: string;
|
|
286
|
+
dispatchToken?: never;
|
|
287
|
+
dispatchContext?: never;
|
|
195
288
|
/** Caller-supplied extras, applied last so a caller can override. */
|
|
196
289
|
extra?: Readonly<Record<string, string>>;
|
|
197
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* A request made AS an operation, with the dispatch token its endpoint was
|
|
293
|
+
* handed.
|
|
294
|
+
*
|
|
295
|
+
* The token is the whole credential. api-public takes the scoped path when
|
|
296
|
+
* `X-Foir-Token` is present and never reads `x-api-key` on it, and the
|
|
297
|
+
* organization, the customer and the schema channel all come from the token's
|
|
298
|
+
* own claims, so none of the key-side inputs has anything to say here.
|
|
299
|
+
*/
|
|
300
|
+
export interface FoirDispatchHeaderInput {
|
|
301
|
+
/** Sent as `X-Foir-Token`. */
|
|
302
|
+
dispatchToken: string;
|
|
303
|
+
/** The dispatch's `X-Foir-Context`, forwarded as it arrived. */
|
|
304
|
+
dispatchContext?: string;
|
|
305
|
+
apiKey?: never;
|
|
306
|
+
draftChannel?: never;
|
|
307
|
+
bearerToken?: never;
|
|
308
|
+
orgId?: never;
|
|
309
|
+
orgUrl?: never;
|
|
310
|
+
/**
|
|
311
|
+
* Caller-supplied extras, applied last. They may not name a credential
|
|
312
|
+
* header: see {@link refuseCredentialHeaders}.
|
|
313
|
+
*/
|
|
314
|
+
extra?: Readonly<Record<string, string>>;
|
|
315
|
+
}
|
|
316
|
+
export type FoirHeaderInput = FoirKeyHeaderInput | FoirDispatchHeaderInput;
|
|
317
|
+
/**
|
|
318
|
+
* Refuse extra headers that would add a second credential to a request made as
|
|
319
|
+
* an operation. Throws naming the header; returns nothing when there is none.
|
|
320
|
+
*/
|
|
321
|
+
export declare function refuseCredentialHeaders(extra: Readonly<Record<string, string>> | undefined): void;
|
|
198
322
|
/**
|
|
199
323
|
* The exact header set every Foir data-plane request carries. Single
|
|
200
324
|
* definition on purpose: `/graphql` and `/cdn/graphql` authenticate identically,
|
|
201
325
|
* and a header that exists on one surface but not the other is a per-surface
|
|
202
326
|
* authorization difference nobody would notice until it mattered.
|
|
327
|
+
*
|
|
328
|
+
* Two credentials, and a request carries exactly one: an api key (with its
|
|
329
|
+
* optional customer bearer, organization and channel), or an operation's
|
|
330
|
+
* dispatch token. A dispatch request never writes `x-api-key`.
|
|
203
331
|
*/
|
|
204
332
|
export declare function buildFoirHeaders(input: FoirHeaderInput): Record<string, string>;
|
|
205
333
|
/**
|
|
@@ -218,17 +346,42 @@ export declare function buildFoirHeaders(input: FoirHeaderInput): Record<string,
|
|
|
218
346
|
* accepted. `data/transport.ts` re-exports it under its published name.
|
|
219
347
|
*/
|
|
220
348
|
export declare function foirApiEndpoint(apiUrl: string, path: `/${string}`): string;
|
|
349
|
+
/**
|
|
350
|
+
* Foir DISCARDED a credential you sent, and served the request without it.
|
|
351
|
+
*
|
|
352
|
+
* A customer JWT is optional: if it fails to verify, the read is served as an
|
|
353
|
+
* ordinary anonymous one rather than refused, because plenty of reads
|
|
354
|
+
* legitimately have nobody signed in. That is the right behaviour and it has a
|
|
355
|
+
* sharp edge — a lapsed token and a genuinely anonymous request produce the
|
|
356
|
+
* same 200, so the failure surfaces as WRONG CONTENT rather than as an error.
|
|
357
|
+
* This block is how you find out.
|
|
358
|
+
*
|
|
359
|
+
* Present only when a token was actually sent and rejected. Its presence is
|
|
360
|
+
* always a signal.
|
|
361
|
+
*/
|
|
362
|
+
export interface FoirAuthDowngrade {
|
|
363
|
+
/** Always `"rejected"`. */
|
|
364
|
+
customerToken: string;
|
|
365
|
+
/**
|
|
366
|
+
* `expired` — re-exchange and retry. `invalid` — not a customer token for
|
|
367
|
+
* this environment. `wrong_project` — the token and the api-key name
|
|
368
|
+
* different projects.
|
|
369
|
+
*/
|
|
370
|
+
reason: 'expired' | 'invalid' | 'wrong_project';
|
|
371
|
+
/** The authority the request actually ran under. */
|
|
372
|
+
servedAs: string;
|
|
373
|
+
}
|
|
221
374
|
export interface FoirResponseEnvelope<T> {
|
|
222
375
|
data?: T;
|
|
223
376
|
errors?: Array<FoirGraphQLErrorItem>;
|
|
377
|
+
extensions?: {
|
|
378
|
+
auth?: FoirAuthDowngrade;
|
|
379
|
+
pruned?: {
|
|
380
|
+
selections?: Array<FoirPrunedSelection>;
|
|
381
|
+
};
|
|
382
|
+
[key: string]: unknown;
|
|
383
|
+
};
|
|
224
384
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
* GraphQL can return partial data alongside errors; when data is present the
|
|
229
|
-
* errors are logged and the data returned (callers handle nulls), and we only
|
|
230
|
-
* throw when there is no data at all. Both Foir data-plane routes — `/graphql`
|
|
231
|
-
* and `/cdn/graphql` — hand their document to the same executor, so the two
|
|
232
|
-
* return this envelope and get the same treatment.
|
|
233
|
-
*/
|
|
234
|
-
export declare function readFoirEnvelope<T>(json: FoirResponseEnvelope<T>): T;
|
|
385
|
+
export declare function readFoirEnvelope<T>(json: FoirResponseEnvelope<T>, options?: {
|
|
386
|
+
strict?: boolean;
|
|
387
|
+
}): T;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foir/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Official Foir SDK — editor iframe client + server-side platform helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
"types": "./dist/data/index.d.ts",
|
|
26
26
|
"import": "./dist/data.js"
|
|
27
27
|
},
|
|
28
|
+
"./live": {
|
|
29
|
+
"types": "./dist/live/index.d.ts",
|
|
30
|
+
"import": "./dist/live.js"
|
|
31
|
+
},
|
|
32
|
+
"./live/react": {
|
|
33
|
+
"types": "./dist/live/react/index.d.ts",
|
|
34
|
+
"import": "./dist/live-react.js"
|
|
35
|
+
},
|
|
28
36
|
"./media": {
|
|
29
37
|
"types": "./dist/media/index.d.ts",
|
|
30
38
|
"import": "./dist/media.js"
|
|
@@ -157,7 +165,7 @@
|
|
|
157
165
|
"node": ">=18.0.0"
|
|
158
166
|
},
|
|
159
167
|
"scripts": {
|
|
160
|
-
"build": "tsup && tsc -p tsconfig.build.json && node scripts/assert-export-types.mjs && node scripts/assert-public-exports.mjs",
|
|
168
|
+
"build": "node scripts/clean-dist.mjs && tsup && tsc -p tsconfig.build.json && node scripts/assert-export-types.mjs && node scripts/assert-public-exports.mjs",
|
|
161
169
|
"snapshot:exports": "node scripts/assert-public-exports.mjs --update",
|
|
162
170
|
"dev": "node scripts/dev-watch.mjs",
|
|
163
171
|
"check-types": "tsc --noEmit",
|