@foir/sdk 0.21.0 → 0.23.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 +135 -1
- package/README.md +15 -15
- package/dist/app/client.d.ts +93 -86
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +14 -13
- package/dist/app/dispatch.d.ts +5 -5
- package/dist/app/errors.d.ts +17 -15
- package/dist/app/index.d.ts +4 -4
- package/dist/app/naming.d.ts +1 -1
- package/dist/app/ticket.d.ts +2 -2
- package/dist/app/vocabulary.generated.d.ts +2 -2
- package/dist/app-contract.js +3 -3
- package/dist/app.js +92 -90
- package/dist/auth/access.d.ts +19 -19
- package/dist/auth/auth.d.ts +251 -117
- package/dist/auth/grant-table.generated.d.ts +5 -4
- package/dist/auth/hosted.d.ts +38 -14
- package/dist/auth/index.d.ts +9 -9
- package/dist/auth/operations.d.ts +153 -33
- package/dist/auth/ops.d.ts +39 -21
- package/dist/auth/session-client.d.ts +3 -3
- package/dist/auth-session.js +3 -3
- package/dist/auth.js +400 -250
- package/dist/client.js +5 -8
- package/dist/codegen/preset.d.ts +1 -1
- package/dist/data/builtins.d.ts +26 -26
- package/dist/data/client.d.ts +3 -3
- package/dist/data/decimal.d.ts +1 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/manifest.d.ts +1 -1
- package/dist/data/schema.d.ts +2 -2
- package/dist/data/transport.d.ts +14 -13
- package/dist/data/ws-transport.d.ts +1 -1
- package/dist/data.js +8 -11
- package/dist/graphql/content-client.d.ts +13 -13
- package/dist/index.js +1 -4
- package/dist/live/drift.d.ts +1 -1
- package/dist/media/screen-capture.d.ts +1 -1
- package/dist/next/index.d.ts +2 -2
- package/dist/next/session.d.ts +10 -10
- package/dist/next.js +78 -60
- package/dist/placement/index.d.ts +13 -13
- package/dist/placement.js +127 -116
- package/dist/push.js +62 -44
- package/dist/react/FoirFeedback.d.ts +2 -2
- package/dist/react/FoirProvider.d.ts +24 -24
- package/dist/react/SignIn.d.ts +4 -4
- package/dist/react/UserButton.d.ts +1 -1
- package/dist/react/feedback-notes.d.ts +8 -6
- package/dist/react/index.d.ts +3 -3
- package/dist/react.js +238 -209
- package/dist/realtime/index.d.ts +1 -1
- package/dist/server/callback-client.d.ts +4 -4
- package/dist/server/events-client.d.ts +21 -21
- package/dist/server/operation-types.d.ts +8 -8
- package/dist/server/require-scoped-token.d.ts +3 -3
- package/dist/server/types.d.ts +2 -2
- package/dist/server/verify-scoped-token.d.ts +3 -3
- package/dist/server.js +130 -126
- package/dist/transport/foir-http.d.ts +14 -14
- package/dist/transport/key-kind.d.ts +1 -12
- package/dist/types/index.d.ts +2 -2
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -5,10 +5,7 @@ import { print as print2 } from "graphql";
|
|
|
5
5
|
var KEY_PREFIXES = [
|
|
6
6
|
["foir_pub_", "publishable"],
|
|
7
7
|
["foir_srv_", "server"],
|
|
8
|
-
["foir_adm_", "admin"]
|
|
9
|
-
["pk_", "publishable"],
|
|
10
|
-
["sk_", "older"],
|
|
11
|
-
["tc_", "older"]
|
|
8
|
+
["foir_adm_", "admin"]
|
|
12
9
|
];
|
|
13
10
|
var FOIR_CREDENTIALS_DOC = "https://docs.foir.dev/credentials";
|
|
14
11
|
function foirKeyKind(apiKey) {
|
|
@@ -200,8 +197,8 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
200
197
|
downgrade;
|
|
201
198
|
constructor(downgrade) {
|
|
202
199
|
super(
|
|
203
|
-
`[foir] your
|
|
204
|
-
{ graphQLErrors: [{ message: "
|
|
200
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}), so the request was served as ${downgrade.servedAs}`,
|
|
201
|
+
{ graphQLErrors: [{ message: "user token rejected", extensions: { code: "AUTH_DOWNGRADED" } }] }
|
|
205
202
|
);
|
|
206
203
|
this.name = "FoirAuthDowngradedError";
|
|
207
204
|
this.downgrade = downgrade;
|
|
@@ -209,7 +206,7 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
209
206
|
};
|
|
210
207
|
var FoirSessionExpiredError = class extends FoirRequestError {
|
|
211
208
|
constructor(options) {
|
|
212
|
-
super("[foir] the
|
|
209
|
+
super("[foir] the user session has expired and could not be refreshed: sign in again", {
|
|
213
210
|
cause: options?.cause,
|
|
214
211
|
graphQLErrors: [{ message: "session expired", extensions: { code: "SESSION_EXPIRED" } }]
|
|
215
212
|
});
|
|
@@ -226,7 +223,7 @@ function reportAuthDowngrade(envelope) {
|
|
|
226
223
|
if (warnedDowngrade === downgrade.reason) return;
|
|
227
224
|
warnedDowngrade = downgrade.reason;
|
|
228
225
|
console.warn(
|
|
229
|
-
`[foir] your
|
|
226
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}) \u2014 this request was served as ${downgrade.servedAs}, so auth-targeted content resolved to its anonymous variant. ` + (downgrade.reason === "expired" ? "Re-exchange the token and retry." : "Check which credential you are sending.")
|
|
230
227
|
);
|
|
231
228
|
}
|
|
232
229
|
function refusePrunedDocument(json, options) {
|
package/dist/codegen/preset.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface FoirCodegenOptions {
|
|
|
16
16
|
/** Foir public API GraphQL endpoint. */
|
|
17
17
|
apiUrl: string;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Publishable API key. Reads the published schema. Used as the fallback
|
|
20
20
|
* when no preview key is set.
|
|
21
21
|
*/
|
|
22
22
|
apiKey?: string;
|
package/dist/data/builtins.d.ts
CHANGED
|
@@ -398,7 +398,7 @@ export type FoirUnexpandedRecord = FoirDegraded;
|
|
|
398
398
|
export type FoirVisibility = 'PRIVATE' | 'PUBLIC';
|
|
399
399
|
export type FoirAccessLevel = 'READ' | 'WRITE' | 'DELETE';
|
|
400
400
|
export type FoirAccessSource = 'PUBLIC' | 'OWNER' | 'GRANT' | 'ROLE';
|
|
401
|
-
export type FoirGranteeKind = '
|
|
401
|
+
export type FoirGranteeKind = 'USER';
|
|
402
402
|
/** `myAccess` — this principal's effective access to one record. */
|
|
403
403
|
export interface FoirMyAccess {
|
|
404
404
|
access: FoirAccessLevel;
|
|
@@ -435,9 +435,9 @@ export interface FoirUpdateManyPayload {
|
|
|
435
435
|
}
|
|
436
436
|
/**
|
|
437
437
|
* `setOwner`: who the record belongs to after an operation handed it to
|
|
438
|
-
* another
|
|
438
|
+
* another user. It carries the owner and not the record, because the
|
|
439
439
|
* operation may have been acting for the previous holder, who can no longer
|
|
440
|
-
* read it. `ownerKind` is always `'
|
|
440
|
+
* read it. `ownerKind` is always `'user'` from this op.
|
|
441
441
|
*/
|
|
442
442
|
export interface FoirRecordOwner {
|
|
443
443
|
id: string;
|
|
@@ -445,7 +445,7 @@ export interface FoirRecordOwner {
|
|
|
445
445
|
ownerKind: string;
|
|
446
446
|
}
|
|
447
447
|
/**
|
|
448
|
-
* What a
|
|
448
|
+
* What a user's note can be ABOUT: the user-plane subset of the closed
|
|
449
449
|
* set the platform's write path enforces.
|
|
450
450
|
*
|
|
451
451
|
* On the wire it is `String!`, not a GraphQL enum, so the manifest publishes no
|
|
@@ -458,23 +458,23 @@ export interface FoirRecordOwner {
|
|
|
458
458
|
* record is `'record'` plus the model key on `subjectModelKey`.
|
|
459
459
|
*
|
|
460
460
|
* The platform set also holds `project` and `organization`, and this union
|
|
461
|
-
* deliberately does not. Both are
|
|
462
|
-
* note about either through a row policy no
|
|
461
|
+
* deliberately does not. Both are the project's own constructs: the platform answers a
|
|
462
|
+
* note about either through a row policy no user claim passes, so no note
|
|
463
463
|
* this surface reads or writes can carry them. This mirrors the part of the
|
|
464
|
-
* platform set a
|
|
465
|
-
* (see the file header) applies: a kind the platform opens to
|
|
464
|
+
* platform set a user can reach, and the obligation that comes with a mirror
|
|
465
|
+
* (see the file header) applies: a kind the platform opens to users widens
|
|
466
466
|
* this union in a minor release, and until then a value outside it is a row no
|
|
467
|
-
*
|
|
467
|
+
* user writer could have produced.
|
|
468
468
|
*/
|
|
469
|
-
export type FoirNoteSubjectType = 'record' | 'file' | 'model' | 'operation' | 'schedule' | '
|
|
469
|
+
export type FoirNoteSubjectType = 'record' | 'file' | 'model' | 'operation' | 'schedule' | 'user';
|
|
470
470
|
/**
|
|
471
|
-
* One file the
|
|
471
|
+
* One file the user submitted with their note.
|
|
472
472
|
*
|
|
473
473
|
* The file's own facts, and nothing about the platform's handling of it: when
|
|
474
474
|
* the link row was written and which retention clock it starts are not the
|
|
475
475
|
* author's business. What is here is enough to render "you sent this".
|
|
476
476
|
*/
|
|
477
|
-
export interface
|
|
477
|
+
export interface FoirUserNoteAttachment {
|
|
478
478
|
/** The file's id — the same one `createMyNote` was given. */
|
|
479
479
|
fileId: string;
|
|
480
480
|
filename: string;
|
|
@@ -484,15 +484,15 @@ export interface FoirCustomerNoteAttachment {
|
|
|
484
484
|
url: string;
|
|
485
485
|
}
|
|
486
486
|
/**
|
|
487
|
-
* One note on the
|
|
487
|
+
* One note on the user plane — `foir.ns.notes.myNotes` and
|
|
488
488
|
* `foir.ns.notes.createMyNote`.
|
|
489
489
|
*
|
|
490
490
|
* Deliberately narrower than the note your team sees in the dashboard. There is
|
|
491
491
|
* no visibility, no resolution note, no mentions and no metadata: those are the
|
|
492
|
-
* project's view of a thread, not the author's, and the
|
|
492
|
+
* project's view of a thread, not the author's, and the user-facing type
|
|
493
493
|
* does not merely omit them from the document — the surface does not carry them.
|
|
494
494
|
*/
|
|
495
|
-
export interface
|
|
495
|
+
export interface FoirUserNote {
|
|
496
496
|
id: string;
|
|
497
497
|
/** The note's prose. */
|
|
498
498
|
body: string | null;
|
|
@@ -507,12 +507,12 @@ export interface FoirCustomerNote {
|
|
|
507
507
|
/**
|
|
508
508
|
* The files submitted with this note. Empty when there are none.
|
|
509
509
|
*
|
|
510
|
-
* The one place the
|
|
510
|
+
* The one place the user surface is WIDER than "the author's own prose",
|
|
511
511
|
* and deliberately: an author who attached a screenshot has to be able to see
|
|
512
512
|
* that it arrived, or the only readings left to them are that it failed
|
|
513
513
|
* silently or that it was taken away.
|
|
514
514
|
*/
|
|
515
|
-
attachments:
|
|
515
|
+
attachments: FoirUserNoteAttachment[];
|
|
516
516
|
/** ISO-8601. */
|
|
517
517
|
createdAt: string;
|
|
518
518
|
}
|
|
@@ -521,7 +521,7 @@ export interface FoirCustomerNote {
|
|
|
521
521
|
* — `foir.ns.notes.noteConfig`.
|
|
522
522
|
*
|
|
523
523
|
* Charged `notes:create` rather than `notes:read`, and refused to a caller with
|
|
524
|
-
* no
|
|
524
|
+
* no user session, because the only caller it is useful to is one that can
|
|
525
525
|
* submit. So a composer can read it as its single readiness probe: an answer
|
|
526
526
|
* means this visitor may write, and a refusal names which half is missing.
|
|
527
527
|
*/
|
|
@@ -546,16 +546,16 @@ export interface FoirMyNotesArgs {
|
|
|
546
546
|
threadRootId?: string;
|
|
547
547
|
}
|
|
548
548
|
/**
|
|
549
|
-
* `myNotes` — the notes this
|
|
550
|
-
* deliberately sent them. Never another
|
|
549
|
+
* `myNotes` — the notes this user wrote, plus the replies your team
|
|
550
|
+
* deliberately sent them. Never another user's, and never your team's
|
|
551
551
|
* internal ones.
|
|
552
552
|
*/
|
|
553
|
-
export type FoirMyNotesResult = FoirConnection<
|
|
553
|
+
export type FoirMyNotesResult = FoirConnection<FoirUserNote>;
|
|
554
554
|
/**
|
|
555
|
-
* `writeNote` arguments: the one note create, for a signed-in
|
|
555
|
+
* `writeNote` arguments: the one note create, for a signed-in user and for
|
|
556
556
|
* a visitor alike. It asks `notes:create` of both.
|
|
557
557
|
*
|
|
558
|
-
* A signed-in
|
|
558
|
+
* A signed-in user's note is theirs and comes back in `myNotes`. A
|
|
559
559
|
* visitor's, through a key that gives `notes:create` to whoever comes through
|
|
560
560
|
* it, on a project that takes visitors' notes, is nobody's: ask for its `id`
|
|
561
561
|
* and `createdAt` and nothing else, because nothing reads it back.
|
|
@@ -584,8 +584,8 @@ export interface FoirWriteNoteArgs {
|
|
|
584
584
|
*/
|
|
585
585
|
attachmentFileIds?: string[];
|
|
586
586
|
/**
|
|
587
|
-
* Where
|
|
588
|
-
*
|
|
587
|
+
* Where your team may reply to a visitor. Refused from a signed-in
|
|
588
|
+
* user, whose note has an author.
|
|
589
589
|
*
|
|
590
590
|
* Captured as typed and NEVER verified: it is not treated as proof of who
|
|
591
591
|
* wrote the note, and nothing is sent to it to confirm it. Do not build a
|
|
@@ -597,7 +597,7 @@ export interface FoirWriteNoteArgs {
|
|
|
597
597
|
* `createMyNote` arguments.
|
|
598
598
|
*
|
|
599
599
|
* @deprecated `createMyNote` is the older name of `writeNote` for a signed-in
|
|
600
|
-
*
|
|
600
|
+
* user. Use `writeNote` and {@link FoirWriteNoteArgs}.
|
|
601
601
|
*/
|
|
602
602
|
export interface FoirCreateMyNoteArgs {
|
|
603
603
|
subjectType: FoirNoteSubjectType;
|
package/dist/data/client.d.ts
CHANGED
|
@@ -166,7 +166,7 @@ export interface FoirRawOptions {
|
|
|
166
166
|
}
|
|
167
167
|
export interface FoirClientCore<S extends FoirSchemaDefinition> {
|
|
168
168
|
/**
|
|
169
|
-
* Non-model root namespaces (`search`, `
|
|
169
|
+
* Non-model root namespaces (`search`, `users`, `notifications`).
|
|
170
170
|
* Separate accessor because the addressing has to distinguish a model from a
|
|
171
171
|
* namespace and the client has no runtime schema to disambiguate with.
|
|
172
172
|
*/
|
|
@@ -181,7 +181,7 @@ export interface FoirClientCore<S extends FoirSchemaDefinition> {
|
|
|
181
181
|
* hatch that could only say what the JSON op could say would be an escape from
|
|
182
182
|
* nothing. As a document it reaches everything the credential can reach —
|
|
183
183
|
* including the root fields no model or namespace claims (`designTokens`,
|
|
184
|
-
* `record`, `locales`, `resolvedLocale`, the whole
|
|
184
|
+
* `record`, `locales`, `resolvedLocale`, the whole user-auth mutation
|
|
185
185
|
* set), which the JSON op could not address at all.
|
|
186
186
|
*
|
|
187
187
|
* Returns the whole `data` object rather than a single root field, because a
|
|
@@ -221,7 +221,7 @@ export interface FoirClientCore<S extends FoirSchemaDefinition> {
|
|
|
221
221
|
/**
|
|
222
222
|
* The client, shaped by the credential's manifest. An op that is not in
|
|
223
223
|
* `FoirSchema` is not on this type, so `foir.tilly_block.create(...)` under a
|
|
224
|
-
* read-only
|
|
224
|
+
* read-only publishable key is a compile error rather than a 400.
|
|
225
225
|
*/
|
|
226
226
|
export type FoirClient<S extends FoirSchemaDefinition> = FoirClientCore<S> & {
|
|
227
227
|
[MK in keyof S['models']]: FoirModelClient<S, S['models'][MK]>;
|
package/dist/data/decimal.d.ts
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
*
|
|
54
54
|
* TypeScript already rejects `price + tax` at compile time. The throw is for
|
|
55
55
|
* JavaScript callers and for anything that reaches this through `any`, where a
|
|
56
|
-
* silent `"19.992.00"` would otherwise reach a
|
|
56
|
+
* silent `"19.992.00"` would otherwise reach a user. Loud beats quiet: the
|
|
57
57
|
* whole reason this type exists is that the float bug was silent.
|
|
58
58
|
*
|
|
59
59
|
* `JSON.stringify` is answered by `toJSON`, so a decimal serialises back to the
|
package/dist/data/index.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export { planFoirModelCall, planFoirNamespaceCall } from './document';
|
|
|
59
59
|
export type { FoirCall, FoirDocumentPlan, FoirNamespaceCall, FoirNamespaceSelections, FoirRecordPath, FoirSelectionModel, FoirSelectionNamespace, FoirSelections, FoirSelectionSource, FoirSelectionUnions, } from './document';
|
|
60
60
|
export { createFoirSubjectRegistry, recordFoirSubjects } from './subjects';
|
|
61
61
|
export type { FoirSubject, FoirSubjectEntry, FoirSubjectOptions, FoirSubjectOrigin, FoirSubjectRegistry, } from './subjects';
|
|
62
|
-
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirWriteNoteArgs,
|
|
62
|
+
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirWriteNoteArgs, FoirUserNote, FoirUserNoteAttachment, 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';
|
|
63
63
|
/**
|
|
64
64
|
* Pre-rename spellings. The `<Name>Block` wrappers and the `<Name>Value`
|
|
65
65
|
* split are the same thing as the types above — an item IS its value — so each
|
package/dist/data/manifest.d.ts
CHANGED
|
@@ -303,7 +303,7 @@ export interface FoirManifest {
|
|
|
303
303
|
composedFor?: string;
|
|
304
304
|
models: Record<string, FoirManifestModel>;
|
|
305
305
|
/**
|
|
306
|
-
* Keyed by namespace name (`auth`, `
|
|
306
|
+
* Keyed by namespace name (`auth`, `users`, `files`, `notifications`,
|
|
307
307
|
* `operations`, `schedules`, `search`, `secrets`). Deliberately an open
|
|
308
308
|
* record rather than the eight names: a namespace the platform adds must
|
|
309
309
|
* reach the generator, and a closed union would silently drop it.
|
package/dist/data/schema.d.ts
CHANGED
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
* 3. **Emit only what the manifest returned.** The manifest is already the
|
|
131
131
|
* per-caller projection of `SchemaForCaller(scopes)`, so "only the ops this
|
|
132
132
|
* credential can perform" needs no extra filtering — it is what arrives. A
|
|
133
|
-
*
|
|
133
|
+
* publishable key's generated schema simply has no `create`, and
|
|
134
134
|
* `foir.tilly_block.create(...)` is then a compile error, which is the
|
|
135
135
|
* entire point of per-key shaping.
|
|
136
136
|
*
|
|
@@ -272,7 +272,7 @@ export interface FoirModelDefinition {
|
|
|
272
272
|
readonly ops: Readonly<Record<string, FoirOpDefinition>>;
|
|
273
273
|
}
|
|
274
274
|
/**
|
|
275
|
-
* One op on a non-model namespace (`search`, `
|
|
275
|
+
* One op on a non-model namespace (`search`, `users`, `notifications`).
|
|
276
276
|
* Namespaces have no model behind them, so there is no `include`/`omit` and
|
|
277
277
|
* the result type is used verbatim.
|
|
278
278
|
*/
|
package/dist/data/transport.d.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* Where a call goes
|
|
6
6
|
* ---------------------------------------------------------------------------
|
|
7
7
|
*
|
|
8
|
-
* read +
|
|
9
|
-
* → `GET /cdn/graphql?key=
|
|
8
|
+
* read + publishable key + no user bearer + published channel
|
|
9
|
+
* → `GET /cdn/graphql?key=foir_pub_…&extensions={"persistedQuery":{"sha256Hash":…}}`
|
|
10
10
|
* A shared cache in front of the caller stores the response, because the
|
|
11
11
|
* request is a GET and the credential is in the URL.
|
|
12
12
|
*
|
|
13
|
-
* anything else
|
|
13
|
+
* anything else: a server or admin key, a user bearer, a mutation, the draft channel
|
|
14
14
|
* → `POST /graphql` with the `x-api-key` header and the APQ body.
|
|
15
15
|
*
|
|
16
16
|
* a call made AS an operation, with its dispatch token
|
|
@@ -158,7 +158,7 @@ export type FoirOperationGeneratedModule<S extends FoirSchemaDefinition = FoirSc
|
|
|
158
158
|
* The endpoint calls Foir AS the operation, with the grants the operation
|
|
159
159
|
* declared, and holds no key of its own. api-public accepts the token as a
|
|
160
160
|
* caller in its own right: it builds the schema overlay from the token's
|
|
161
|
-
* grants, and it reads the organization and the
|
|
161
|
+
* grants, and it reads the organization and the user from the
|
|
162
162
|
* token's claims. So nothing a key client configures beside its key
|
|
163
163
|
* (`bearerToken`, `orgId`, `channel`) means anything here, and none of it is
|
|
164
164
|
* accepted.
|
|
@@ -182,8 +182,9 @@ export interface FoirDispatchCredential {
|
|
|
182
182
|
context?: string | null;
|
|
183
183
|
}
|
|
184
184
|
/**
|
|
185
|
-
* A client that calls Foir with an api key: a storefront's
|
|
186
|
-
*
|
|
185
|
+
* A client that calls Foir with an api key: a storefront's publishable key, or
|
|
186
|
+
* the server key of the server that renders its pages, optionally for a
|
|
187
|
+
* signed-in user.
|
|
187
188
|
*/
|
|
188
189
|
export interface FoirKeyClientConfig<S extends FoirSchemaDefinition = FoirSchemaDefinition> {
|
|
189
190
|
/**
|
|
@@ -194,7 +195,7 @@ export interface FoirKeyClientConfig<S extends FoirSchemaDefinition = FoirSchema
|
|
|
194
195
|
* a trailing `/graphql` is stripped before a path is appended.
|
|
195
196
|
*/
|
|
196
197
|
apiUrl: string;
|
|
197
|
-
/** `
|
|
198
|
+
/** `foir_pub_…` or `foir_srv_…`. The manifest, and therefore the generated types, are shaped by it. */
|
|
198
199
|
apiKey: string;
|
|
199
200
|
/** A key client has no dispatch token; see {@link FoirDispatchClientConfig}. */
|
|
200
201
|
dispatch?: never;
|
|
@@ -206,13 +207,13 @@ export interface FoirKeyClientConfig<S extends FoirSchemaDefinition = FoirSchema
|
|
|
206
207
|
*/
|
|
207
208
|
channel?: 'published' | 'draft';
|
|
208
209
|
/**
|
|
209
|
-
*
|
|
210
|
+
* User bearer for self-scoped reads (`myProfile`, `myTillyNotes`). A
|
|
210
211
|
* function is resolved before every request so a cookie session can be read
|
|
211
212
|
* per-request; a bare string is used as-is.
|
|
212
213
|
*/
|
|
213
214
|
bearerToken?: string | FoirTokenProvider;
|
|
214
215
|
/**
|
|
215
|
-
* Renew the
|
|
216
|
+
* Renew the user bearer when an answer comes back served without it
|
|
216
217
|
* because it expired. Resolve the new token, or null when the session is
|
|
217
218
|
* over. It is called at most once per request, and one call is shared by the
|
|
218
219
|
* client's requests that find the bearer lapsed at the same time.
|
|
@@ -247,8 +248,8 @@ export interface FoirKeyClientConfig<S extends FoirSchemaDefinition = FoirSchema
|
|
|
247
248
|
*
|
|
248
249
|
* A credential already confined to an organization may only name its own
|
|
249
250
|
* (`PERMISSION_DENIED` otherwise), so this cannot steer an organization key
|
|
250
|
-
* elsewhere. And a request that also carries a
|
|
251
|
-
* unless that
|
|
251
|
+
* elsewhere. And a request that also carries a user bearer is refused
|
|
252
|
+
* unless that user stands in the named organization. The storefront
|
|
252
253
|
* pattern is still the confined credential, one per organization; this is
|
|
253
254
|
* for the side of the product that holds the project key and has to choose.
|
|
254
255
|
*
|
|
@@ -527,7 +528,7 @@ export interface FoirKeyClientConfig<S extends FoirSchemaDefinition = FoirSchema
|
|
|
527
528
|
}
|
|
528
529
|
/**
|
|
529
530
|
* The options that belong to a key: the key itself, what it is presented with
|
|
530
|
-
* (a
|
|
531
|
+
* (a user bearer, an organization, a channel), and the subscription socket,
|
|
531
532
|
* which only a key can open.
|
|
532
533
|
*/
|
|
533
534
|
type FoirKeySideOption = 'apiKey' | 'dispatch' | 'channel' | 'bearerToken' | 'refreshBearer' | 'orgId' | 'orgUrl' | 'visitor' | 'wsUrl' | 'webSocketImpl' | 'reconnect' | 'connectionParams';
|
|
@@ -600,7 +601,7 @@ export interface FoirSchemaDrift {
|
|
|
600
601
|
* way, as paths into the manifest, sorted:
|
|
601
602
|
* `models.order.selection.status`, `models.order.ops.update`,
|
|
602
603
|
* `namespaces.search.ops.searchRecords.args.query`,
|
|
603
|
-
* `models.order.relations.
|
|
604
|
+
* `models.order.relations.user`, `selectionUnions.Block.HeroBlock.title`.
|
|
604
605
|
*
|
|
605
606
|
* `changed` says what moved; this says what BROKE. Empty when the surface
|
|
606
607
|
* only grew, which is the usual drift, and then no typed call is refused.
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* being replayed after it expired.
|
|
44
44
|
*
|
|
45
45
|
* An operation's dispatch token is not one of them. The server's
|
|
46
|
-
* `connection_init` authenticates a key,
|
|
46
|
+
* `connection_init` authenticates a key, a console token or a user bearer
|
|
47
47
|
* and has no path for a dispatch token, so a client built from one is refused
|
|
48
48
|
* here, by name, rather than opening a socket the server will close as "Not
|
|
49
49
|
* authenticated".
|
package/dist/data.js
CHANGED
|
@@ -166,8 +166,8 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
166
166
|
downgrade;
|
|
167
167
|
constructor(downgrade) {
|
|
168
168
|
super(
|
|
169
|
-
`[foir] your
|
|
170
|
-
{ graphQLErrors: [{ message: "
|
|
169
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}), so the request was served as ${downgrade.servedAs}`,
|
|
170
|
+
{ graphQLErrors: [{ message: "user token rejected", extensions: { code: "AUTH_DOWNGRADED" } }] }
|
|
171
171
|
);
|
|
172
172
|
this.name = "FoirAuthDowngradedError";
|
|
173
173
|
this.downgrade = downgrade;
|
|
@@ -175,7 +175,7 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
175
175
|
};
|
|
176
176
|
var FoirSessionExpiredError = class extends FoirRequestError {
|
|
177
177
|
constructor(options) {
|
|
178
|
-
super("[foir] the
|
|
178
|
+
super("[foir] the user session has expired and could not be refreshed: sign in again", {
|
|
179
179
|
cause: options?.cause,
|
|
180
180
|
graphQLErrors: [{ message: "session expired", extensions: { code: "SESSION_EXPIRED" } }]
|
|
181
181
|
});
|
|
@@ -192,7 +192,7 @@ function reportAuthDowngrade(envelope) {
|
|
|
192
192
|
if (warnedDowngrade === downgrade.reason) return;
|
|
193
193
|
warnedDowngrade = downgrade.reason;
|
|
194
194
|
console.warn(
|
|
195
|
-
`[foir] your
|
|
195
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}) \u2014 this request was served as ${downgrade.servedAs}, so auth-targeted content resolved to its anonymous variant. ` + (downgrade.reason === "expired" ? "Re-exchange the token and retry." : "Check which credential you are sending.")
|
|
196
196
|
);
|
|
197
197
|
}
|
|
198
198
|
function refusePrunedDocument(json, options) {
|
|
@@ -240,10 +240,7 @@ function readFoirEnvelope(json, options) {
|
|
|
240
240
|
var KEY_PREFIXES = [
|
|
241
241
|
["foir_pub_", "publishable"],
|
|
242
242
|
["foir_srv_", "server"],
|
|
243
|
-
["foir_adm_", "admin"]
|
|
244
|
-
["pk_", "publishable"],
|
|
245
|
-
["sk_", "older"],
|
|
246
|
-
["tc_", "older"]
|
|
243
|
+
["foir_adm_", "admin"]
|
|
247
244
|
];
|
|
248
245
|
var FOIR_CREDENTIALS_DOC = "https://docs.foir.dev/credentials";
|
|
249
246
|
function foirKeyKind(apiKey) {
|
|
@@ -356,7 +353,7 @@ function assertFoirCredential(config) {
|
|
|
356
353
|
if (kind === "admin") {
|
|
357
354
|
throw new FoirRequestError(adminKeyRefusal("createFoirClient"));
|
|
358
355
|
}
|
|
359
|
-
if (loose.visitor !== void 0 && kind !== "server"
|
|
356
|
+
if (loose.visitor !== void 0 && kind !== "server") {
|
|
360
357
|
throw new FoirRequestError(
|
|
361
358
|
`[foir] createFoirClient: \`visitor\` names the visitor a server calls for, and only a server key holding visitors:forward may send it. A publishable key already calls as its visitor, and cannot hold the permission. Drop \`visitor\`, or build this client from your server key on the server that renders your pages. See ${FOIR_CREDENTIALS_DOC}`
|
|
362
359
|
);
|
|
@@ -383,7 +380,7 @@ function assertFoirCredential(config) {
|
|
|
383
380
|
].filter((name) => loose[name] !== void 0);
|
|
384
381
|
if (keySide.length > 0) {
|
|
385
382
|
throw new FoirRequestError(
|
|
386
|
-
`[foir] createFoirClient was given \`dispatch\` and ${keySide.map((n) => `\`${n}\``).join(", ")}. A dispatch token and an api key are different actors, and the dispatch token carries its own
|
|
383
|
+
`[foir] createFoirClient was given \`dispatch\` and ${keySide.map((n) => `\`${n}\``).join(", ")}. A dispatch token and an api key are different actors, and the dispatch token carries its own user, organization and channel. Pass one credential: \`dispatch\` inside an operation endpoint, \`apiKey\` everywhere else.`
|
|
387
384
|
);
|
|
388
385
|
}
|
|
389
386
|
const dispatch = loose.dispatch;
|
|
@@ -1411,7 +1408,7 @@ var FoirSocketTransport = class {
|
|
|
1411
1408
|
constructor(config) {
|
|
1412
1409
|
if (config.dispatch !== void 0) {
|
|
1413
1410
|
throw new FoirRequestError(
|
|
1414
|
-
"[foir] a client built from a dispatch token cannot subscribe. The subscription socket authenticates an api key,
|
|
1411
|
+
"[foir] a client built from a dispatch token cannot subscribe. The subscription socket authenticates an api key, a console token or a user bearer, and has no path for a dispatch token. Queries and mutations made as the operation go to /graphql and work as they are."
|
|
1415
1412
|
);
|
|
1416
1413
|
}
|
|
1417
1414
|
this.config = config;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session-bound content client — forwards the
|
|
3
|
-
* when one is available, so auth/segment-targeted variants and
|
|
2
|
+
* Session-bound content client — forwards the user bearer on every read
|
|
3
|
+
* when one is available, so auth/segment-targeted variants and user-scoped
|
|
4
4
|
* records resolve out of the box, with no per-query wiring to forget.
|
|
5
5
|
*
|
|
6
6
|
* Resolution keys off the request's principal (see the platform's two-gate
|
|
7
7
|
* model): the `x-api-key` alone resolves the *anonymous baseline*
|
|
8
|
-
* (`context.authStatus = ""`); adding the
|
|
9
|
-
* as that
|
|
8
|
+
* (`context.authStatus = ""`); adding the user bearer resolves the request
|
|
9
|
+
* as that user (`context.authStatus = "authenticated"`, `user_id` set).
|
|
10
10
|
*
|
|
11
11
|
* Caching: an api-key-only read is identical for every anonymous visitor and is
|
|
12
|
-
* shared-cacheable; a bearer-bearing read resolves per-
|
|
13
|
-
* Because the bearer only exists once a
|
|
12
|
+
* shared-cacheable; a bearer-bearing read resolves per-user and is not.
|
|
13
|
+
* Because the bearer only exists once a user signs in, anonymous traffic
|
|
14
14
|
* keeps hitting the shared-cacheable baseline — only signed-in reads become
|
|
15
|
-
* per-
|
|
16
|
-
* apps, everywhere the
|
|
15
|
+
* per-user. Bind this client wherever content is personalized (for most
|
|
16
|
+
* apps, everywhere the user may be signed in) and use plain `foirRequest`
|
|
17
17
|
* for reads that must stay anonymous regardless of session.
|
|
18
18
|
*/
|
|
19
19
|
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
@@ -25,9 +25,9 @@ import type { FoirTokenProvider } from '../transport/foir-http';
|
|
|
25
25
|
export interface FoirContentClientConfig {
|
|
26
26
|
env: FoirEnv;
|
|
27
27
|
/**
|
|
28
|
-
* Resolves the current
|
|
28
|
+
* Resolves the current user bearer before every request. When it yields a
|
|
29
29
|
* token the token is forwarded, so `authStatus`/segment targeting and
|
|
30
|
-
*
|
|
30
|
+
* user-scoped records resolve as that user; when it yields
|
|
31
31
|
* null/undefined the read hits the anonymous baseline. Omit for a client that
|
|
32
32
|
* is always anonymous (equivalent to calling `foirRequest` directly).
|
|
33
33
|
*/
|
|
@@ -51,7 +51,7 @@ export interface FoirContentClientConfig {
|
|
|
51
51
|
}
|
|
52
52
|
export interface FoirContentClient {
|
|
53
53
|
/**
|
|
54
|
-
* Run a typed document, forwarding the bound
|
|
54
|
+
* Run a typed document, forwarding the bound user bearer when available.
|
|
55
55
|
* An explicit `options.bearerToken` still wins, so a single call can override
|
|
56
56
|
* the bound token (or force anonymous by passing `undefined` — the bound
|
|
57
57
|
* token only fills in when `options.bearerToken` is not supplied).
|
|
@@ -60,7 +60,7 @@ export interface FoirContentClient {
|
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* Create a {@link FoirContentClient} bound to an env + token source. Every
|
|
63
|
-
* `request` forwards the
|
|
64
|
-
* that read from the anonymous baseline to the signed-in
|
|
63
|
+
* `request` forwards the user bearer when `getToken` yields one, upgrading
|
|
64
|
+
* that read from the anonymous baseline to the signed-in user.
|
|
65
65
|
*/
|
|
66
66
|
export declare function createFoirContentClient(config: FoirContentClientConfig): FoirContentClient;
|
package/dist/index.js
CHANGED
|
@@ -98,10 +98,7 @@ function isValidHostMessage(data) {
|
|
|
98
98
|
var KEY_PREFIXES = [
|
|
99
99
|
["foir_pub_", "publishable"],
|
|
100
100
|
["foir_srv_", "server"],
|
|
101
|
-
["foir_adm_", "admin"]
|
|
102
|
-
["pk_", "publishable"],
|
|
103
|
-
["sk_", "older"],
|
|
104
|
-
["tc_", "older"]
|
|
101
|
+
["foir_adm_", "admin"]
|
|
105
102
|
];
|
|
106
103
|
var FOIR_CREDENTIALS_DOC = "https://docs.foir.dev/credentials";
|
|
107
104
|
function foirKeyKind(apiKey) {
|
package/dist/live/drift.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type { FoirSchemaDrift } from '../data/transport';
|
|
|
12
12
|
* Nothing else catches it. The dependency graph is about records, and no record
|
|
13
13
|
* changed. `seq` is about ordering, and nothing is out of order. The entry is
|
|
14
14
|
* fresh, correctly tagged, correctly stamped, and describes a schema that no
|
|
15
|
-
* longer exists — until the tab is reloaded, which on a long-lived
|
|
15
|
+
* longer exists — until the tab is reloaded, which on a long-lived console
|
|
16
16
|
* session can be days.
|
|
17
17
|
*
|
|
18
18
|
* The SDK already detects this: the data client compares the surface version
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* ---------------------------------------------------------------------------
|
|
7
7
|
*
|
|
8
8
|
* Two surfaces need the same picture: the console's own bug report, and the
|
|
9
|
-
* feedback widget a
|
|
9
|
+
* feedback widget a user's visitors use (`@foir/sdk/react`). One of those
|
|
10
10
|
* ships to npm and the other does not, so the shared half has to sit in the
|
|
11
11
|
* published package — a copy in each would be two answers to "how big is a
|
|
12
12
|
* screenshot", free to drift, and the drift would only show up as an upload the
|
package/dist/next/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@foir/sdk/next` — server-side Foir
|
|
2
|
+
* `@foir/sdk/next` — server-side Foir user auth for the Next.js App Router
|
|
3
3
|
* (the BFF pattern): an HttpOnly-cookie session adapter, server-side session
|
|
4
4
|
* helpers, a provider-callback exchange, and a route-protection middleware.
|
|
5
5
|
*
|
|
@@ -18,5 +18,5 @@ export { readFoirCacheTags, foirCacheTags, createFoirTaggingFetch, foirTagsForRe
|
|
|
18
18
|
export type { FoirHeaderSource, FoirCacheTags, FoirCacheTagsOptions, FoirTaggingReport, FoirTaggingFetchOptions, FoirRecordChange, } from './cacheTags';
|
|
19
19
|
export { createFoirMiddleware } from './middleware';
|
|
20
20
|
export type { FoirMiddlewareConfig, FoirMiddlewareRequest } from './middleware';
|
|
21
|
-
export type {
|
|
21
|
+
export type { FoirUser, SessionAdapter } from '../auth';
|
|
22
22
|
export type { FoirEnv } from '../graphql/foir-request';
|
package/dist/next/session.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FoirUser } from '../auth';
|
|
2
2
|
import type { FoirEnv } from '../graphql/foir-request';
|
|
3
3
|
import type { CookieAdapterOptions, FoirCookieStore } from './cookieAdapter';
|
|
4
4
|
/**
|
|
@@ -6,14 +6,14 @@ import type { CookieAdapterOptions, FoirCookieStore } from './cookieAdapter';
|
|
|
6
6
|
* cookie store (`await cookies()` in a route handler / server action, or the
|
|
7
7
|
* read-only `cookies()` in a Server Component for reads).
|
|
8
8
|
*
|
|
9
|
-
* Note on "verification": the
|
|
9
|
+
* Note on "verification": the user access token is opaque to your app and
|
|
10
10
|
* validated by the Foir API on use, so `getFoirSession` returns the identity
|
|
11
11
|
* stored in the cookies rather than locally verifying a JWT. A rejected/expired
|
|
12
12
|
* bearer surfaces when you call the API (and `@foir/sdk/auth`'s `authedRequest`
|
|
13
13
|
* transparently refreshes it).
|
|
14
14
|
*/
|
|
15
|
-
/** The signed-in
|
|
16
|
-
export declare function getFoirSession(store: FoirCookieStore): Promise<
|
|
15
|
+
/** The signed-in user from the session cookies, or null. */
|
|
16
|
+
export declare function getFoirSession(store: FoirCookieStore): Promise<FoirUser | null>;
|
|
17
17
|
/** The current access token from the session cookies, or null. */
|
|
18
18
|
export declare function getFoirToken(store: FoirCookieStore): Promise<string | null>;
|
|
19
19
|
/** Clear the session cookies (sign out). */
|
|
@@ -26,9 +26,9 @@ interface PasswordArgs {
|
|
|
26
26
|
cookieOptions?: CookieAdapterOptions['cookieOptions'];
|
|
27
27
|
}
|
|
28
28
|
/** Password sign-in in a route handler; persists the session to cookies. */
|
|
29
|
-
export declare function foirSignInWithPassword({ env, store, email, password, cookieOptions, }: PasswordArgs): Promise<
|
|
30
|
-
/** Register a
|
|
31
|
-
export declare function foirRegisterWithPassword({ env, store, email, password, cookieOptions, }: PasswordArgs): Promise<
|
|
29
|
+
export declare function foirSignInWithPassword({ env, store, email, password, cookieOptions, }: PasswordArgs): Promise<FoirUser>;
|
|
30
|
+
/** Register a user in a route handler; persists the session to cookies. */
|
|
31
|
+
export declare function foirRegisterWithPassword({ env, store, email, password, cookieOptions, }: PasswordArgs): Promise<FoirUser>;
|
|
32
32
|
/** Complete an OTP sign-in in a route handler; persists the session to cookies. */
|
|
33
33
|
export declare function foirSignInWithOtp({ env, store, email, otp, cookieOptions, }: {
|
|
34
34
|
env: FoirEnv;
|
|
@@ -36,11 +36,11 @@ export declare function foirSignInWithOtp({ env, store, email, otp, cookieOption
|
|
|
36
36
|
email: string;
|
|
37
37
|
otp: string;
|
|
38
38
|
cookieOptions?: CookieAdapterOptions['cookieOptions'];
|
|
39
|
-
}): Promise<
|
|
39
|
+
}): Promise<FoirUser>;
|
|
40
40
|
/**
|
|
41
41
|
* Complete a provider (OAuth/OIDC) login in your callback route handler:
|
|
42
42
|
* exchange the `code` + `state` for tokens and persist them to the session
|
|
43
|
-
* cookies. Returns the
|
|
43
|
+
* cookies. Returns the user.
|
|
44
44
|
*/
|
|
45
45
|
export declare function completeFoirProviderLogin(opts: {
|
|
46
46
|
env: FoirEnv;
|
|
@@ -49,5 +49,5 @@ export declare function completeFoirProviderLogin(opts: {
|
|
|
49
49
|
code?: string;
|
|
50
50
|
state?: string;
|
|
51
51
|
cookieOptions?: CookieAdapterOptions['cookieOptions'];
|
|
52
|
-
}): Promise<
|
|
52
|
+
}): Promise<FoirUser>;
|
|
53
53
|
export {};
|