@foir/sdk 0.8.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.
- package/CHANGELOG.md +550 -28
- package/README.md +111 -0
- package/dist/auth/auth.d.ts +72 -1
- package/dist/auth/hosted.d.ts +93 -4
- package/dist/auth/index.d.ts +4 -4
- package/dist/auth/operations.d.ts +84 -0
- package/dist/auth/ops.d.ts +10 -0
- package/dist/auth.js +138 -19
- package/dist/client.js +63 -6
- 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/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 +131 -12
- package/dist/data/client.d.ts +82 -1
- package/dist/data/decimal.d.ts +212 -0
- package/dist/data/document.d.ts +1 -1
- package/dist/data/index.d.ts +11 -9
- package/dist/data/manifest.d.ts +33 -6
- package/dist/data/schema.d.ts +2 -2
- package/dist/data/subscriptions.d.ts +36 -10
- package/dist/data/transport.d.ts +120 -3
- package/dist/data.js +377 -16
- package/dist/graphql/cdn-route.d.ts +1 -1
- package/dist/graphql/foir-request.d.ts +1 -1
- package/dist/live/coalesce.d.ts +57 -0
- package/dist/live/drift.d.ts +59 -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 +179 -6
- package/dist/push.js +81 -8
- package/dist/react.js +76 -5
- package/dist/realtime/index.d.ts +4 -5
- package/dist/server/events-client.d.ts +54 -4
- package/dist/server/index.d.ts +3 -2
- package/dist/server/require-scoped-token.d.ts +1 -1
- package/dist/server.js +454 -2
- package/dist/transport/foir-http.d.ts +113 -10
- package/package.json +10 -2
package/dist/data/schema.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* THE `FoirSchema` CONTRACT.
|
|
3
3
|
*
|
|
4
4
|
* `createFoirClient<FoirSchema>()` is parameterised by a type that `foir types`
|
|
5
|
-
*
|
|
5
|
+
* emits from `GET /v1/manifest`. This file is that contract: the
|
|
6
6
|
* shape the generator writes and the shape the client reads. It is the only
|
|
7
7
|
* coupling between the two, so it is written down here rather than living in
|
|
8
8
|
* the generator's head.
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
* 8. **`raw` and `ns` are reserved top-level names.** They are the escape hatch
|
|
162
162
|
* and the namespace accessor on the client object. A model whose key is
|
|
163
163
|
* `raw` or `ns` would collide; the server should reject those model keys at
|
|
164
|
-
* create time (
|
|
164
|
+
* create time (a known limit).
|
|
165
165
|
*
|
|
166
166
|
* 9. **`models` and `namespaces` must be object type LITERALS, not named
|
|
167
167
|
* `interface`s.** TypeScript gives an implicit index signature to object
|
|
@@ -38,13 +38,17 @@
|
|
|
38
38
|
* carries a resolved selection tree, which `subscription-document.ts` prints
|
|
39
39
|
* with the same walk the query path prints models with.
|
|
40
40
|
*
|
|
41
|
-
* A
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
41
|
+
* A client built with `foirSubscriptions` makes no request at all: `client.ts`
|
|
42
|
+
* hands this an already-resolved descriptor instead of the fetcher, and the
|
|
43
|
+
* printer cannot tell the difference — it is the same shape either way, which is
|
|
44
|
+
* why nothing in this file changed when that landed.
|
|
45
|
+
*
|
|
46
|
+
* Without them it fetches once, which is what every client did before `foir
|
|
47
|
+
* types` emitted subscription trees and what a module generated before that
|
|
48
|
+
* still does. It is ONE fetch, memoised across every subscription and shared
|
|
49
|
+
* with the query path, sitting behind a long-lived socket. `client.ts` rejects a
|
|
50
|
+
* round trip in front of every query for good reason; in front of a socket that
|
|
51
|
+
* will live for minutes it is noise.
|
|
48
52
|
*/
|
|
49
53
|
import type { FoirManifest } from './manifest';
|
|
50
54
|
import type { FoirClientConfig } from './transport';
|
|
@@ -88,8 +92,12 @@ export interface FoirJobProgressEvent {
|
|
|
88
92
|
/** `operationExecution` / `operation{Op}Execution` — one execution's progress. */
|
|
89
93
|
export interface FoirOperationExecutionEvent {
|
|
90
94
|
id: string;
|
|
91
|
-
/**
|
|
92
|
-
|
|
95
|
+
/**
|
|
96
|
+
* The same spelling `operationExecution` and `operationExecutions` return for
|
|
97
|
+
* the run, so one comparison serves a read and an event. The socket carries
|
|
98
|
+
* the platform's lowercase value; this client hands it over as the enum name.
|
|
99
|
+
*/
|
|
100
|
+
status: 'PENDING' | 'DISPATCHED' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMED_OUT';
|
|
93
101
|
/** 0-100; null on terminal events. */
|
|
94
102
|
progressPct?: number | null;
|
|
95
103
|
progressMessage?: string | null;
|
|
@@ -117,6 +125,24 @@ export type FoirExecutionSubscribeOptions = FoirSubscriptionHandlers<FoirOperati
|
|
|
117
125
|
*/
|
|
118
126
|
operation?: string;
|
|
119
127
|
};
|
|
128
|
+
/**
|
|
129
|
+
* What `onChange` takes: the handlers, plus the optional resume cursor.
|
|
130
|
+
*
|
|
131
|
+
* `after` is the highest `seq` this consumer has already applied. Passing it
|
|
132
|
+
* makes the stream cursor-resumable — the server replays what came after it
|
|
133
|
+
* from the durable log before continuing live, so a message lost anywhere
|
|
134
|
+
* upstream is repaired by the next connect rather than left as a permanently
|
|
135
|
+
* stale cache entry. Omitting it is the stream as it has always been: live from
|
|
136
|
+
* the moment you connect.
|
|
137
|
+
*
|
|
138
|
+
* The two refusals it can produce — a cursor older than the retention window,
|
|
139
|
+
* or one further behind than the surface will page — arrive on `onError` with a
|
|
140
|
+
* machine-readable code, and both mean the cache must be thrown away rather
|
|
141
|
+
* than partially repaired. `@foir/sdk/live` classifies them.
|
|
142
|
+
*/
|
|
143
|
+
export type FoirChangeSubscribeOptions = FoirSubscriptionHandlers<FoirRecordChangedEvent> & {
|
|
144
|
+
after?: number;
|
|
145
|
+
};
|
|
120
146
|
/** The subscriptions a model accessor carries. */
|
|
121
147
|
export interface FoirModelSubscriptions {
|
|
122
148
|
/**
|
|
@@ -125,7 +151,7 @@ export interface FoirModelSubscriptions {
|
|
|
125
151
|
* The socket refuses a model this credential may not read, but the model is
|
|
126
152
|
* not in `FoirSchema` in that case, so the refusal is a type error first.
|
|
127
153
|
*/
|
|
128
|
-
onChange(
|
|
154
|
+
onChange(options: FoirChangeSubscribeOptions): FoirSubscription;
|
|
129
155
|
}
|
|
130
156
|
/**
|
|
131
157
|
* The subscriptions one namespace carries, by name.
|
package/dist/data/transport.d.ts
CHANGED
|
@@ -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
|
|
440
|
+
export declare function executeFoirRawDocument<T>(config: FoirClientConfig, document: string, mutation: boolean, variables?: Record<string, unknown>, options?: {
|
|
441
|
+
strict?: boolean;
|
|
442
|
+
}): Promise<T>;
|