@foir/sdk 0.5.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +1126 -0
  2. package/README.md +94 -0
  3. package/dist/auth/auth.d.ts +243 -0
  4. package/dist/auth/hosted.d.ts +40 -0
  5. package/dist/auth/index.d.ts +21 -0
  6. package/dist/auth/operations.d.ts +144 -0
  7. package/dist/auth/ops.d.ts +99 -0
  8. package/dist/auth/session-client.d.ts +76 -0
  9. package/dist/auth-session.js +137 -0
  10. package/dist/auth.js +973 -92
  11. package/dist/chunk-IGNLWT3H.js +35 -0
  12. package/dist/chunk-OHYVKTGB.js +45 -0
  13. package/dist/chunk-TAL5H6VA.js +39 -0
  14. package/dist/client/platform-client.d.ts +29 -0
  15. package/dist/client.js +275 -37
  16. package/dist/codegen/block-registry.d.ts +13 -23
  17. package/dist/codegen/block-registry.js +3 -31
  18. package/dist/codegen/block-scaffold.d.ts +12 -25
  19. package/dist/codegen/block-scaffold.js +3 -41
  20. package/dist/codegen/index.d.ts +12 -61
  21. package/dist/codegen/index.js +7 -11
  22. package/dist/codegen/item-registry.d.ts +28 -0
  23. package/dist/codegen/item-registry.js +7 -0
  24. package/dist/codegen/item-scaffold.d.ts +29 -0
  25. package/dist/codegen/item-scaffold.js +7 -0
  26. package/dist/codegen/preset.d.ts +67 -0
  27. package/dist/codegen/shared.d.ts +36 -0
  28. package/dist/components/EditorProvider.d.ts +36 -0
  29. package/dist/content/content-renderer.d.ts +113 -0
  30. package/dist/content/index.d.ts +18 -0
  31. package/dist/content/richtext.d.ts +118 -0
  32. package/dist/content.js +227 -80
  33. package/dist/data/builtins.d.ts +506 -0
  34. package/dist/data/client.d.ts +166 -0
  35. package/dist/data/document.d.ts +251 -0
  36. package/dist/data/index.d.ts +73 -0
  37. package/dist/data/manifest.d.ts +424 -0
  38. package/dist/data/schema.d.ts +313 -0
  39. package/dist/data/selection.d.ts +524 -0
  40. package/dist/data/subjects.d.ts +221 -0
  41. package/dist/data/subscription-document.d.ts +75 -0
  42. package/dist/data/subscriptions.d.ts +181 -0
  43. package/dist/data/transport.d.ts +325 -0
  44. package/dist/data/ws-transport.d.ts +197 -0
  45. package/dist/data.js +1683 -0
  46. package/dist/graphql/cdn-route.d.ts +26 -0
  47. package/dist/graphql/client.d.ts +30 -0
  48. package/dist/graphql/content-client.d.ts +66 -0
  49. package/dist/graphql/foir-request.d.ts +47 -0
  50. package/dist/hooks/useAutoResize.d.ts +22 -0
  51. package/dist/hooks/useEditor.d.ts +16 -0
  52. package/dist/index.d.ts +10 -394
  53. package/dist/media/BlurhashCanvas.d.ts +10 -0
  54. package/dist/media/ProgressiveMedia.d.ts +56 -0
  55. package/dist/media/index.d.ts +18 -0
  56. package/dist/media/media.d.ts +139 -0
  57. package/dist/media/screen-capture.d.ts +81 -0
  58. package/dist/media/useProgressiveLoad.d.ts +23 -0
  59. package/dist/media.js +102 -0
  60. package/dist/next/cookieAdapter.d.ts +26 -0
  61. package/dist/next/index.d.ts +15 -0
  62. package/dist/next/middleware.d.ts +49 -0
  63. package/dist/next/session.d.ts +53 -0
  64. package/dist/next.js +763 -78
  65. package/dist/protocol/constants.d.ts +6 -0
  66. package/dist/protocol/messages.d.ts +155 -0
  67. package/dist/protocol/validation.d.ts +8 -0
  68. package/dist/push/index.d.ts +14 -0
  69. package/dist/push/operations.d.ts +22 -0
  70. package/dist/push/web-push.d.ts +39 -0
  71. package/dist/push.js +892 -78
  72. package/dist/react/FoirFeedback.d.ts +62 -0
  73. package/dist/react/FoirProvider.d.ts +116 -0
  74. package/dist/react/OAuthButtons.d.ts +20 -0
  75. package/dist/react/SignIn.d.ts +22 -0
  76. package/dist/react/UserButton.d.ts +13 -0
  77. package/dist/react/feedback-notes.d.ts +101 -0
  78. package/dist/react/index.d.ts +25 -0
  79. package/dist/react/localStorageAdapter.d.ts +10 -0
  80. package/dist/react.js +1373 -91
  81. package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
  82. package/dist/realtime.js +1 -1
  83. package/dist/server/callback-client.d.ts +92 -0
  84. package/dist/server/events-client.d.ts +279 -0
  85. package/dist/server/index.d.ts +12 -0
  86. package/dist/server/operation-types.d.ts +134 -0
  87. package/dist/server/require-scoped-token.d.ts +76 -0
  88. package/dist/server/server-client.d.ts +34 -0
  89. package/dist/server/types.d.ts +54 -0
  90. package/dist/server/verify-scoped-token.d.ts +100 -0
  91. package/dist/server/webhook-helpers.d.ts +36 -0
  92. package/dist/server.js +298 -0
  93. package/dist/transport/foir-http.d.ts +234 -0
  94. package/dist/transport/persisted-query.d.ts +68 -0
  95. package/dist/transport/retry.d.ts +61 -0
  96. package/dist/types/index.d.ts +124 -0
  97. package/package.json +35 -13
  98. package/dist/auth.d.ts +0 -382
  99. package/dist/chunk-ADO5FJNM.js +0 -30
  100. package/dist/client.d.ts +0 -135
  101. package/dist/content.d.ts +0 -96
  102. package/dist/media.d.ts +0 -234
  103. package/dist/next.d.ts +0 -178
  104. package/dist/push.d.ts +0 -109
  105. package/dist/react.d.ts +0 -319
  106. package/dist/server.d.ts +0 -534
  107. package/dist/shared-DkWlEd2Z.d.ts +0 -18
