@foir/sdk 0.9.0 → 0.10.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 (57) hide show
  1. package/CHANGELOG.md +497 -27
  2. package/README.md +111 -0
  3. package/dist/auth/auth.d.ts +72 -1
  4. package/dist/auth/hosted.d.ts +71 -4
  5. package/dist/auth/index.d.ts +3 -3
  6. package/dist/auth/operations.d.ts +84 -0
  7. package/dist/auth/ops.d.ts +10 -0
  8. package/dist/auth.js +127 -19
  9. package/dist/client.js +63 -6
  10. package/dist/codegen/block-registry.d.ts +1 -1
  11. package/dist/codegen/block-scaffold.d.ts +1 -1
  12. package/dist/codegen/index.d.ts +1 -1
  13. package/dist/codegen/item-registry.d.ts +1 -1
  14. package/dist/codegen/item-scaffold.d.ts +1 -1
  15. package/dist/content/content-renderer.d.ts +4 -5
  16. package/dist/content/index.d.ts +1 -1
  17. package/dist/content/richtext.d.ts +1 -1
  18. package/dist/data/builtins.d.ts +64 -12
  19. package/dist/data/client.d.ts +82 -1
  20. package/dist/data/decimal.d.ts +212 -0
  21. package/dist/data/document.d.ts +1 -1
  22. package/dist/data/index.d.ts +10 -8
  23. package/dist/data/manifest.d.ts +9 -1
  24. package/dist/data/schema.d.ts +2 -2
  25. package/dist/data/subscriptions.d.ts +36 -10
  26. package/dist/data/transport.d.ts +120 -3
  27. package/dist/data.js +377 -16
  28. package/dist/graphql/cdn-route.d.ts +1 -1
  29. package/dist/graphql/foir-request.d.ts +1 -1
  30. package/dist/live/coalesce.d.ts +57 -0
  31. package/dist/live/drift.d.ts +59 -0
  32. package/dist/live/events.d.ts +13 -0
  33. package/dist/live/headers.d.ts +53 -0
  34. package/dist/live/index.d.ts +54 -0
  35. package/dist/live/keys.d.ts +72 -0
  36. package/dist/live/manager.d.ts +77 -0
  37. package/dist/live/react/index.d.ts +143 -0
  38. package/dist/live/reconcile.d.ts +82 -0
  39. package/dist/live/resync.d.ts +59 -0
  40. package/dist/live/store.d.ts +134 -0
  41. package/dist/live/tagIndex.d.ts +29 -0
  42. package/dist/live/tanstack.d.ts +127 -0
  43. package/dist/live/writetoken.d.ts +86 -0
  44. package/dist/live-react.js +395 -0
  45. package/dist/live.js +845 -0
  46. package/dist/next/cacheTags.d.ts +245 -0
  47. package/dist/next/index.d.ts +7 -0
  48. package/dist/next.js +179 -6
  49. package/dist/push.js +81 -8
  50. package/dist/react.js +76 -5
  51. package/dist/realtime/index.d.ts +4 -5
  52. package/dist/server/events-client.d.ts +54 -4
  53. package/dist/server/index.d.ts +3 -2
  54. package/dist/server/require-scoped-token.d.ts +1 -1
  55. package/dist/server.js +454 -2
  56. package/dist/transport/foir-http.d.ts +113 -10
  57. package/package.json +10 -2
@@ -55,10 +55,11 @@
55
55
  * with more force here — this client is per-credential, so anything cached
56
56
  * across instances is cached across tenants.
57
57
  */
58
- import { type FoirTokenProvider } from '../transport/foir-http';
58
+ import { FoirRequestError, type FoirTokenProvider } from '../transport/foir-http';
59
59
  import type { FoirDocumentPlan, FoirNamespaceSelections, FoirSelections, FoirSelectionUnions } from './document';
60
60
  import type { FoirSchemaDefinition } from './schema';
61
61
  import type { FoirSubjectRegistry } from './subjects';
62
+ import type { FoirManifestSubscription } from './manifest';
62
63
  import type { FoirReconnectPolicy, FoirWebSocketFactory } from './ws-transport';
63
64
  /**
64
65
  * `foir.generated.ts`, whole — the shape a namespace import of it has.
@@ -91,6 +92,19 @@ export interface FoirGeneratedModule<S extends FoirSchemaDefinition = FoirSchema
91
92
  readonly foirNamespaceSelections: FoirNamespaceSelections;
92
93
  /** `foirSelectionUnions` — each union expanded once; a tree references one by name. */
