@foir/sdk 0.10.0 → 0.12.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 (52) hide show
  1. package/CHANGELOG.md +353 -2
  2. package/README.md +10 -2
  3. package/dist/app/client.d.ts +371 -0
  4. package/dist/app/contract/index.d.ts +23 -0
  5. package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +1465 -0
  6. package/dist/app/contract/proto/apps/v1/discovery_pb.d.ts +678 -0
  7. package/dist/app/dispatch.d.ts +64 -0
  8. package/dist/app/errors.d.ts +50 -0
  9. package/dist/app/index.d.ts +40 -0
  10. package/dist/app/manifest.d.ts +56 -0
  11. package/dist/app/naming.d.ts +31 -0
  12. package/dist/app/record-key.d.ts +17 -0
  13. package/dist/app/ticket.d.ts +37 -0
  14. package/dist/app/vocabulary.generated.d.ts +20 -0
  15. package/dist/app-contract.js +124 -0
  16. package/dist/app.js +998 -0
  17. package/dist/auth/auth.d.ts +24 -6
  18. package/dist/auth/index.d.ts +2 -1
  19. package/dist/auth/ops.d.ts +8 -0
  20. package/dist/auth.js +230 -60
  21. package/dist/client.js +80 -15
  22. package/dist/codegen/preset.d.ts +3 -2
  23. package/dist/data/builtins.d.ts +17 -1
  24. package/dist/data/client.d.ts +10 -8
  25. package/dist/data/document.d.ts +13 -0
  26. package/dist/data/index.d.ts +15 -5
  27. package/dist/data/manifest.d.ts +31 -3
  28. package/dist/data/schema.d.ts +16 -9
  29. package/dist/data/selection.d.ts +15 -0
  30. package/dist/data/transport.d.ts +277 -23
  31. package/dist/data/ws-transport.d.ts +8 -2
  32. package/dist/data.js +313 -72
  33. package/dist/graphql/client.d.ts +1 -1
  34. package/dist/graphql/foir-request.d.ts +1 -1
  35. package/dist/index.js +0 -9
  36. package/dist/live/drift.d.ts +9 -10
  37. package/dist/live.js +1 -1
  38. package/dist/next.js +158 -37
  39. package/dist/placement/index.d.ts +129 -0
  40. package/dist/placement.js +144 -0
  41. package/dist/push.js +212 -50
  42. package/dist/react.js +158 -37
  43. package/dist/server/callback-client.d.ts +30 -9
  44. package/dist/server/continue-run.d.ts +56 -0
  45. package/dist/server/index.d.ts +6 -3
  46. package/dist/server/operation-types.d.ts +114 -65
  47. package/dist/server/read-dispatch.d.ts +53 -0
  48. package/dist/server/require-scoped-token.d.ts +71 -7
  49. package/dist/server/verify-scoped-token.d.ts +109 -3
  50. package/dist/server.js +566 -138
  51. package/dist/transport/foir-http.d.ts +103 -5
  52. package/package.json +24 -6