package/README.md CHANGED
@@ -24,6 +24,7 @@ pnpm add yjs y-websocket # for @foir/sdk/realtime
24
24
  | --- | --- | --- |
25
25
  | `@foir/sdk` | Editor iframe client — `EditorProvider`, `useEditor`, `useAutoResize`, `createPlatformClient`, host↔editor message protocol | `react` |
26
26
  | `@foir/sdk/client` | Client-agnostic GraphQL transport for SSR/edge loaders — `foirRequest`, `isFoirPreview`, `absolutiseFoirMediaUrl` | `graphql` |
27
+ | `@foir/sdk/data` | Typed data client over `/graphql` — `createFoirClient`, per-key-shaped ops, `include`/`omit`, `fetchFoirManifest`; prints the document for you, so still no `graphql` | **none** |
27
28
  | `@foir/sdk/auth` | Framework-agnostic customer auth — login/register/OTP/provider flows, token lifecycle (`SessionAdapter`), `createFoirSessionClient` (Foir-hosted cookie sessions), `authedRequest`, guards | `graphql` |
28
29
  | `@foir/sdk/react` | Drop-in React auth — `<FoirProvider>` (token or `hostedSession` mode), `useFoirSession`/`useFoirUser`/`useFoirAuth`, `<SignIn>`, `<OAuthButtons>`, `<UserButton>` | `react`, `graphql` |
29
30
  | `@foir/sdk/next` | Next.js App Router BFF auth — `createFoirCookieAdapter`, `getFoirSession`, `foirSignIn*`, `completeFoirProviderLogin`, `createFoirMiddleware` | `graphql` |
@@ -35,6 +36,99 @@ pnpm add yjs y-websocket # for @foir/sdk/realtime
35
36
 
36
37
  ## Usage
37
38
 
39
+ ### Typed data client (no GraphQL, no codegen step)
40
+
41
+ `@foir/sdk/data` executes over the same `/graphql` endpoint every other consumer
42
+ uses — one execution surface, one executor, one set of scope checks, one response
43
+ cache. What you skip is writing the documents: the client loads the selection
44
+ tree `GET /v1/manifest` publishes for that op, applies your `include`/`omit`, and
45
+ prints one.
46
+
47
+ Printing a pre-resolved tree is string building, so the package still needs **no
48
+ `graphql` dependency at all** — no `graphql`, no
49
+ `@graphql-typed-document-node/core`, no `@graphql-codegen/*`, and no new peer.
50
+
51
+ ```ts
52
+ import { createFoirClient } from '@foir/sdk/data';
53
+ // generated by `foir types` from GET /v1/manifest
54
+ import * as generated from './foir.generated';
55
+
56
+ const foir = createFoirClient({
57
+ apiUrl: process.env.PLATFORM_API_URL!, // a bare origin or the /graphql URL
58
+ apiKey: process.env.PLATFORM_API_KEY!,
59
+ generated, // the whole module: types, trees, version
60
+ });
61
+
62
+ const post = await foir.post.findUnique({
63
+ naturalKey: 'hello',
64
+ include: { author: true }, // relations opt in
65
+ omit: ['bodyRichtext'], // drop the heavy field
66
+ });
67
+
68
+ const posts = await foir.post.findMany({ first: 10 }); // a Relay connection
69
+ const hits = await foir.ns.search.searchRecords({ query: 'shoes' });
70
+ const other = await foir.raw('{ designTokens { colors } }'); // any document, any root field
71
+ ```
72
+
73
+ **One import, no type parameter.** `generated` carries both halves of what
74
+ `foir types` writes — the `FoirSchema` type and the runtime selection trees the
75
+ documents are printed from — so `FoirSchema` is inferred rather than restated,
76
+ and an artifact added to the generator later costs your call site nothing. A
77
+ module missing one of those exports is a compile error naming it, not a quiet
78
+ degrade: pass it whole, or do not pass it.
79
+
80
+ The four fields it replaces (`schemaVersion`, `selections`,
81
+ `namespaceSelections`, `selectionUnions`) still work and still win where both
82
+ are given, so an existing client keeps compiling and behaving exactly as it did.
83
+
84
+ **Scalars are always complete**, so `post.title` is typed present and *is*
85
+ present — there is no partial-`select` footgun. `include` adds a relation key,
86
+ `omit` removes a scalar, and polymorphic fields are discriminated unions on
87
+ `__typename` that narrow under an exhaustive `switch` with no casts.
88
+
89
+ **The client is shaped by your credential.** The generated `FoirSchema` contains
90
+ only what that key's manifest lists, so a read-only `pk_` key has no `create` to
91
+ call:
92
+
93
+ ```ts
94
+ foir.post.create({ data }); // ✗ compile error under a pk_ key — not in its manifest
95
+ ```
96
+
97
+ `generated` is optional. Without it — and without `selections` — the client
98
+ fetches `GET /v1/manifest` once at first use and reads the same trees off it. A
99
+ fallback, so a zero-codegen start works, at the cost of a round trip in front of
100
+ the first query on every cold invocation. It says so, once, when it is built
101
+ that way; silence it with `announceManifestFallback: false`.
102
+
103
+ Reads that can be shared are sent to `GET /cdn/graphql` with only the
104
+ persisted-query hash in the URL, so a CDN in front of you can serve the next
105
+ visitor without reaching origin. That is a query, on a `pk_` key, with no
106
+ customer bearer, on the published channel; everything else is a `POST /graphql`.
107
+
108
+ `foir.raw()` is the escape hatch, and it takes a **GraphQL document** — it
109
+ reaches anything the credential can, including root fields no model or namespace
110
+ claims. It returns the whole `data` object.
111
+
112
+ Namespaces (`search`, `customers`, `notifications`) live under `foir.ns` because
113
+ the client carries no runtime schema to tell a namespace from a model. `ns` and
114
+ `raw` are the only reserved names.
115
+
116
+ Session-bound reads take a `bearerToken`, as a string or a function resolved per
117
+ request:
118
+
119
+ ```ts
120
+ const foir = createFoirClient({
121
+ apiUrl,
122
+ apiKey,
123
+ generated,
124
+ bearerToken: () => session.get('foir_access_token'),
125
+ });
126
+ ```
127
+
128
+ Already have `PLATFORM_API_URL` / `PLATFORM_API_KEY` / `FOIR_PREVIEW` wired for
129
+ the GraphQL path? `createFoirClientFromEnv(env, { generated })` reuses them,
130
+ including the preview `sk_` key and the draft schema channel.
131
+
38
132
  ### Typed GraphQL from a server loader