93
94
  readonly foirSelectionUnions: FoirSelectionUnions;
95
+ /**
96
+ * `foirSubscriptions` — the Subscription root fields this credential reaches,
97
+ * with their resolved payload selections. Lets a subscribing client print its
98
+ * document from the module instead of fetching `GET /v1/manifest`.
99
+ *
100
+ * OPTIONAL, and deliberately not in `GENERATED_EXPORTS`. That list is checked
101
+ * strictly — a module missing any of it is refused as stale or hand-assembled
102
+ * — so requiring this one would break every client built against a module
103
+ * generated before it existed, at construction, for a round trip they were
104
+ * already happily paying. Absent means "fetch the manifest", which is exactly
105
+ * what those clients do today.
106
+ */
107
+ readonly foirSubscriptions?: readonly FoirManifestSubscription[];
94
108
  /** Type carrier for `FoirSchema`. Never read: its runtime value is `undefined`. */
95
109
  readonly foirSchemaType: S;
96
110
  }
@@ -117,6 +131,41 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
117
131
  * per-request; a bare string is used as-is.
118
132
  */
119
133
  bearerToken?: string | FoirTokenProvider;
134
+ /**
135
+ * The organization every request from this client acts in, sent as
136
+ * `X-Org-Id`.
137
+ *
138
+ * For the PROJECT credential of a product built on organizations: the
139
+ * back-office, or the multi-tenant app that has just resolved
140
+ * `<sub>.theapp.com` to an organization and now acts inside it. The server
141
+ * resolves the id against the credential's OWN project — an unknown,
142
+ * suspended or another project's organization all answer `NOT_FOUND`, so
143
+ * nothing enumerates — and binds the request to it: reads are confined to
144
+ * that organization plus the project-shared tier, writes are stamped into
145
+ * it, and an organization-owned record created without an explicit owner
146
+ * belongs to it.
147
+ *
148
+ * ⚠️ IT NAMES A SUBTREE, NOT A NODE. Organizations nest, and reach runs
149
+ * downward: name a reseller and the reads include every client beneath it as
150
+ * well as the reseller's own rows. Name the client and you get the client
151
+ * and nothing above or beside it. So the id to send is the NARROWEST one
152
+ * that covers what this request is about — sending the group when you meant
153
+ * one branch is not an error anywhere, it is a wider answer that looks
154
+ * right.
155
+ *
156
+ * A credential already confined to an organization may only name its own
157
+ * (`PERMISSION_DENIED` otherwise), so this cannot steer an organization key
158
+ * elsewhere. And a request that also carries a customer bearer is refused
159
+ * unless that customer stands in the named organization. The storefront
160
+ * pattern is still the confined credential, one per organization; this is
161
+ * for the side of the product that holds the project key and has to choose.
162
+ *
163
+ * Routing: a request naming an organization always goes to the origin
164
+ * endpoint, never the shared one. The answer is one organization's, and a
165
+ * cache in front of the caller keys by URL, where this header does not
166
+ * appear.
167
+ */
168
+ orgId?: string;
120
169
  /**
121
170
  * The generated module, whole — **the documented way to wire a client**.
122
171
  *
@@ -206,10 +255,53 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
206
255
  * Carried by `generated`; set it here only to override that.
207
256
  */
208
257
  schemaVersion?: number;
258
+ /**
259
+ * Subscription descriptors, from the generated module's `foirSubscriptions`.
260
+ *
261
+ * Carried by `generated`; set it here only to override that. When present a
262
+ * subscribing client prints from these and never fetches `GET /v1/manifest`;
263
+ * when absent it fetches once, as it always has.
264
+ */
265
+ subscriptions?: readonly FoirManifestSubscription[];
209
266
  /** Set `false` to suppress the drift check even when `schemaVersion` is set. */
210
267
  checkSchemaDrift?: boolean;
211
268
  /** Called instead of `console.warn` when drift is detected. */
212
269
  onSchemaDrift?: (drift: FoirSchemaDrift) => void;
