@foir/sdk 0.10.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 +222 -0
- package/README.md +10 -2
- package/dist/auth/ops.d.ts +8 -0
- package/dist/auth.js +91 -37
- package/dist/client.js +44 -13
- package/dist/codegen/preset.d.ts +3 -2
- package/dist/data/builtins.d.ts +17 -1
- package/dist/data/client.d.ts +10 -8
- package/dist/data/document.d.ts +13 -0
- package/dist/data/index.d.ts +13 -3
- package/dist/data/manifest.d.ts +31 -3
- package/dist/data/schema.d.ts +16 -9
- package/dist/data/selection.d.ts +15 -0
- package/dist/data/transport.d.ts +244 -22
- package/dist/data/ws-transport.d.ts +8 -2
- package/dist/data.js +236 -70
- package/dist/index.js +0 -9
- package/dist/live/drift.d.ts +9 -10
- package/dist/live.js +1 -1
- package/dist/next.js +88 -35
- package/dist/push.js +91 -37
- package/dist/react.js +88 -35
- package/dist/server/index.d.ts +1 -0
- 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 +19 -0
- package/dist/server.js +162 -22
- package/dist/transport/foir-http.d.ts +55 -5
- package/package.json +1 -1
package/dist/data/document.d.ts
CHANGED
|
@@ -237,6 +237,19 @@ export interface FoirNamespaceCall {
|
|
|
237
237
|
* shape for, and inventing one is what this design exists to stop.
|
|
238
238
|
*/
|
|
239
239
|
export declare function planFoirModelCall(source: FoirSelectionSource, call: FoirCall): FoirDocumentPlan;
|
|
240
|
+
/**
|
|
241
|
+
* A create's arguments with an idempotency key, when the server's create takes
|
|
242
|
+
* one and the caller sent none.
|
|
243
|
+
*
|
|
244
|
+
* The key is made here, before the first attempt, so a retry of the same call
|
|
245
|
+
* carries the same key and the platform answers it with the record the first
|
|
246
|
+
* attempt made rather than making a second. A key made on the server would be
|
|
247
|
+
* new on every attempt, and one derived from the request would merge two
|
|
248
|
+
* deliberate, identical creates into one. A caller that sends its own key keeps
|
|
249
|
+
* it, and an update is left alone: an increment's key is the caller's to
|
|
250
|
+
* choose, since only the caller knows which calls are the same one.
|
|
251
|
+
*/
|
|
252
|
+
export declare function withCreateIdempotencyKey(source: FoirSelectionSource, model: string, op: string, args: Record<string, unknown>): Record<string, unknown>;
|
|
240
253
|
/**
|
|
241
254
|
* Turn a namespace call into a document.
|
|
242
255
|
*
|
package/dist/data/index.d.ts
CHANGED
|
@@ -37,6 +37,15 @@
|
|
|
37
37
|
* const anything = await foir.raw('{ designTokens { name value } }');
|
|
38
38
|
* ```
|
|
39
39
|
*
|
|
40
|
+
* Inside an operation endpoint the client is built from the dispatch instead of
|
|
41
|
+
* a key, and calls Foir as the operation, with the module `foir types
|
|
42
|
+
* --operation <key>` generated for that operation:
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* const { dispatch } = await requireScopedToken(request, { expectedProjectId });
|
|
46
|
+
* const foir = createFoirClient({ apiUrl, dispatch, generated });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
40
49
|
* `FoirSchema` comes from `foir types`. The contract it generates
|
|
41
50
|
* against is written down in `schema.ts` — read that file before changing
|
|
42
51
|
* anything here, because a generator is written against it.
|
|
@@ -44,12 +53,13 @@
|
|
|
44
53
|
export { createFoirClient, createFoirClientFromEnv, graphql } from './client';
|
|
45
54
|
export type { FoirClient, FoirClientCore, FoirModelClient, FoirNamespaceClient, FoirRawOptions, TypedFoirDocument, } from './client';
|
|
46
55
|
export { fetchFoirManifest } from './manifest';
|
|
56
|
+
export type { FoirManifestRequestOptions } from './manifest';
|
|
47
57
|
export type { FoirManifest, FoirManifestComposite, FoirManifestModel, FoirManifestNamespace, FoirManifestOp, FoirManifestOpPayload, FoirManifestRelation, FoirManifestScalar, FoirManifestSelection, FoirManifestSelectionField, FoirManifestSelectionMember, FoirManifestSelectionUnion, FoirManifestStructural, FoirManifestSubscription, FoirManifestSubscriptionArgScope, FoirManifestUnclassifiedField, } from './manifest';
|
|
48
58
|
export { planFoirModelCall, planFoirNamespaceCall } from './document';
|
|
49
59
|
export type { FoirCall, FoirDocumentPlan, FoirNamespaceCall, FoirNamespaceSelections, FoirRecordPath, FoirSelectionModel, FoirSelectionNamespace, FoirSelections, FoirSelectionSource, FoirSelectionUnions, } from './document';
|
|
50
60
|
export { createFoirSubjectRegistry, recordFoirSubjects } from './subjects';
|
|
51
61
|
export type { FoirSubject, FoirSubjectEntry, FoirSubjectOptions, FoirSubjectOrigin, FoirSubjectRegistry, } from './subjects';
|
|
52
|
-
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirSubmitAnonymousNoteArgs, FoirAnonymousNoteReceipt, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
|
|
62
|
+
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirSubmitAnonymousNoteArgs, FoirAnonymousNoteReceipt, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirRecordOwner, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
|
|
53
63
|
/**
|
|
54
64
|
* Pre-rename spellings. The `<Name>Block` wrappers and the `<Name>Value`
|
|
55
65
|
* split are the same thing as the types above — an item IS its value — so each
|
|
@@ -61,14 +71,14 @@ export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAcces
|
|
|
61
71
|
*/
|
|
62
72
|
export type { FileBlock, GeoPointValue, ImageBlock, ImageValue, ReferenceBlock, ReferenceValue, RichtextBlock, RichtextValue, VideoBlock, VideoValue, } from './builtins';
|
|
63
73
|
export { FoirSchemaDriftError, foirDataEndpoint, foirRoute } from './transport';
|
|
64
|
-
export type { FoirClientConfig, FoirGeneratedModule, FoirLane, FoirLaneReason, FoirRouteDecision, FoirRouteInput, FoirSchemaDrift, } from './transport';
|
|
74
|
+
export type { FoirClientConfig, FoirDispatchClientConfig, FoirDispatchCredential, FoirGeneratedModule, FoirKeyClientConfig, FoirKeyGeneratedModule, FoirLane, FoirLaneReason, FoirOperationCredential, FoirOperationGeneratedModule, FoirRequestConfig, FoirRouteDecision, FoirRouteInput, FoirSchemaDrift, } from './transport';
|
|
65
75
|
export { foirSocketEndpoint, FoirSocketTransport } from './ws-transport';
|
|
66
76
|
export type { FoirReconnectPolicy, FoirSubscribeRequest, FoirSubscription, FoirSubscriptionHandlers, FoirWebSocketFactory, FoirWebSocketLike, } from './ws-transport';
|
|
67
77
|
export { printSubscriptionDocument } from './subscription-document';
|
|
68
78
|
export type { FoirSubscriptionArgument, PrintSubscriptionDocumentInput, } from './subscription-document';
|
|
69
79
|
export type { FoirChangeSubscribeOptions, FoirExecutionSubscribeOptions, FoirJobProgressEvent, FoirModelSubscriptions, FoirNamespaceSubscriptions, FoirNotificationEvent, FoirOperationExecutionEvent, FoirRecordChangedEvent, } from './subscriptions';
|
|
70
80
|
export type { FoirConnection, FoirFieldsShape, FoirModelDefinition, FoirNamespaceDefinition, FoirNamespaceOpDefinition, FoirOpDefinition, FoirRelationDefinition, FoirReturnDefinition, FoirSchemaDefinition, } from './schema';
|
|
71
|
-
export type { FoirInclude, FoirModelOperation, FoirNamespaceOperation, FoirOmittableKey, FoirOpResult, FoirQuery, FoirRecordResult, } from './selection';
|
|
81
|
+
export type { FoirInclude, FoirModelOperation, FoirNamespaceOperation, FoirOmittableKey, FoirOpResult, FoirQuery, FoirRecordResult, FoirWriteResult, } from './selection';
|
|
72
82
|
export { FoirDecimal, decimal } from './decimal';
|
|
73
83
|
export type { FoirDecimalDivOptions, FoirDecimalInput, FoirDecimalRounding, } from './decimal';
|
|
74
84
|
export { FoirRequestError } from '../transport/foir-http';
|
package/dist/data/manifest.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* (`api-public/internal/schema/manifest.go`) field for field; where the two
|
|
15
15
|
* disagree the platform is right and this file is the bug.
|
|
16
16
|
*/
|
|
17
|
-
import { type
|
|
17
|
+
import { type FoirRequestConfig } from './transport';
|
|
18
18
|
export interface FoirManifestScalar {
|
|
19
19
|
key: string;
|
|
20
20
|
/**
|
|
@@ -273,7 +273,25 @@ export interface FoirManifestNamespace {
|
|
|
273
273
|
ops?: Record<string, FoirManifestOp>;
|
|
274
274
|
}
|
|
275
275
|
export interface FoirManifest {
|
|
276
|
+
/**
|
|
277
|
+
* The invalidation counter for this project and channel. Every write that
|
|
278
|
+
* could change a response moves it, including the many that change nothing
|
|
279
|
+
* here, so it keys the ETag and the server's caches and nothing on the client
|
|
280
|
+
* is pinned to it.
|
|
281
|
+
*/
|
|
276
282
|
schemaVersion: number;
|
|
283
|
+
/**
|
|
284
|
+
* A hash over exactly the sections `foir types` reads, so it moves if and only
|
|
285
|
+
* if regenerating the types would write a different file. The generated
|
|
286
|
+
* module records it, and the drift check compares the two.
|
|
287
|
+
*/
|
|
288
|
+
surfaceVersion: string;
|
|
289
|
+
/**
|
|
290
|
+
* The components `surfaceVersion` is folded from, keyed by where each sits in
|
|
291
|
+
* this body: `models.<key>`, `namespaces.<name>`, `selectionUnions.<name>`,
|
|
292
|
+
* `subscriptions`, `composedFor`. A drift names what moved by comparing these.
|
|
293
|
+
*/
|
|
294
|
+
surfaceHashes: Record<string, string>;
|
|
277
295
|
channel: 'published' | 'draft';
|
|
278
296
|
/**
|
|
279
297
|
* The organisation whose composition this surface is, absent for the
|
|
@@ -441,11 +459,21 @@ export interface FoirManifestSelectionMember {
|
|
|
441
459
|
typeName: string;
|
|
442
460
|
fields: FoirManifestSelectionField[];
|
|
443
461
|
}
|
|
462
|
+
/** What a manifest request asks for beyond the credential's own surface. */
|
|
463
|
+
export interface FoirManifestRequestOptions {
|
|
464
|
+
/**
|
|
465
|
+
* Describe the surface this operation's dispatch token is served instead of
|
|
466
|
+
* the credential's own (`GET /v1/manifest?operation=<key>`). Answered to a
|
|
467
|
+
* credential that can author the operation (one holding `operations:write`)
|
|
468
|
+
* and refused to any other. `foir types --operation` sends it.
|
|
469
|
+
*/
|
|
470
|
+
operation?: string;
|
|
471
|
+
}
|
|
444
472
|
/**
|
|
445
473
|
* Fetch the manifest for the credential in `config`.
|
|
446
474
|
*
|
|
447
475
|
* Exported because two things outside this file need it: `foir types` reads it
|
|
448
476
|
* to generate `FoirSchema`, and the client's drift check reads its
|
|
449
|
-
* `
|
|
477
|
+
* `surfaceVersion`. Both would otherwise hand-roll the same fetch.
|
|
450
478
|
*/
|
|
451
|
-
export declare function fetchFoirManifest(config:
|
|
479
|
+
export declare function fetchFoirManifest(config: FoirRequestConfig, options?: FoirManifestRequestOptions): Promise<FoirManifest>;
|
package/dist/data/schema.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* ```
|
|
20
20
|
*
|
|
21
21
|
* A `.ts` and not a `.d.ts` because the file has a runtime half — the selection
|
|
22
|
-
* trees, `
|
|
22
|
+
* trees, `foirSurfaceVersion`, and `foirSchemaType`, which is what carries
|
|
23
23
|
* `FoirSchema` across the type/value boundary so the client can infer it (a
|
|
24
24
|
* namespace import carries values; an interface erases). `FoirGeneratedModule`
|
|
25
25
|
* in `transport.ts` is the shape those exports have to satisfy, and it requires
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
* // foir.generated.ts — DO NOT EDIT. Generated by `foir types`.
|
|
31
31
|
* import type { FoirSchemaDefinition } from '@foir/sdk/data';
|
|
32
32
|
*
|
|
33
|
-
* /**
|
|
34
|
-
* export const
|
|
33
|
+
* /** The surface these types were generated from. *\/
|
|
34
|
+
* export const foirSurfaceVersion = '6f1c0d2a9b8e4f7c3a5d1e0b2c4f6a8d';
|
|
35
|
+
* export const foirSurfaceHashes: Record<string, string> = { 'models.tilly_block': '…' };
|
|
35
36
|
* export const foirSchemaChannel = 'published';
|
|
36
37
|
*
|
|
37
38
|
* // `<D>` ONLY on the types between the record and a union stopping point —
|
|
@@ -71,7 +72,7 @@
|
|
|
71
72
|
* }
|
|
72
73
|
*
|
|
73
74
|
* export interface FoirSchema extends FoirSchemaDefinition {
|
|
74
|
-
*
|
|
75
|
+
* surfaceVersion: typeof foirSurfaceVersion;
|
|
75
76
|
* channel: 'published';
|
|
76
77
|
* models: {
|
|
77
78
|
* tilly_block: {
|
|
@@ -211,10 +212,16 @@ export interface FoirRelationDefinition {
|
|
|
211
212
|
/** True when the relation itself can resolve null. */
|
|
212
213
|
readonly nullable: boolean;
|
|
213
214
|
}
|
|
214
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* How an op's result wraps (or does not wrap) the model. `write` is a record
|
|
217
|
+
* write's result: the record, and whether the answer is a replay (see
|
|
218
|
+
* `FoirWriteResult`).
|
|
219
|
+
*/
|
|
215
220
|
export type FoirReturnDefinition = {
|
|
216
221
|
readonly kind: 'record';
|
|
217
222
|
readonly nullable: boolean;
|
|
223
|
+
} | {
|
|
224
|
+
readonly kind: 'write';
|
|
218
225
|
} | {
|
|
219
226
|
readonly kind: 'connection';
|
|
220
227
|
} | {
|
|
@@ -282,12 +289,12 @@ export interface FoirNamespaceDefinition {
|
|
|
282
289
|
*/
|
|
283
290
|
export interface FoirSchemaDefinition {
|
|
284
291
|
/**
|
|
285
|
-
* `
|
|
292
|
+
* `surfaceVersion` from the manifest these types were generated from. Kept as
|
|
286
293
|
* a literal so a human reading the generated file sees it, and mirrored by a
|
|
287
|
-
* runtime `
|
|
288
|
-
* read at runtime.
|
|
294
|
+
* runtime `foirSurfaceVersion` const for drift detection, because the type
|
|
295
|
+
* cannot be read at runtime.
|
|
289
296
|
*/
|
|
290
|
-
readonly
|
|
297
|
+
readonly surfaceVersion: string;
|
|
291
298
|
readonly channel: 'published' | 'draft';
|
|
292
299
|
readonly models: Readonly<Record<string, FoirModelDefinition>>;
|
|
293
300
|
readonly namespaces: Readonly<Record<string, FoirNamespaceDefinition>>;
|
package/dist/data/selection.d.ts
CHANGED
|
@@ -428,11 +428,26 @@ type WrapRelation<R extends FoirRelationDefinition, T> = R['list'] extends true
|
|
|
428
428
|
export type FoirRecordResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, Q, D extends number = IncludeDepth, Depth extends number = DepthOf<Q>> = Q extends {
|
|
429
429
|
include: infer I;
|
|
430
430
|
} ? ApplySelection<M, Q, Depth> & IncludedRelations<S, M, I, D, Depth> : ApplySelection<M, Q, Depth>;
|
|
431
|
+
/**
|
|
432
|
+
* What a record write (`create`, `update`, `rename`) answers with.
|
|
433
|
+
*
|
|
434
|
+
* `replayed` is true when the call carried an `idempotencyKey` an earlier
|
|
435
|
+
* attempt of the same write already used: the platform wrote nothing this time
|
|
436
|
+
* and `record` is as that first attempt left it. A caller that acts on the
|
|
437
|
+
* result (a stock count read back after a take, say) checks it, because a
|
|
438
|
+
* replayed answer looks exactly like a fresh one.
|
|
439
|
+
*/
|
|
440
|
+
export interface FoirWriteResult<R> {
|
|
441
|
+
record: R;
|
|
442
|
+
replayed: boolean;
|
|
443
|
+
}
|
|
431
444
|
/** Apply an op's return wrapper to the shaped record. */
|
|
432
445
|
export type FoirOpResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, O extends FoirOpDefinition, Q> = O['returns'] extends {
|
|
433
446
|
kind: 'opaque';
|
|
434
447
|
type: infer T;
|
|
435
448
|
} ? T : O['returns'] extends {
|
|
449
|
+
kind: 'write';
|
|
450
|
+
} ? FoirWriteResult<FoirRecordResult<S, M, Q>> : O['returns'] extends {
|
|
436
451
|
kind: 'connection';
|
|
437
452
|
} ? FoirConnection<FoirRecordResult<S, M, Q>> : O['returns'] extends {
|
|
438
453
|
kind: 'list';
|
package/dist/data/transport.d.ts
CHANGED
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
* anything else — `sk_`, a customer bearer, a mutation, the draft channel
|
|
14
14
|
* → `POST /graphql` with the `x-api-key` header and the APQ body.
|
|
15
15
|
*
|
|
16
|
+
* a call made AS an operation, with its dispatch token
|
|
17
|
+
* → `POST /graphql` with `X-Foir-Token` and the dispatch's `X-Foir-Context`,
|
|
18
|
+
* and no `x-api-key`. Always this lane: the shared one takes its
|
|
19
|
+
* credential from the URL, and a dispatch token has no key to put there.
|
|
20
|
+
*
|
|
16
21
|
* All five conditions the server's shared-lane gate refuses on are evaluated
|
|
17
22
|
* HERE, locally: this code is constructing the URL, it can read the key prefix,
|
|
18
23
|
* it knows whether a bearer resolved, the manifest told it whether the op is a
|
|
@@ -84,8 +89,18 @@ import type { FoirReconnectPolicy, FoirWebSocketFactory } from './ws-transport';
|
|
|
84
89
|
* Its runtime value is `undefined` by design and nothing reads it.
|
|
85
90
|
*/
|
|
86
91
|
export interface FoirGeneratedModule<S extends FoirSchemaDefinition = FoirSchemaDefinition> {
|
|
87
|
-
/**
|
|
88
|
-
|
|
92
|
+
/**
|
|
93
|
+
* `foirSurfaceVersion`: the surface these types were generated from, a hash
|
|
94
|
+
* over exactly the parts of the manifest `foir types` reads. Drives the drift
|
|
95
|
+
* check, which compares it with the one the server is serving now.
|
|
96
|
+
*/
|
|
97
|
+
readonly foirSurfaceVersion: string;
|
|
98
|
+
/**
|
|
99
|
+
* `foirSurfaceHashes`: the components that version is folded from, keyed as
|
|
100
|
+
* the manifest keys them (`models.<key>`, `namespaces.<name>`, …). What lets a
|
|
101
|
+
* drift name the models that moved rather than just say something did.
|
|
102
|
+
*/
|
|
103
|
+
readonly foirSurfaceHashes: Readonly<Record<string, string>>;
|
|
89
104
|
/** `foirSelections` — one entry per model, in the printer's own input shape. */
|
|
90
105
|
readonly foirSelections: FoirSelections;
|
|
91
106
|
/** `foirNamespaceSelections` — the same, per namespace op. */
|
|
@@ -107,8 +122,70 @@ export interface FoirGeneratedModule<S extends FoirSchemaDefinition = FoirSchema
|
|
|
107
122
|
readonly foirSubscriptions?: readonly FoirManifestSubscription[];
|
|
108
123
|
/** Type carrier for `FoirSchema`. Never read: its runtime value is `undefined`. */
|
|
109
124
|
readonly foirSchemaType: S;
|
|
125
|
+
/**
|
|
126
|
+
* `foirCredential`: present when the module was generated for an operation
|
|
127
|
+
* (`foir types --operation <key>`), naming it. Its types are the surface that
|
|
128
|
+
* operation's dispatch token is served, so the module is for a client built
|
|
129
|
+
* from that token and for no other: a dispatch client requires it, and a key
|
|
130
|
+
* client refuses it. Absent from a module generated for a key.
|
|
131
|
+
*/
|
|
132
|
+
readonly foirCredential?: FoirOperationCredential;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* What a module `foir types --operation <key>` generated says about itself:
|
|
136
|
+
* the operation whose dispatch token its types describe.
|
|
137
|
+
*/
|
|
138
|
+
export interface FoirOperationCredential {
|
|
139
|
+
readonly operation: string;
|
|
140
|
+
}
|
|
141
|
+
/** A module generated for an api key: the kind a key client takes. */
|
|
142
|
+
export type FoirKeyGeneratedModule<S extends FoirSchemaDefinition = FoirSchemaDefinition> = FoirGeneratedModule<S> & {
|
|
143
|
+
readonly foirCredential?: undefined;
|
|
144
|
+
};
|
|
145
|
+
/** A module generated for an operation: the kind a dispatch client takes. */
|
|
146
|
+
export type FoirOperationGeneratedModule<S extends FoirSchemaDefinition = FoirSchemaDefinition> = FoirGeneratedModule<S> & {
|
|
147
|
+
readonly foirCredential: FoirOperationCredential;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* The credential of a client built inside an operation endpoint: the dispatch
|
|
151
|
+
* token the platform handed it.
|
|
152
|
+
*
|
|
153
|
+
* ```ts
|
|
154
|
+
* const { dispatch } = await requireScopedToken(request, { expectedProjectId });
|
|
155
|
+
* const foir = createFoirClient({ apiUrl, dispatch, generated });
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* The endpoint calls Foir AS the operation, with the capabilities the operation
|
|
159
|
+
* declared, and holds no key of its own. api-public accepts the token as a
|
|
160
|
+
* caller in its own right: it builds the schema overlay from the token's
|
|
161
|
+
* capabilities, and it reads the organization and the customer from the
|
|
162
|
+
* token's claims. So nothing a key client configures beside its key
|
|
163
|
+
* (`bearerToken`, `orgId`, `channel`) means anything here, and none of it is
|
|
164
|
+
* accepted.
|
|
165
|
+
*
|
|
166
|
+
* `requireScopedToken` returns one of these, built from the token it verified.
|
|
167
|
+
* Build one by hand only when the token reaches you some other way.
|
|
168
|
+
*/
|
|
169
|
+
export interface FoirDispatchCredential {
|
|
170
|
+
/**
|
|
171
|
+
* The dispatch token, sent as `X-Foir-Token`. Resolved before every request,
|
|
172
|
+
* so a caller holding a refreshed token hands the client the current one
|
|
173
|
+
* rather than the one it was built with. A provider that yields nothing fails
|
|
174
|
+
* the request: there is no other credential to fall back to.
|
|
175
|
+
*/
|
|
176
|
+
token: FoirTokenProvider;
|
|
177
|
+
/**
|
|
178
|
+
* The dispatch's `X-Foir-Context` header, forwarded as it arrived so the
|
|
179
|
+
* requests the operation makes carry the run's correlation metadata. Pass
|
|
180
|
+
* `request.headers.get('X-Foir-Context')` as it is; absent is fine.
|
|
181
|
+
*/
|
|
182
|
+
context?: string | null;
|
|
110
183
|
}
|
|
111
|
-
|
|
184
|
+
/**
|
|
185
|
+
* A client that calls Foir with an api key: a storefront's `pk_`, a server's
|
|
186
|
+
* `sk_`, optionally for a signed-in customer.
|
|
187
|
+
*/
|
|
188
|
+
export interface FoirKeyClientConfig<S extends FoirSchemaDefinition = FoirSchemaDefinition> {
|
|
112
189
|
/**
|
|
113
190
|
* Foir public API origin, e.g. `https://api.foir.dev`.
|
|
114
191
|
*
|
|
@@ -119,10 +196,13 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
119
196
|
apiUrl: string;
|
|
120
197
|
/** `pk_…` or `sk_…`. The manifest, and therefore the generated types, are shaped by it. */
|
|
121
198
|
apiKey: string;
|
|
199
|
+
/** A key client has no dispatch token; see {@link FoirDispatchClientConfig}. */
|
|
200
|
+
dispatch?: never;
|
|
122
201
|
/**
|
|
123
|
-
* Read the draft schema channel (`X-Foir-Schema-Channel: draft`). Needs a
|
|
124
|
-
*
|
|
125
|
-
*
|
|
202
|
+
* Read the draft schema channel (`X-Foir-Schema-Channel: draft`). Needs a key
|
|
203
|
+
* holding `drafts:read`: a preview environment's own `pk_`, or an `sk_`. A
|
|
204
|
+
* public key without it is served the published copy; a secret key without
|
|
205
|
+
* it is refused. Defaults to `'published'`.
|
|
126
206
|
*/
|
|
127
207
|
channel?: 'published' | 'draft';
|
|
128
208
|
/**
|
|
@@ -166,6 +246,33 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
166
246
|
* appear.
|
|
167
247
|
*/
|
|
168
248
|
orgId?: string;
|
|
249
|
+
/**
|
|
250
|
+
* The organization every request from this client acts in, named by the
|
|
251
|
+
* address of the page it is for, sent as `X-Org-Url`.
|
|
252
|
+
*
|
|
253
|
+
* For an app that serves many organizations from its own addresses: a path
|
|
254
|
+
* per organization (`saasapp.com/soho`), a subdomain each, or a domain an
|
|
255
|
+
* organization brought. Pass the page URL and the server finds the
|
|
256
|
+
* organization whose registered address (its app base URL, or a verified
|
|
257
|
+
* custom domain bound to it) is the longest prefix of that URL at a
|
|
258
|
+
* path-segment boundary, so `saasapp.com/soho` answers for `/soho/menu` and
|
|
259
|
+
* never for `/sohonews`. It works with the publishable key the page already
|
|
260
|
+
* ships.
|
|
261
|
+
*
|
|
262
|
+
* ```ts
|
|
263
|
+
* const foir = createFoirClient({ apiUrl, apiKey, generated, orgUrl: window.location.href });
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* It is `orgId` by another spelling and bounded the same way: a URL no
|
|
267
|
+
* organization's address covers answers `NOT_FOUND` exactly as an unknown id
|
|
268
|
+
* does, a credential confined to an organization is answered about that
|
|
269
|
+
* organization alone, and the organization is a selection rather than
|
|
270
|
+
* authority. Sent with `orgId`, the two must name the same organization.
|
|
271
|
+
* `resolveOrganizationAddress(url)` answers the same id up front.
|
|
272
|
+
*
|
|
273
|
+
* Routing: always the origin endpoint, for the reason `orgId` is.
|
|
274
|
+
*/
|
|
275
|
+
orgUrl?: string;
|
|
169
276
|
/**
|
|
170
277
|
* The generated module, whole — **the documented way to wire a client**.
|
|
171
278
|
*
|
|
@@ -174,8 +281,8 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
174
281
|
* const foir = createFoirClient({ apiUrl, apiKey, generated });
|
|
175
282
|
* ```
|
|
176
283
|
*
|
|
177
|
-
* It supplies `
|
|
178
|
-
* `selectionUnions` in one member, and carries `FoirSchema` with it so the
|
|
284
|
+
* It supplies `surfaceVersion`, `surfaceHashes`, `selections`,
|
|
285
|
+
* `namespaceSelections` and `selectionUnions` in one member, and carries `FoirSchema` with it so the
|
|
179
286
|
* type parameter is inferred rather than restated. An artifact added to
|
|
180
287
|
* `foir types` later then costs a call site nothing.
|
|
181
288
|
*
|
|
@@ -188,8 +295,12 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
188
295
|
* Omitting `generated` AND `selections` asks for the manifest fallback, which
|
|
189
296
|
* is supported, announced (see `announceManifestFallback`) and never reached
|
|
190
297
|
* by accident.
|
|
298
|
+
*
|
|
299
|
+
* A module generated for an operation (`foir types --operation`) is refused
|
|
300
|
+
* here: its types are what that operation's dispatch token is served, not
|
|
301
|
+
* what this key is.
|
|
191
302
|
*/
|
|
192
|
-
generated?:
|
|
303
|
+
generated?: FoirKeyGeneratedModule<S>;
|
|
193
304
|
/**
|
|
194
305
|
* The runtime half of `foir.generated.ts` — `foirSelections`, the resolved
|
|
195
306
|
* complete-by-default selection per model.
|
|
@@ -236,7 +347,11 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
236
347
|
* `subjects.add(...)`.
|
|
237
348
|
*/
|
|
238
349
|
subjects?: FoirSubjectRegistry;
|
|
239
|
-
/**
|
|
350
|
+
/**
|
|
351
|
+
* Extra headers, applied last so they can override the ones above. A client
|
|
352
|
+
* built from a dispatch token refuses a credential header here (`x-api-key`,
|
|
353
|
+
* `Authorization`, `X-Foir-Token`, `X-Foir-Context`).
|
|
354
|
+
*/
|
|
240
355
|
headers?: Readonly<Record<string, string>>;
|
|
241
356
|
/**
|
|
242
357
|
* `fetch` implementation. Defaults to `globalThis.fetch`. Injectable for
|
|
@@ -250,11 +365,17 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
250
365
|
*/
|
|
251
366
|
requestInit?: Omit<RequestInit, 'method' | 'headers' | 'body'>;
|
|
252
367
|
/**
|
|
253
|
-
* `
|
|
254
|
-
* compares it against the server
|
|
255
|
-
* Carried by `generated`; set it here only to
|
|
368
|
+
* `foirSurfaceVersion` from the generated module. When set, the client
|
|
369
|
+
* compares it against the surface the server is serving, once, and reports
|
|
370
|
+
* drift when they differ. Carried by `generated`; set it here only to
|
|
371
|
+
* override that.
|
|
372
|
+
*/
|
|
373
|
+
surfaceVersion?: string;
|
|
374
|
+
/**
|
|
375
|
+
* `foirSurfaceHashes` from the generated module, so a drift can name what
|
|
376
|
+
* moved. Carried by `generated`; set it here only to override that.
|
|
256
377
|
*/
|
|
257
|
-
|
|
378
|
+
surfaceHashes?: Readonly<Record<string, string>>;
|
|
258
379
|
/**
|
|
259
380
|
* Subscription descriptors, from the generated module's `foirSubscriptions`.
|
|
260
381
|
*
|
|
@@ -263,7 +384,7 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
263
384
|
* when absent it fetches once, as it always has.
|
|
264
385
|
*/
|
|
265
386
|
subscriptions?: readonly FoirManifestSubscription[];
|
|
266
|
-
/** Set `false` to suppress the drift check even when `
|
|
387
|
+
/** Set `false` to suppress the drift check even when `surfaceVersion` is set. */
|
|
267
388
|
checkSchemaDrift?: boolean;
|
|
268
389
|
/** Called instead of `console.warn` when drift is detected. */
|
|
269
390
|
onSchemaDrift?: (drift: FoirSchemaDrift) => void;
|
|
@@ -295,7 +416,12 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
295
416
|
* class of bug by construction, and refusing it would punish the escape
|
|
296
417
|
* hatch people reach for precisely because it is immune.
|
|
297
418
|
*
|
|
298
|
-
*
|
|
419
|
+
* Turning it off is never the remedy for a drift. The version it compares
|
|
420
|
+
* is the SURFACE the types were generated from, which moves only when
|
|
421
|
+
* regenerating would write a different file, so every drift it reports is a
|
|
422
|
+
* real one and the fix is `foir types`. The opt-out exists for an app that
|
|
423
|
+
* knowingly ships a stale module for a while, and it says so at the call
|
|
424
|
+
* site, where a reader can see it:
|
|
299
425
|
*
|
|
300
426
|
* ```ts
|
|
301
427
|
* createFoirClient({ strictSchemaDrift: false }) // serve stale, warn only
|
|
@@ -352,12 +478,74 @@ export interface FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDef
|
|
|
352
478
|
*/
|
|
353
479
|
connectionParams?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
354
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* The options that belong to a key: the key itself, what it is presented with
|
|
483
|
+
* (a customer bearer, an organization, a channel), and the subscription socket,
|
|
484
|
+
* which only a key can open.
|
|
485
|
+
*/
|
|
486
|
+
type FoirKeySideOption = 'apiKey' | 'dispatch' | 'channel' | 'bearerToken' | 'orgId' | 'orgUrl' | 'wsUrl' | 'webSocketImpl' | 'reconnect' | 'connectionParams';
|
|
487
|
+
/**
|
|
488
|
+
* A client that calls Foir AS an operation, from inside the endpoint the
|
|
489
|
+
* operation dispatched to. See {@link FoirDispatchCredential}.
|
|
490
|
+
*
|
|
491
|
+
* Everything a key client takes except the key-side options, which are typed
|
|
492
|
+
* `never` rather than left out, so passing one is a compile error that names
|
|
493
|
+
* it. No `apiKey` above all: the dispatch token is the credential, and an
|
|
494
|
+
* endpoint that holds a key as well has become the server in the middle the
|
|
495
|
+
* operation exists to replace.
|
|
496
|
+
*/
|
|
497
|
+
export interface FoirDispatchClientConfig<S extends FoirSchemaDefinition = FoirSchemaDefinition> extends Omit<FoirKeyClientConfig<S>, FoirKeySideOption | 'generated'> {
|
|
498
|
+
/** The dispatch token this endpoint was handed, and the dispatch's context header. */
|
|
499
|
+
dispatch: FoirDispatchCredential;
|
|
500
|
+
/**
|
|
501
|
+
* The module `foir types --operation <key>` generated for this operation.
|
|
502
|
+
* Required: its types are the surface this operation's token is served, and
|
|
503
|
+
* a module generated for a key would type calls the token is refused. A
|
|
504
|
+
* dispatch whose context names a different operation is refused too.
|
|
505
|
+
*
|
|
506
|
+
* ```ts
|
|
507
|
+
* // foir types --operation checkout --out src/checkout.generated.ts
|
|
508
|
+
* import * as generated from './checkout.generated';
|
|
509
|
+
* const foir = createFoirClient({ apiUrl, dispatch, generated });
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
generated: FoirOperationGeneratedModule<S>;
|
|
513
|
+
apiKey?: never;
|
|
514
|
+
channel?: never;
|
|
515
|
+
bearerToken?: never;
|
|
516
|
+
orgId?: never;
|
|
517
|
+
orgUrl?: never;
|
|
518
|
+
wsUrl?: never;
|
|
519
|
+
webSocketImpl?: never;
|
|
520
|
+
reconnect?: never;
|
|
521
|
+
connectionParams?: never;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* How a data client is configured: with an api key, or with an operation's
|
|
525
|
+
* dispatch token. Exactly one of the two. They are different actors, and there
|
|
526
|
+
* is no configuration that accepts either.
|
|
527
|
+
*/
|
|
528
|
+
export type FoirClientConfig<S extends FoirSchemaDefinition = FoirSchemaDefinition> = FoirKeyClientConfig<S> | FoirDispatchClientConfig<S>;
|
|
529
|
+
/**
|
|
530
|
+
* The credential and transport half of a client config, which is everything a
|
|
531
|
+
* request reads. A manifest fetch takes this rather than a whole client config:
|
|
532
|
+
* what it asks for does not depend on a generated module, and a dispatch token
|
|
533
|
+
* asks for its own surface before any module exists.
|
|
534
|
+
*/
|
|
535
|
+
export type FoirRequestConfig = Omit<FoirKeyClientConfig, 'generated'> | Omit<FoirDispatchClientConfig, 'generated'>;
|
|
355
536
|
export interface FoirSchemaDrift {
|
|
356
|
-
/**
|
|
357
|
-
generated:
|
|
358
|
-
/**
|
|
359
|
-
server:
|
|
537
|
+
/** The surface version the local types were generated from. */
|
|
538
|
+
generated: string;
|
|
539
|
+
/** The surface version the server is serving now. */
|
|
540
|
+
server: string;
|
|
360
541
|
channel: string;
|
|
542
|
+
/**
|
|
543
|
+
* What moved, as the manifest keys its components: `models.<key>`,
|
|
544
|
+
* `namespaces.<name>`, `selectionUnions.<name>`, `subscriptions`,
|
|
545
|
+
* `composedFor`. A component added or removed since generation is listed
|
|
546
|
+
* too. Empty only when the module was wired without `surfaceHashes`.
|
|
547
|
+
*/
|
|
548
|
+
changed: readonly string[];
|
|
361
549
|
}
|
|
362
550
|
/**
|
|
363
551
|
* Thrown by a typed call under `strictSchemaDrift` once drift is detected.
|
|
@@ -380,8 +568,39 @@ export declare class FoirSchemaDriftError extends FoirRequestError {
|
|
|
380
568
|
* than a re-export so `@foir/sdk/data`'s public surface is unchanged.
|
|
381
569
|
*/
|
|
382
570
|
export declare function foirDataEndpoint(apiUrl: string, path: `/${string}`): string;
|
|
383
|
-
/**
|
|
384
|
-
export
|
|
571
|
+
/** One request's credential, resolved now. */
|
|
572
|
+
export interface FoirResolvedAuth {
|
|
573
|
+
/** The headers the request carries, `extra` included. */
|
|
574
|
+
headers: Record<string, string>;
|
|
575
|
+
/**
|
|
576
|
+
* The key and the resolved bearer, when the credential is a key. Absent for a
|
|
577
|
+
* dispatch token, which has no key to put in a URL and so never takes the
|
|
578
|
+
* shared lane.
|
|
579
|
+
*/
|
|
580
|
+
key?: {
|
|
581
|
+
apiKey: string;
|
|
582
|
+
bearerToken?: string;
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* The credential a request carries, resolved at the moment it is sent.
|
|
587
|
+
*
|
|
588
|
+
* The one place every transport (`/graphql`, `GET /v1/manifest`, the socket's
|
|
589
|
+
* `connection_init`) asks, so the three cannot drift into authenticating a
|
|
590
|
+
* client differently. Providers are called here, once per request, and not at
|
|
591
|
+
* construction.
|
|
592
|
+
*/
|
|
593
|
+
export declare function resolveFoirAuth(config: FoirRequestConfig): Promise<FoirResolvedAuth>;
|
|
594
|
+
/**
|
|
595
|
+
* Refuse a config that names no credential, or two.
|
|
596
|
+
*
|
|
597
|
+
* The types already say this. It is checked again at runtime for a caller in
|
|
598
|
+
* plain JavaScript, or one who spread two configs together, because the
|
|
599
|
+
* failure it prevents is quiet: a client handed both a key and a dispatch token
|
|
600
|
+
* would send one and discard the other, and which actor made each request
|
|
601
|
+
* would depend on a precedence rule nobody chose.
|
|
602
|
+
*/
|
|
603
|
+
export declare function assertFoirCredential(config: FoirClientConfig): void;
|
|
385
604
|
/** Which GraphQL endpoint a call is sent to. */
|
|
386
605
|
export type FoirLane = 'shared' | 'origin';
|
|
387
606
|
/**
|
|
@@ -400,6 +619,8 @@ export interface FoirRouteInput {
|
|
|
400
619
|
bearerToken?: string;
|
|
401
620
|
/** The organization the call acts in (`X-Org-Id`), when it names one. */
|
|
402
621
|
orgId?: string;
|
|
622
|
+
/** The page URL the call names its organization by (`X-Org-Url`), when it does. */
|
|
623
|
+
orgUrl?: string;
|
|
403
624
|
mutation: boolean;
|
|
404
625
|
channel?: 'published' | 'draft';
|
|
405
626
|
/**
|
|
@@ -440,3 +661,4 @@ export declare function executeFoirDocument<T>(config: FoirClientConfig, plan: F
|
|
|
440
661
|
export declare function executeFoirRawDocument<T>(config: FoirClientConfig, document: string, mutation: boolean, variables?: Record<string, unknown>, options?: {
|
|
441
662
|
strict?: boolean;
|
|
442
663
|
}): Promise<T>;
|
|
664
|
+
export {};
|
|
@@ -41,9 +41,15 @@
|
|
|
41
41
|
* `config.connectionParams`, which is a FUNCTION and is called on every
|
|
42
42
|
* connect — so a short-lived credential is re-resolved on reconnect instead of
|
|
43
43
|
* being replayed after it expired.
|
|
44
|
+
*
|
|
45
|
+
* An operation's dispatch token is not one of them. The server's
|
|
46
|
+
* `connection_init` authenticates a key, an admin token or a customer bearer
|
|
47
|
+
* and has no path for a dispatch token, so a client built from one is refused
|
|
48
|
+
* here, by name, rather than opening a socket the server will close as "Not
|
|
49
|
+
* authenticated".
|
|
44
50
|
*/
|
|
45
51
|
import { FoirRequestError } from '../transport/foir-http';
|
|
46
|
-
import { type
|
|
52
|
+
import { type FoirRequestConfig } from './transport';
|
|
47
53
|
/**
|
|
48
54
|
* The slice of `WebSocket` this transport uses.
|
|
49
55
|
*
|
|
@@ -143,7 +149,7 @@ export declare class FoirSocketTransport {
|
|
|
143
149
|
private disposed;
|
|
144
150
|
private keepAlive;
|
|
145
151
|
private retryTimer;
|
|
146
|
-
constructor(config:
|
|
152
|
+
constructor(config: FoirRequestConfig);
|
|
147
153
|
/**
|
|
148
154
|
* Start a subscription.
|
|
149
155
|
*
|