39
133
 
40
134
  `foirRequest` is a typed `fetch` wrapper, not a GraphQL client: it carries the
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Foir customer auth — framework-agnostic flows, token lifecycle, and guards.
3
+ *
4
+ * Session I/O is injected via {@link SessionAdapter} (no Hydrogen/React-Router
5
+ * coupling). Pre-login flows authenticate with the project `x-api-key`;
6
+ * post-login reads add the customer bearer. The project-specific profile (e.g.
7
+ * `isAdmin`) is resolved by a consumer-supplied `resolveUser` — the SDK never
8
+ * hardcodes the per-project customer profile shape.
9
+ *
10
+ * TRANSPORT. Every flow below addresses the server's generated `auth` namespace
11
+ * through `./ops`, which is `@foir/sdk/data`'s op transport. This module holds
12
+ * no GraphQL documents and issues no requests of its own. What it does hold is
13
+ * the part a generated namespace does not give you and never could: where the
14
+ * tokens live, when they rotate, what a 401 means, and what to clear when a
15
+ * session ends.
16
+ */
17
+ import { type FoirEnv } from '../graphql/foir-request';
18
+ import { type AuthOpName, type AuthOpOptions } from './ops';
19
+ import { type AuthConfig, type AuthProviderPublic, type CurrentUser, type CustomerAuthResult, type DefaultAuthProvider, type DeleteAccountResult, type ProviderCallbackInput, type ProviderLoginInput, type ProviderLoginResult, type ProviderOTPVerifyInput, type ProviderToken, type SignupContext, type WaitlistJoinResult } from './operations';
20
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
21
+ /** An access token + its rotating refresh token. */
22
+ export interface FoirAuthTokens {
23
+ token: string;
24
+ refreshToken: string;
25
+ }
26
+ /** The customer identity carried by an auth result. */
27
+ export interface FoirCustomer {
28
+ id: string;
29
+ email: string;
30
+ }
31
+ /** The resolved customer for a guarded surface. */
32
+ export interface FoirUser {
33
+ id: string;
34
+ email: string;
35
+ isAdmin: boolean;
36
+ }
37
+ /**
38
+ * Injected session store. Methods may be sync or async so consumers can plug
39
+ * in a cookie session (Hydrogen/Remix), a KV store, etc.
40
+ */
41
+ export interface SessionAdapter {
42
+ get(key: string): string | null | undefined | Promise<string | null | undefined>;
43
+ set(key: string, value: string): void | Promise<void>;
44
+ unset(key: string): void | Promise<void>;
45
+ }
46
+ /** Login with email + password. Requires the project to have passwords enabled. */
47
+ export declare function customerLogin(env: FoirEnv, vars: {
48
+ email: string;
49
+ password: string;
50
+ }): Promise<CustomerAuthResult>;
51
+ /** Register a new customer with email + password. */
52
+ export declare function customerRegister(env: FoirEnv, vars: {
53
+ email: string;
54
+ password: string;
55
+ }): Promise<CustomerAuthResult>;
56
+ /** Request a one-time password be emailed to the customer. */
57
+ export declare function customerRequestOTP(env: FoirEnv, email: string): Promise<boolean>;
58
+ /** Complete an OTP login. */
59
+ export declare function customerLoginOTP(env: FoirEnv, vars: {
60
+ email: string;
61
+ otp: string;
62
+ }): Promise<CustomerAuthResult>;
63
+ /** Rotate an access token using a refresh token. */
64
+ export declare function customerRefreshToken(env: FoirEnv, refreshToken: string): Promise<CustomerAuthResult>;
65
+ /**
66
+ * Initiate provider (OAuth/OIDC) login — returns a `redirectUrl` to send the
67
+ * user to, or an inline `token` for non-redirect providers.
68
+ */
69
+ export declare function customerLoginWithProvider(env: FoirEnv, input: ProviderLoginInput): Promise<ProviderLoginResult>;
70
+ /** Complete a provider login from the OAuth callback (`code`/`state`). */
71
+ export declare function customerProviderCallback(env: FoirEnv, input: ProviderCallbackInput): Promise<CustomerAuthResult>;
72
+ /** Verify an OTP issued by an external provider. */
73
+ export declare function customerProviderVerifyOTP(env: FoirEnv, input: ProviderOTPVerifyInput): Promise<CustomerAuthResult>;
74
+ /** List the project's configured auth providers (for a login UI). */
75
+ export declare function getAuthProviders(env: FoirEnv): Promise<AuthProviderPublic[]>;
76
+ /**
77
+ * Read the project's public auth configuration — which built-in methods are
78
+ * enabled and which external providers to offer. This is what a login screen
79
+ * renders itself from, before any customer token exists.
80
+ */
81
+ export declare function getAuthConfig(env: FoirEnv): Promise<AuthConfig>;
82
+ /**
83
+ * The provider a login screen should default to, or `null` when the project has
84
+ * configured none.
85
+ */
86
+ export declare function getDefaultAuthProvider(env: FoirEnv): Promise<DefaultAuthProvider | null>;
87
+ /**
88
+ * Read the project's customer signup mode (public/restricted/waitlist/closed)
89
+ * so a self-hosted frontend can render the right signup surface proactively.
90
+ */
91
+ export declare function customerSignupContext(env: FoirEnv): Promise<SignupContext>;
92
+ /** Join the project's signup waitlist. The project must be in waitlist mode. */
93
+ export declare function customerJoinWaitlist(env: FoirEnv, email: string): Promise<WaitlistJoinResult>;
94
+ /**
95
+ * Send a password-reset email. Resolves `true` whether or not the address has
96
+ * an account: the server answers identically either way, on purpose, so this
97
+ * endpoint cannot be used to test whether someone has signed up.
98
+ */
99
+ export declare function customerRequestPasswordReset(env: FoirEnv, email: string): Promise<boolean>;
100
+ /**
101
+ * Redeem an emailed reset token and set a new password. Returns a fresh session
102
+ * — resetting a password signs the customer in — so pair it with
103
+ * {@link saveSession}, or use {@link resetPasswordAndSignIn} which does both.
104
+ */
105
+ export declare function customerResetPassword(env: FoirEnv, vars: {
106
+ token: string;
107
+ newPassword: string;
108
+ }): Promise<CustomerAuthResult>;
109
+ /** Redeem an emailed verification token. */
110
+ export declare function customerVerifyEmail(env: FoirEnv, token: string): Promise<boolean>;
111
+ /**
112
+ * Change the calling customer's password. Requires the current password as well
113
+ * as the bearer, so a stolen token alone cannot take the account over.
114
+ */
115
+ export declare function customerUpdatePassword(env: FoirEnv, vars: {
116
+ currentPassword: string;
117
+ newPassword: string;
118
+ }, options?: AuthOpOptions): Promise<boolean>;
119
+ /**
120
+ * The calling customer, or `null` when the request carried no customer token.
121
+ * An absent session is an answer rather than a failure, so this does not throw.
122
+ */
123
+ export declare function currentUser(env: FoirEnv, options?: AuthOpOptions): Promise<CurrentUser | null>;
124
+ /**
125
+ * The stored upstream provider token for the calling customer, refreshed
126
+ * transparently by the server when it has expired. `null` when there is no
127
+ * customer token on the request or none stored for that provider.
128
+ */
129
+ export declare function myProviderToken(env: FoirEnv, providerKey: string, options?: AuthOpOptions): Promise<ProviderToken | null>;
130
+ /**
131
+ * End the session server-side, revoking stored provider tokens. This does NOT
132
+ * clear local session state — {@link signOut} is the pairing that does both.
133
+ */
134
+ export declare function customerLogout(env: FoirEnv, options?: AuthOpOptions): Promise<boolean>;
135
+ /**
136
+ * Delete the calling customer's account and their owned records and files.
137
+ * Irreversible. `password` is required for password accounts and omitted for
138
+ * provider-only ones; the server enforces the actual re-auth gate and fails
139
+ * closed, so omitting it is refused rather than allowed.
140
+ */
141
+ export declare function deleteMyAccount(env: FoirEnv, vars: {
142
+ confirm: boolean;
143
+ password?: string;
144
+ }, options?: AuthOpOptions): Promise<DeleteAccountResult>;
145
+ /** Persist an auth result to the session; returns the customer identity. */
146
+ export declare function saveSession(adapter: SessionAdapter, result: CustomerAuthResult): Promise<FoirCustomer>;
147
+ /** Clear all Foir auth keys from the session. */
148
+ export declare function clearSession(adapter: SessionAdapter): Promise<void>;
149
+ /** Read the stored access token (null when signed out). */
150
+ export declare function getSessionToken(adapter: SessionAdapter): Promise<string | null>;
151
+ /** Read the stored customer identity (null when signed out). */
152
+ export declare function getSessionCustomer(adapter: SessionAdapter): Promise<FoirCustomer | null>;
153
+ /** Login with email + password and persist the session. */
154
+ export declare function loginWithPassword(adapter: SessionAdapter, env: FoirEnv, vars: {
155
+ email: string;
156
+ password: string;
157
+ }): Promise<FoirCustomer>;
158
+ /** Register with email + password and persist the session. */
159
+ export declare function registerWithPassword(adapter: SessionAdapter, env: FoirEnv, vars: {
160
+ email: string;
161
+ password: string;
162
+ }): Promise<FoirCustomer>;
163
+ /** Complete an OTP login and persist the session. */
164
+ export declare function loginWithOTP(adapter: SessionAdapter, env: FoirEnv, vars: {
165
+ email: string;
166
+ otp: string;
167
+ }): Promise<FoirCustomer>;
168
+ /**
169
+ * Complete a forgot-password flow: redeem the emailed token, set the new
170
+ * password, and persist the session the server mints. The peer of
171
+ * {@link loginWithPassword} for the branch where the customer cannot log in.
172
+ */
173
+ export declare function resetPasswordAndSignIn(adapter: SessionAdapter, env: FoirEnv, vars: {
174
+ token: string;
175
+ newPassword: string;
176
+ }): Promise<FoirCustomer>;
177
+ /**
178
+ * End the session on both sides: revoke it server-side, then clear local state.
179
+ *
180
+ * The local clear happens even when the server call fails. A customer who
181
+ * pressed sign-out and got a network error must not be left holding a session
182
+ * on the device in front of them — the token expires on its own, and the
183
+ * failure mode of clearing anyway (one orphaned provider token) is strictly
184
+ * better than the failure mode of not (a signed-in browser the user believes is
185
+ * signed out).
186
+ */
187
+ export declare function signOut(adapter: SessionAdapter, env: FoirEnv): Promise<void>;
188
+ /**
189
+ * Refresh the access token from the stored refresh token, persisting the
190
+ * rotated pair. Returns the new tokens, or null when there's no refresh token
191
+ * or the refresh fails (the session is cleared in that case).
192
+ */
193
+ export declare function refreshSession(adapter: SessionAdapter, env: FoirEnv): Promise<FoirAuthTokens | null>;
194
+ /**
195
+ * Run a bearer-authed GraphQL operation with refresh-with-retry: uses the
196
+ * stored access token, and on a 401-shaped failure (rejected/expired bearer)
197
+ * attempts a single token refresh and retries once. Any other error — network
198
+ * blips, server errors, validation — propagates without a retry so transient
199
+ * mutation failures can't be duplicated. Throws the original error if there's
200
+ * no refresh path.
201
+ */
202
+ export declare function authedRequest<TData, TVariables>(adapter: SessionAdapter, env: FoirEnv, document: TypedDocumentNode<TData, TVariables>, variables?: TVariables): Promise<TData>;
203
+ /**
204
+ * {@link authedRequest}'s twin for the generated `auth` namespace: run a
205
+ * post-credential auth op with the stored access token, refreshing once and
206
+ * retrying once on a 401-shaped failure.
207
+ *
208
+ * The two exist side by side because they serve different documents, not
209
+ * different transports. `authedRequest` runs the CONSUMER's document — the
210
+ * project's own `myProfile` and friends, whose shape the SDK cannot know — and
211
+ * this runs a root field the server generated. Refresh-with-retry is the same
212
+ * behaviour in both, and it is the reason either is worth having: it is session
213
+ * management, which is the half a generated namespace does not give you.
214
+ */
215
+ export declare function authedOp<T>(adapter: SessionAdapter, env: FoirEnv, op: AuthOpName, args?: Record<string, unknown>): Promise<T>;
216
+ export interface RequireAuthOptions {
217
+ /**
218
+ * Resolve the full {@link FoirUser} (including the project-specific
219
+ * `isAdmin`) from the session identity. Typically runs the project's
220
+ * `myProfile` query via {@link authedRequest}. Defaults to `isAdmin: false`.
221
+ */
222
+ resolveUser?: (ctx: {
223
+ token: string;
224
+ customer: FoirCustomer;
225
+ }) => FoirUser | Promise<FoirUser>;
226
+ /**
227
+ * Called when there's no valid session — throw your framework's redirect
228
+ * (e.g. `throw redirect(loginUrl)`). Must not return.
229
+ */
230
+ onUnauthenticated: () => never | Promise<never>;
231
+ }
232
+ /**
233
+ * Require a signed-in customer. Resolves the {@link FoirUser} via
234
+ * `resolveUser` (or a minimal identity), or invokes `onUnauthenticated` when
235
+ * there's no session.
236
+ */
237
+ export declare function requireAuth(adapter: SessionAdapter, options: RequireAuthOptions): Promise<FoirUser>;
238
+ export interface RequireAdminOptions extends RequireAuthOptions {
239
+ /** Called when the user is authenticated but not an admin. Must not return. */
240
+ onForbidden: () => never | Promise<never>;
241
+ }
242
+ /** Require a signed-in customer who is also an admin. */
243
+ export declare function requireAdmin(adapter: SessionAdapter, options: RequireAdminOptions): Promise<FoirUser>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Hosted-login bridge — for storefronts that delegate sign-in to a Foir
3
+ * white-label front door (the `customerLoginWithFoir` flow) instead of
4
+ * rendering their own login UI.
5
+ *
6
+ * The front door sets a first-party session cookie on the registrable domain;
7
+ * because that domain is shared, the cookie reaches the storefront, which
8
+ * exchanges it server-side for a short-lived customer bearer + identity. The
9
+ * cookie is the durable credential (re-exchanged on bearer expiry) — there's
10
+ * no separate refresh token in this flow.
11
+ *
12
+ * Every URL/cookie/path is CONFIG, not a constant, so a domain restructure
13
+ * is a config change, not a code change.
14
+ */
15
+ export interface HostedLoginConfig {
16
+ /** White-label front-door origin, e.g. `https://login.example.com`. */
17
+ loginOrigin: string;
18
+ /** Session cookie name set on the registrable domain. Default `foir_customer_session`. */
19
+ cookieName?: string;
20
+ /** Session-exchange path on the front door. Default `/session/token`. */
21
+ sessionPath?: string;
22
+ }
23
+ /**
24
+ * Build the redirect URL to the hosted front door, forwarding a best-effort
25
+ * absolute `returnTo` (honoured if the front door supports it).
26
+ */
27
+ export declare function hostedLoginUrl(config: HostedLoginConfig, returnTo?: string): string;
28
+ /** The identity + bearer returned by a successful hosted-session exchange. */
29
+ export interface HostedSession {
30
+ token: string;
31
+ customerId: string;
32
+ email: string;
33
+ }
34
+ /**
35
+ * Exchange the hosted-login cookie on an incoming request for a customer
36
+ * bearer + identity (server-to-server: forwards the cookie to the front
37
+ * door's session endpoint with the CSRF header). Returns null when there's no
38
+ * cookie or it's expired/revoked.
39
+ */
40
+ export declare function fetchHostedSession(request: Request, config: HostedLoginConfig): Promise<HostedSession | null>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@foir/sdk/auth` — framework-agnostic Foir customer auth.
3
+ *
4
+ * The whole customer auth surface — login, register, OTP, external providers,
5
+ * refresh, password management, email verification and account deletion — over
6
+ * the server's generated `auth` namespace, plus token lifecycle with an
7
+ * injected `SessionAdapter`, guards, and the optional hosted-login bridge.
8
+ *
9
+ * There are no operation documents here any more. The flows address root fields
10
+ * the caller's own manifest advertises, through the same op transport
11
+ * `@foir/sdk/data` uses, so `graphql` is not on this module's path at all.
12
+ */
13
+ export { customerLogin, customerRegister, customerRequestOTP, customerLoginOTP, customerRefreshToken, customerLoginWithProvider, customerProviderCallback, customerProviderVerifyOTP, getAuthProviders, getAuthConfig, getDefaultAuthProvider, customerSignupContext, customerJoinWaitlist, customerRequestPasswordReset, customerResetPassword, customerUpdatePassword, customerVerifyEmail, currentUser, myProviderToken, customerLogout, deleteMyAccount, saveSession, clearSession, getSessionToken, getSessionCustomer, loginWithPassword, registerWithPassword, loginWithOTP, resetPasswordAndSignIn, refreshSession, signOut, authedRequest, authedOp, requireAuth, requireAdmin, } from './auth';
14
+ export type { FoirAuthTokens, FoirCustomer, FoirUser, SessionAdapter, RequireAuthOptions, RequireAdminOptions, } from './auth';
15
+ export { AUTH_OPS } from './ops';
16
+ export type { AuthOpName, AuthOpOptions } from './ops';
17
+ export { hostedLoginUrl, fetchHostedSession } from './hosted';
18
+ export type { HostedLoginConfig, HostedSession } from './hosted';
19
+ export { createFoirSessionClient } from './session-client';
20
+ export type { FoirSessionClient, FoirSessionConfig, FoirSessionUser, } from './session-client';
21
+ export type { CustomerAuthResult, ProviderLoginResult, AuthProviderPublic, ProviderLoginInput, ProviderCallbackInput, ProviderOTPVerifyInput, SignupContext, SignupMode, WaitlistJoinResult, AuthConfig, AuthConfigProvider, DefaultAuthProvider, CurrentUser, ProviderToken, DeleteAccountResult, } from './operations';
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Foir customer-auth result and input types.
3
+ *
4
+ * These are PLATFORM-STABLE primitives — login / register / refresh / OTP /
5
+ * provider login / password management / account lifecycle are fixed-shape
6
+ * operations served by api-public for every project (gated per-project by the
7
+ * password / OTP / provider flags, but identical in shape when present). Unlike
8
+ * per-project model types they do not vary by configured models, so the SDK
9
+ * owns these declarations directly rather than relying on the consumer's
10
+ * codegen.
11
+ *
12
+ * WHAT USED TO BE HERE. This file also held eleven hand-written GraphQL
13
+ * documents, `parse()`d at module load. They are gone. Every flow in `./auth`
14
+ * now addresses the server's generated `auth` namespace through the same op
15
+ * transport `@foir/sdk/data` uses, so the SDK has one execution surface rather
16
+ * than a private one for auth and a shared one for everything else. Two
17
+ * consequences worth knowing:
18
+ *
19
+ * - the shapes below are no longer a document's selection set. They describe
20
+ * what the server's own payload writer selects for these fields, which is
21
+ * every scalar the type has. `authNamespaceOpShapes` in `./ops` is the
22
+ * machine-checked version of that sentence.
23
+ * - `graphql` is no longer imported here, or anywhere under `./auth`'s flow
24
+ * path. The top-level `parse()` calls were what no bundler could shake out,
25
+ * and they are the reason `@foir/sdk/auth/session` had to exist as a
26
+ * separate graphql-free door.
27
+ *
28
+ * The project-specific customer PROFILE (e.g. `myProfile.isAdmin`) is still NOT
29
+ * here — that shape varies per project, so it stays consumer-injected (see
30
+ * `requireAuth`'s `resolveUser`).
31
+ */
32
+ /** The common auth result every login/register/refresh mutation returns. */
33
+ export interface CustomerAuthResult {
34
+ token: string;
35
+ refreshToken: string;
36
+ customerId: string;
37
+ email: string;
38
+ }
39
+ /** Result of initiating a provider login — a redirect URL or an inline token. */
40
+ export interface ProviderLoginResult {
41
+ /** OAuth/OIDC authorization URL to redirect the user to (redirect flows). */
42
+ redirectUrl: string | null;
43
+ /** Inline access token (non-redirect provider flows). */
44
+ token: string | null;
45
+ }
46
+ /** A configured auth provider, as exposed to unauthenticated callers. */
47
+ export interface AuthProviderPublic {
48
+ key: string;
49
+ name: string;
50
+ /** OAUTH2 | TOKEN_SSO | SAML | OIDC | EXTERNAL */
51
+ type: string;
52
+ isDefault: boolean | null;
53
+ }
54
+ export interface ProviderLoginInput {
55
+ providerKey: string;
56
+ email?: string;
57
+ returnTo?: string;
58
+ /** Override the provider's default redirect_uri (must be allow-listed). */
59
+ redirectUri?: string;
60
+ }
61
+ export interface ProviderCallbackInput {
62
+ providerKey: string;
63
+ code?: string;
64
+ state?: string;
65
+ email?: string;
66
+ idToken?: string;
67
+ }
68
+ export interface ProviderOTPVerifyInput {
69
+ providerKey: string;
70
+ email: string;
71
+ otp: string;
72
+ }
73
+ /** The project's customer signup mode. */
74
+ export type SignupMode = 'PUBLIC' | 'RESTRICTED' | 'WAITLIST' | 'CLOSED';
75
+ /** The signup surface a self-hosted frontend reads to render the right UI. */
76
+ export interface SignupContext {
77
+ signupMode: SignupMode;
78
+ }
79
+ /** The queued (or existing pending) entry a waitlist join returns. */
80
+ export interface WaitlistJoinResult {
81
+ id: string;
82
+ email: string;
83
+ /** Waitlist lifecycle status; PENDING on a fresh join. */
84
+ status: string;
85
+ }
86
+ /** One provider as it appears inside {@link AuthConfig}. */
87
+ export interface AuthConfigProvider {
88
+ key: string;
89
+ name: string;
90
+ /** OAUTH2 | TOKEN_SSO | SAML | OIDC | EXTERNAL */
91
+ type: string;
92
+ }
93
+ /**
94
+ * The project's public auth configuration: which built-in methods are on, and
95
+ * which external providers a login screen should offer. Readable before any
96
+ * customer token exists — it is what a login screen renders itself from.
97
+ */
98
+ export interface AuthConfig {
99
+ passwordEnabled: boolean;
100
+ otpEnabled: boolean;
101
+ providers: AuthConfigProvider[] | null;
102
+ }
103
+ /**
104
+ * The provider a login screen should default to, or `null` when the project has
105
+ * configured none.
106
+ */
107
+ export interface DefaultAuthProvider {
108
+ key: string | null;
109
+ name: string | null;
110
+ type: string | null;
111
+ }
112
+ /**
113
+ * The calling customer, resolved from the bearer token. `null` when the request
114
+ * carried no customer token — an absent session is an answer, not a failure.
115
+ */
116
+ export interface CurrentUser {
117
+ id: string;
118
+ email: string;
119
+ /** ACTIVE | PENDING | SUSPENDED | … */
120
+ status: string;
121
+ /** ISO-8601, or null when the address is unverified. */
122
+ emailVerifiedAt: string | null;
123
+ /** ISO-8601. */
124
+ createdAt: string;
125
+ }
126
+ /**
127
+ * A stored upstream provider token for the calling customer. `null` when there
128
+ * is no customer token on the request, or none stored for that provider.
129
+ */
130
+ export interface ProviderToken {
131
+ providerKey: string;
132
+ accessToken: string;
133
+ refreshToken: string | null;
134
+ /** ISO-8601. */
135
+ expiresAt: string | null;
136
+ /** Space-separated provider scopes, as the provider issued them. */
137
+ scopes: string | null;
138
+ }
139
+ /** What a self-serve account deletion removed. */
140
+ export interface DeleteAccountResult {
141
+ success: boolean;
142
+ recordsDeleted: number;
143
+ filesDeleted: number;
144
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The auth client's execution path: the server's generated `auth` namespace.
3
+ *
4
+ * `@foir/sdk/auth` used to carry its own transport — eleven hand-written
5
+ * GraphQL documents `parse()`d at module load and posted to `/graphql`. That was
6
+ * a second wire protocol for one substrate inside one package, and it had the
7
+ * cost such things always have: the hand-written set covered nine of the
8
+ * surface's root fields and the other eleven — the whole of password
9
+ * management, email verification and account deletion — were reachable nowhere
10
+ * in the SDK, so "forgot password" meant dropping to hand-written GraphQL.
11
+ *
12
+ * Now every flow addresses a root field the caller's own manifest advertises,
13
+ * printed and executed by the same functions `@foir/sdk/data` runs every model
14
+ * op through. ONE transport, and the auth client's remaining job is the half
15
+ * that was always the hard part: token storage, refresh, redirects and the
16
+ * React context.
17
+ *
18
+ * WHAT THIS FILE IS NOT. It is not a schema. It names root fields and nothing
19
+ * else — no types, no selection sets, no argument coercion. The SHAPE of what an
20
+ * op selects comes from `namespaces.auth.ops.<op>.payload` in the caller's own
21
+ * manifest, and the argument types from `argTypes` beside it; if this file ever
22
+ * needed to know a field's type itself, the op vocabulary would be
23
+ * under-specified and the fix would belong on the server.
24
+ *
25
+ * ORDERING, and it is load-bearing: these ops resolve against the manifest the
26
+ * deployed api-public serves. A server that does not publish per-op payloads for
27
+ * the `auth` namespace answers every op below with a refusal naming the op. The
28
+ * server half ships first.
29
+ */
30
+ import { type FoirEnv } from '../transport/foir-http';
31
+ /**
32
+ * The `auth` namespace's root field names, as the manifest lists them.
33
+ *
34
+ * Written out rather than derived so a typo is a compile error at the call site
35
+ * instead of a 400 at runtime, and so this file is the one place to read what
36
+ * the auth client addresses. The names are the server's, unmodified — the op
37
+ * vocabulary for every namespace except `operations` IS the root field name, so
38
+ * nothing here is constructed and nothing can drift into a different name.
39
+ */
40
+ export declare const AUTH_OPS: {
41
+ readonly authConfig: "authConfig";
42
+ readonly authProviders: "authProviders";
43
+ readonly defaultAuthProvider: "defaultAuthProvider";
44
+ readonly customerSignupContext: "customerSignupContext";
45
+ readonly currentUser: "currentUser";
46
+ readonly myProviderToken: "myProviderToken";
47
+ readonly customerLogin: "customerLogin";
48
+ readonly customerRegister: "customerRegister";
49
+ readonly customerRequestOTP: "customerRequestOTP";
50
+ readonly customerLoginOTP: "customerLoginOTP";
51
+ readonly customerRefreshToken: "customerRefreshToken";
52
+ readonly customerLogout: "customerLogout";
53
+ readonly customerJoinWaitlist: "customerJoinWaitlist";
54
+ readonly customerRequestPasswordReset: "customerRequestPasswordReset";
55
+ readonly customerResetPassword: "customerResetPassword";
56
+ readonly customerUpdatePassword: "customerUpdatePassword";
57
+ readonly customerVerifyEmail: "customerVerifyEmail";
58
+ readonly deleteMyAccount: "deleteMyAccount";
59
+ readonly customerLoginWithProvider: "customerLoginWithProvider";
60
+ readonly customerProviderCallback: "customerProviderCallback";
61
+ readonly customerProviderVerifyOTP: "customerProviderVerifyOTP";
62
+ };
63
+ /** One of the auth namespace's root field names. */
64
+ export type AuthOpName = (typeof AUTH_OPS)[keyof typeof AUTH_OPS];
65
+ /** Per-call overrides for an auth op. */
66
+ export interface AuthOpOptions {
67
+ /**
68
+ * Customer bearer token. api-public still needs the `x-api-key` to resolve
69
+ * the project; the bearer adds the customer identity the post-credential ops
70
+ * (`currentUser`, `customerUpdatePassword`, `deleteMyAccount`, …) are
71
+ * authorised by. Ignored by the pre-credential ops, which mint it.
72
+ */
73
+ bearerToken?: string;
74
+ /**
75
+ * `fetch` implementation. Defaults to `globalThis.fetch`. Injectable for
76
+ * tests and for runtimes that hand you a bound `fetch` (Hydrogen/Oxygen).
77
+ */
78
+ fetch?: typeof globalThis.fetch;
79
+ /** Extra headers, applied last so they can override the ones above. */
80
+ headers?: Readonly<Record<string, string>>;
81
+ }
82
+ /**
83
+ * Forget every memoised descriptor.
84
+ *
85
+ * For tests and for a long-lived process that has rotated a key. Not part of a
86
+ * normal flow — the descriptor is refetched on schema change by the drift check
87
+ * on the data client, and auth ops are not where that is noticed.
88
+ */
89
+ export declare function clearAuthManifestCache(): void;
90
+ /**
91
+ * Execute one auth op and return the root field's value.
92
+ *
93
+ * Arguments are sent as GraphQL variables, typed from the op's published
94
+ * `argTypes`. `undefined` entries are dropped rather than sent as null: a
95
+ * GraphQL argument that is absent and one that is explicitly null mean different
96
+ * things to the resolvers below (`deleteMyAccount`'s optional `password` is the
97
+ * live example).
98
+ */
99
+ export declare function authOp<T>(env: FoirEnv, op: AuthOpName, args?: Record<string, unknown>, options?: AuthOpOptions): Promise<T>;