270
+ /**
271
+ * Refuse typed calls once drift is detected, instead of serving the stale
272
+ * shape. **On by default**; pass `false` to opt out.
273
+ *
274
+ * The asymmetry this closes: an unknown ARGUMENT is refused client-side
275
+ * before the server sees it, while a field the server has and the generated
276
+ * selection does not is simply not requested. The second is the common case
277
+ * when a schema evolves, and it surfaces as absent data rather than as a
278
+ * tooling problem — so it is the one that needs to be loud.
279
+ *
280
+ * It is the default rather than an opt-in because the thing it prevents is
281
+ * SILENT. An opt-in guard against a silent failure only protects the people
282
+ * who already suspected the failure existed, and the warning it replaces is
283
+ * one more line to scroll past.
284
+ *
285
+ * Two things it deliberately does not do.
286
+ *
287
+ * It does not fail the FIRST call. Drift is only known once the manifest
288
+ * comes back, and that request is made off the response path after a call has
289
+ * already been served. A client that refused retroactively would have to
290
+ * block every first call on a second round trip, which is the cost the
291
+ * warning was designed to avoid.
292
+ *
293
+ * It does not touch `raw`. A hand-written document asks for exactly the
294
+ * fields it names, so it cannot silently omit one — raw is immune to this
295
+ * class of bug by construction, and refusing it would punish the escape
296
+ * hatch people reach for precisely because it is immune.
297
+ *
298
+ * Opt out at the call site, where a reader can see it:
299
+ *
300
+ * ```ts
301
+ * createFoirClient({ strictSchemaDrift: false }) // serve stale, warn only
302
+ * ```
303
+ */
304
+ strictSchemaDrift?: boolean;
213
305
  /**
214
306
  * Set `false` to silence the one-line notice a client prints when it is built
215
307
  * with neither `generated` nor `selections` and will therefore read its
@@ -267,6 +359,18 @@ export interface FoirSchemaDrift {
267
359
  server: number;
268
360
  channel: string;
269
361
  }
362
+ /**
363
+ * Thrown by a typed call under `strictSchemaDrift` once drift is detected.
364
+ *
365
+ * A subclass rather than a plain `FoirRequestError` so an app can tell "your
366
+ * generated module is stale" from "this request failed", which want different
367
+ * handling: the first is fixed by a command on the developer's machine and
368
+ * will fail identically until they run it, the second is worth a retry.
369
+ */
370
+ export declare class FoirSchemaDriftError extends FoirRequestError {
371
+ readonly drift: FoirSchemaDrift;
372
+ constructor(message: string, drift: FoirSchemaDrift);
373
+ }
270
374
  /**
271
375
  * Build a `/v1/...` URL from whatever the caller put in `apiUrl`.
272
376
  *
@@ -284,7 +388,7 @@ export type FoirLane = 'shared' | 'origin';
284
388
  * Why a call did not take the shared lane. One name per condition the server's
285
389
  * gate refuses on, so a decision can be asserted rather than inferred.
286
390
  */
287
- export type FoirLaneReason = 'key' | 'bearer' | 'operation' | 'channel';
391
+ export type FoirLaneReason = 'key' | 'bearer' | 'org' | 'operation' | 'channel' | 'floor';
288
392
  export interface FoirRouteDecision {
289
393
  lane: FoirLane;
290
394
  /** Set only on the origin lane: the first condition that ruled the shared one out. */
@@ -294,8 +398,19 @@ export interface FoirRouteInput {
294
398
  apiKey: string;
295
399
  /** The RESOLVED bearer, not the provider — a provider that yields null is not a bearer. */
296
400
  bearerToken?: string;
401
+ /** The organization the call acts in (`X-Org-Id`), when it names one. */
402
+ orgId?: string;
297
403
  mutation: boolean;
298
404
  channel?: 'published' | 'draft';
405
+ /**
406
+ * A write token from an earlier mutation, making this read a FLOORED one:
407
+ * serve a cached entry only if it resolved at or above the write's position.
408
+ *
409
+ * The token itself is opaque here — resolving it to a seq is the server's job
410
+ * and needs the event log. All this needs to know is that one is present,
411
+ * because that alone decides the lane.
412
+ */
413
+ writeToken?: string;
299
414
  }
300
415
  /**
301
416
  * Where this call goes.
@@ -322,4 +437,6 @@ export declare function executeFoirDocument<T>(config: FoirClientConfig, plan: F
322
437
  * what it gives up is the single-root-field unwrapping, because a document
323
438
  * written by hand may legitimately have several.
324
439
  */
325
- export declare function executeFoirRawDocument<T>(config: FoirClientConfig, document: string, mutation: boolean, variables?: Record<string, unknown>): Promise<T>;
440
+ export declare function executeFoirRawDocument<T>(config: FoirClientConfig, document: string, mutation: boolean, variables?: Record<string, unknown>, options?: {
441
+ strict?: boolean;
442
+ }): Promise<T>;