@@ -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` + a `sk_…`
34
- * `PLATFORM_PREVIEW_API_KEY` with `drafts:read` instead of the default env.
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 `sk_` preview key when
42
- * `FOIR_PREVIEW=true` (it is the one with `drafts:read`), the `pk_` key
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
  */
@@ -210,6 +211,12 @@ export interface FoirRequestOptions {
210
211
  * (e.g. `myProfile`). Used by `@foir/sdk/auth`'s post-login profile fetch.
211
212
  */
212
213
  bearerToken?: string;
214
+ /**
215
+ * Refuse an answer served without the bearer this request sent: throw
216
+ * {@link FoirAuthDowngradedError} instead of returning the anonymous data.
217
+ * `authedRequest` sets it, so it can refresh and ask again.
218
+ */
219
+ rejectDowngrade?: boolean;
213
220
  /**
214
221
  * The organization this request acts in, sent as `X-Org-Id`. For a
215
222
  * project-scoped key: the server resolves it to an active organization of
@@ -230,6 +237,12 @@ export interface FoirRequestOptions {
230
237
  * covers what the request is about.
231
238
  */
232
239
  orgId?: string;
240
+ /**
241
+ * The organization this request acts in, named by the address of the page it
242
+ * is for, sent as `X-Org-Url`. See `FoirClientConfig.orgUrl` for the full
243
+ * contract. With `orgId` as well, the two must name the same organization.
244
+ */
245
+ orgUrl?: string;
233
246
  /**
234
247
  * Called with the `X-RateLimit-*` headers of every response, successful or
235
248
  * not, and with `undefined` when a response carried none.
@@ -252,7 +265,8 @@ export interface FoirRequestOptions {
252
265
  * they would end up accepting subtly different things.
253
266
  */
254
267
  export type FoirTokenProvider = () => string | null | undefined | Promise<string | null | undefined>;
255
- export interface FoirHeaderInput {
268
+ /** A request made with an api key, optionally for a signed-in customer. */
269
+ export interface FoirKeyHeaderInput {
256
270
  apiKey: string;
257
271
  /**
258
272
  * Opt into the draft *schema* channel (`X-Foir-Schema-Channel: draft`) so
@@ -270,15 +284,75 @@ export interface FoirHeaderInput {
270
284
  * spells the header in `extra` still wins, because `extra` is applied last.
271
285
  */
272
286
  orgId?: string;
287
+ /**
288
+ * The organization the request acts in, named by a page URL (`X-Org-Url`).
289
+ * Set from `FoirClientConfig.orgUrl` / `FoirRequestOptions.orgUrl`.
290
+ */
291
+ orgUrl?: string;
292
+ /**
293
+ * The address of the visitor a server rendering pages is calling for
294
+ * (`X-Foir-Visitor-IP`). Set from `FoirClientConfig.visitor`.
295
+ */
296
+ visitorAddress?: string;
297
+ dispatchToken?: never;
298
+ dispatchContext?: never;
273
299
  /** Caller-supplied extras, applied last so a caller can override. */
274
300
  extra?: Readonly<Record<string, string>>;
275
301
  }
302
+ /**
303
+ * A request made AS an operation, with the dispatch token its endpoint was
304
+ * handed.
305
+ *
306
+ * The token is the whole credential. api-public takes the scoped path when
307
+ * `X-Foir-Token` is present and never reads `x-api-key` on it, and the
308
+ * organization, the customer and the schema channel all come from the token's
309
+ * own claims, so none of the key-side inputs has anything to say here.
310
+ */
311
+ export interface FoirDispatchHeaderInput {
312
+ /** Sent as `X-Foir-Token`. */
313
+ dispatchToken: string;
314
+ /** The dispatch's `X-Foir-Context`, forwarded as it arrived. */
315
+ dispatchContext?: string;
316
+ apiKey?: never;
317
+ draftChannel?: never;
318
+ bearerToken?: never;
319
+ orgId?: never;
320
+ orgUrl?: never;
321
+ /**
322
+ * Caller-supplied extras, applied last. They may not name a credential
323
+ * header: see {@link refuseCredentialHeaders}.
324
+ */
325
+ extra?: Readonly<Record<string, string>>;
326
+ }
327
+ export type FoirHeaderInput = FoirKeyHeaderInput | FoirDispatchHeaderInput;
328
+ /**
329
+ * Refuse extra headers that would add a second credential to a request made as
330
+ * an operation. Throws naming the header; returns nothing when there is none.
331
+ */
332
+ export declare function refuseCredentialHeaders(extra: Readonly<Record<string, string>> | undefined): void;
276
333
  /**
277
334
  * The exact header set every Foir data-plane request carries. Single
278
335
  * definition on purpose: `/graphql` and `/cdn/graphql` authenticate identically,
279
336
  * and a header that exists on one surface but not the other is a per-surface
280
337
  * authorization difference nobody would notice until it mattered.
338
+ *
339
+ * Two credentials, and a request carries exactly one: an api key (with its
340
+ * optional customer bearer, organization and channel), or an operation's
341
+ * dispatch token. A dispatch request never writes `x-api-key`.
342
+ */
343
+ /** The header a server names the visitor it calls for in. */
344
+ export declare const FOIR_VISITOR_IP_HEADER = "X-Foir-Visitor-IP";
345
+ /**
346
+ * The address of the visitor a server rendering pages is calling for, or a
347
+ * function that resolves it for each request.
348
+ *
349
+ * The SDK does not read it off a request, because which request header holds
350
+ * the visitor's real address, and whether a visitor can forge it, depends on
351
+ * what sits in front of your server. Your host says which one its edge sets and
352
+ * strips; that value, and only that, belongs here. A function that yields
353
+ * nothing sends nothing, and the API counts the request as your server's own.
281
354
  */
355
+ export type FoirVisitorSource = string | (() => string | null | undefined | Promise<string | null | undefined>);
282
356
  export declare function buildFoirHeaders(input: FoirHeaderInput): Record<string, string>;
283
357
  /**
284
358
  * Build a `/v1/...` URL from whatever the caller put in `apiUrl`.
@@ -321,6 +395,29 @@ export interface FoirAuthDowngrade {
321
395
  /** The authority the request actually ran under. */
322
396
  servedAs: string;
323
397
  }
398
+ /**
399
+ * A request that sent a customer token was served without it.
400
+ *
401
+ * Thrown only to a caller that asked (`rejectDowngrade`): one that sent the
402
+ * token because it wants the signed-in answer, and would otherwise be handed the
403
+ * anonymous one as though nothing had happened. `downgrade.reason` says whether
404
+ * refreshing could help: `expired` can be refreshed, `invalid` and
405
+ * `wrong_project` cannot.
406
+ */
407
+ export declare class FoirAuthDowngradedError extends FoirRequestError {
408
+ readonly downgrade: FoirAuthDowngrade;
409
+ constructor(downgrade: FoirAuthDowngrade);
410
+ }
411
+ /**
412
+ * The customer's session is over: its token was rejected and could not be
413
+ * refreshed. It is what a signed-in request gets in place of the anonymous
414
+ * answer. Send the customer to sign in again.
415
+ */
416
+ export declare class FoirSessionExpiredError extends FoirRequestError {
417
+ constructor(options?: {
418
+ cause?: unknown;
419
+ });
420
+ }
324
421
  export interface FoirResponseEnvelope<T> {
325
422
  data?: T;
326
423
  errors?: Array<FoirGraphQLErrorItem>;
@@ -334,4 +431,5 @@ export interface FoirResponseEnvelope<T> {
334
431
  }
335
432
  export declare function readFoirEnvelope<T>(json: FoirResponseEnvelope<T>, options?: {
336
433
  strict?: boolean;
434
+ rejectDowngrade?: boolean;
337
435
  }): T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/sdk",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Official Foir SDK — editor iframe client + server-side platform helpers",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -45,6 +45,18 @@
45
45
  "types": "./dist/auth/index.d.ts",
46
46
  "import": "./dist/auth.js"
47
47
  },
48
+ "./placement": {
49
+ "types": "./dist/placement/index.d.ts",
50
+ "import": "./dist/placement.js"
51
+ },
52
+ "./app": {
53
+ "types": "./dist/app/index.d.ts",
54
+ "import": "./dist/app.js"
55
+ },
56
+ "./app/contract": {
57
+ "types": "./dist/app/contract/index.d.ts",
58
+ "import": "./dist/app-contract.js"
59
+ },
48
60
  "./auth/session": {
49
61
  "types": "./dist/auth/session-client.d.ts",
50
62
  "import": "./dist/auth-session.js"
@@ -107,6 +119,7 @@
107
119
  "@graphql-typed-document-node/core": "3.2.0"
108
120
  },
109
121
  "peerDependencies": {
122
+ "@bufbuild/protobuf": "^2.12.0",
110
123
  "@graphql-codegen/cli": "^5.0.0",
111
124
  "@graphql-codegen/client-preset": "^4.0.0",
112
125
  "blurhash": "^2.0.0",
@@ -117,6 +130,9 @@
117
130
  "yjs": "^13.6.0"
118
131
  },
119
132
  "peerDependenciesMeta": {
133
+ "@bufbuild/protobuf": {
134
+ "optional": true
135
+ },
120
136
  "@graphql-codegen/cli": {
121
137
  "optional": true
122
138
  },
@@ -143,6 +159,7 @@
143
159
  }
144
160
  },
145
161
  "devDependencies": {
162
+ "@bufbuild/protobuf": "^2.0.0",
146
163
  "@graphql-codegen/cli": "^5.0.7",
147
164
  "@testing-library/react": "^16.2.0",
148
165
  "@types/react": "^19.2.0",
@@ -159,18 +176,19 @@
159
176
  "vitest": "^3.2.4",
160
177
  "ws": "^8.18.0",
161
178
  "y-websocket": "^3.0.0",
162
- "yjs": "^13.6.30"
179
+ "yjs": "^13.6.30",
180
+ "@foir/proto-ts": "0.109.0"
163
181
  },
164
182
  "engines": {
165
183
  "node": ">=18.0.0"
166
184
  },
167
185
  "scripts": {
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",
186
+ "build": "node scripts/generate-app-vocabulary.mjs && node scripts/clean-dist.mjs && tsup && tsc -p tsconfig.build.json && node scripts/bundle-app-contract-types.mjs && node scripts/assert-export-types.mjs && node scripts/assert-public-exports.mjs",
169
187
  "snapshot:exports": "node scripts/assert-public-exports.mjs --update",
170
188
  "dev": "node scripts/dev-watch.mjs",
171
- "check-types": "tsc --noEmit",
172
- "test": "vitest run",
173
- "test:watch": "vitest watch",
189
+ "check-types": "node scripts/generate-app-vocabulary.mjs && tsc --noEmit",
190
+ "test": "node scripts/generate-app-vocabulary.mjs && vitest run",
191
+ "test:watch": "node scripts/generate-app-vocabulary.mjs && vitest watch",
174
192
  "release:patch": "pnpm version patch",
175
193
  "release:minor": "pnpm version minor",
176
194
  "release:major": "pnpm version major"