@foir/sdk 0.5.0 → 0.7.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 (82) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/README.md +63 -0
  3. package/dist/auth/auth.d.ts +140 -0
  4. package/dist/auth/hosted.d.ts +40 -0
  5. package/dist/auth/index.d.ts +15 -0
  6. package/dist/auth/operations.d.ts +123 -0
  7. package/dist/auth/session-client.d.ts +76 -0
  8. package/dist/auth-session.js +137 -0
  9. package/dist/auth.js +238 -26
  10. package/dist/client/platform-client.d.ts +29 -0
  11. package/dist/client.js +281 -37
  12. package/dist/codegen/block-registry.d.ts +4 -8
  13. package/dist/codegen/block-scaffold.d.ts +4 -8
  14. package/dist/codegen/index.d.ts +8 -62
  15. package/dist/codegen/preset.d.ts +60 -0
  16. package/dist/codegen/shared.d.ts +25 -0
  17. package/dist/components/EditorProvider.d.ts +36 -0
  18. package/dist/content/content-renderer.d.ts +47 -0
  19. package/dist/content/index.d.ts +12 -0
  20. package/dist/content/richtext.d.ts +70 -0
  21. package/dist/data/builtins.d.ts +296 -0
  22. package/dist/data/client.d.ts +94 -0
  23. package/dist/data/index.d.ts +54 -0
  24. package/dist/data/manifest.d.ts +161 -0
  25. package/dist/data/schema.d.ts +243 -0
  26. package/dist/data/selection.d.ts +330 -0
  27. package/dist/data/transport.d.ts +107 -0
  28. package/dist/data.js +309 -0
  29. package/dist/graphql/cdn-route.d.ts +42 -0
  30. package/dist/graphql/client.d.ts +30 -0
  31. package/dist/graphql/content-client.d.ts +66 -0
  32. package/dist/graphql/foir-request.d.ts +47 -0
  33. package/dist/hooks/useAutoResize.d.ts +22 -0
  34. package/dist/hooks/useEditor.d.ts +16 -0
  35. package/dist/index.d.ts +10 -394
  36. package/dist/media/BlurhashCanvas.d.ts +10 -0
  37. package/dist/media/ProgressiveMedia.d.ts +56 -0
  38. package/dist/media/index.d.ts +14 -0
  39. package/dist/media/media.d.ts +139 -0
  40. package/dist/media/useProgressiveLoad.d.ts +23 -0
  41. package/dist/media.js +5 -0
  42. package/dist/next/cookieAdapter.d.ts +26 -0
  43. package/dist/next/index.d.ts +15 -0
  44. package/dist/next/middleware.d.ts +49 -0
  45. package/dist/next/session.d.ts +53 -0
  46. package/dist/next.js +230 -26
  47. package/dist/protocol/constants.d.ts +6 -0
  48. package/dist/protocol/messages.d.ts +155 -0
  49. package/dist/protocol/validation.d.ts +8 -0
  50. package/dist/push/index.d.ts +14 -0
  51. package/dist/push/operations.d.ts +22 -0
  52. package/dist/push/web-push.d.ts +39 -0
  53. package/dist/push.js +230 -26
  54. package/dist/react/FoirProvider.d.ts +116 -0
  55. package/dist/react/OAuthButtons.d.ts +20 -0
  56. package/dist/react/SignIn.d.ts +22 -0
  57. package/dist/react/UserButton.d.ts +13 -0
  58. package/dist/react/index.d.ts +19 -0
  59. package/dist/react/localStorageAdapter.d.ts +10 -0
  60. package/dist/react.js +230 -26
  61. package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
  62. package/dist/server/callback-client.d.ts +92 -0
  63. package/dist/server/index.d.ts +8 -0
  64. package/dist/server/operation-types.d.ts +134 -0
  65. package/dist/server/require-scoped-token.d.ts +76 -0
  66. package/dist/server/server-client.d.ts +34 -0
  67. package/dist/server/types.d.ts +54 -0
  68. package/dist/server/verify-scoped-token.d.ts +100 -0
  69. package/dist/server/webhook-helpers.d.ts +36 -0
  70. package/dist/transport/foir-http.d.ts +217 -0
  71. package/dist/transport/retry.d.ts +61 -0
  72. package/dist/types/index.d.ts +124 -0
  73. package/package.json +23 -13
  74. package/dist/auth.d.ts +0 -382
  75. package/dist/client.d.ts +0 -135
  76. package/dist/content.d.ts +0 -96
  77. package/dist/media.d.ts +0 -234
  78. package/dist/next.d.ts +0 -178
  79. package/dist/push.d.ts +0 -109
  80. package/dist/react.d.ts +0 -319
  81. package/dist/server.d.ts +0 -534
  82. package/dist/shared-DkWlEd2Z.d.ts +0 -18
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Public type definitions for the Editor SDK.
3
+ */
4
+ export type { EditorPlacement } from '../server/types.js';
5
+ /** Data provided by the host when the editor initializes. */
6
+ export interface EditorInit {
7
+ values: Record<string, unknown>;
8
+ metadata?: Record<string, unknown>;
9
+ modelKey: string;
10
+ recordId: string;
11
+ naturalKey?: string;
12
+ versionId: string | null;
13
+ variantKey: string | null;
14
+ locale: string | null;
15
+ schema: unknown;
16
+ mode: 'edit' | 'readonly';
17
+ theme: 'light' | 'dark';
18
+ /** Secret API key for public API access. */
19
+ apiKey: string;
20
+ /** Public API GraphQL endpoint URL (provided by host). */
21
+ apiUrl: string;
22
+ /** Config connection domain (e.g. my-store.myshopify.com) */
23
+ connectionDomain?: string;
24
+ /** How this editor is rendered in the admin UI. */
25
+ configMode?: 'editor' | 'sidebar';
26
+ /** Currently selected field keys (sidebar mode). */
27
+ selectedFieldKeys?: string[];
28
+ /** Available fields for selection (sidebar mode). */
29
+ selectableFields?: Array<{
30
+ key: string;
31
+ label: string;
32
+ type: string;
33
+ }>;
34
+ /** Locale context (sidebar mode). */
35
+ localeContext?: LocaleContext | null;
36
+ }
37
+ /** Locale context passed by the host for sidebar editors. */
38
+ export interface LocaleContext {
39
+ currentLocale: string;
40
+ sourceLocale: string;
41
+ isTranslating: boolean;
42
+ locales: Array<{
43
+ code: string;
44
+ displayName: string;
45
+ isDefault: boolean;
46
+ }>;
47
+ }
48
+ /** Value exposed by the EditorProvider context. */
49
+ export interface EditorContextValue {
50
+ /** Whether the editor has received init data from the host. */
51
+ isReady: boolean;
52
+ /** Initial data from the host (available after init). */
53
+ init: EditorInit | null;
54
+ /** Current host theme. */
55
+ theme: 'light' | 'dark';
56
+ /** Platform GraphQL client (available after init). */
57
+ client: PlatformClient;
58
+ /** Send complete form values to the host (full replacement). */
59
+ updateValues: (values: Record<string, unknown>) => void;
60
+ /** Send a single field update to the host. */
61
+ updateField: (path: string, value: unknown) => void;
62
+ /** Request the host to save the current values. */
63
+ requestSave: () => void;
64
+ /** Request the host to publish the current version. */
65
+ requestPublish: () => void;
66
+ /** Report iframe height for auto-resize. */
67
+ resize: (height: number) => void;
68
+ /** Report dirty state (unsaved changes). */
69
+ setDirty: (isDirty: boolean) => void;
70
+ /** Request the host to navigate to an admin path. */
71
+ requestNavigate: (path: string) => void;
72
+ /** Request the host to execute a platform operation. Returns the result.
73
+ * `mode` controls whether the platform waits for the operation to complete
74
+ * ('sync') or returns immediately after queueing ('async'). Defaults to 'sync'. */
75
+ requestOperation: (operationKey: string, input?: Record<string, unknown>, options?: {
76
+ mode?: 'sync' | 'async';
77
+ }) => Promise<{
78
+ success: boolean;
79
+ result?: unknown;
80
+ error?: {
81
+ code: string;
82
+ message: string;
83
+ };
84
+ }>;
85
+ /** Editor mode ('editor' or 'sidebar'). */
86
+ configMode: 'editor' | 'sidebar';
87
+ /** Currently selected field keys (sidebar mode). */
88
+ selectedFieldKeys: string[];
89
+ /** Available fields for selection (sidebar mode). */
90
+ selectableFields: Array<{
91
+ key: string;
92
+ label: string;
93
+ type: string;
94
+ }>;
95
+ /** Locale context (sidebar mode). */
96
+ localeContext: LocaleContext | null;
97
+ }
98
+ /** Options for creating a PlatformClient. */
99
+ export interface PlatformClientOptions {
100
+ /** GraphQL endpoint URL. */
101
+ apiUrl: string;
102
+ /** API key for authentication. */
103
+ apiKey: string;
104
+ }
105
+ /** A minimal GraphQL error shape. */
106
+ export interface GraphQLError {
107
+ message: string;
108
+ locations?: Array<{
109
+ line: number;
110
+ column: number;
111
+ }>;
112
+ path?: Array<string | number>;
113
+ extensions?: Record<string, unknown>;
114
+ }
115
+ /** Response shape from a GraphQL query/mutation. */
116
+ export interface GraphQLResponse<T = Record<string, unknown>> {
117
+ data: T | null;
118
+ errors?: GraphQLError[];
119
+ }
120
+ /** Fetch-based GraphQL client for platform API calls. */
121
+ export interface PlatformClient {
122
+ /** Execute a GraphQL query or mutation. */
123
+ query<T = Record<string, unknown>>(query: string, variables?: Record<string, unknown>): Promise<GraphQLResponse<T>>;
124
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Official Foir SDK — editor iframe client + server-side platform helpers",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -14,39 +14,47 @@
14
14
  "import": "./dist/index.js"
15
15
  },
16
16
  "./server": {
17
- "types": "./dist/server.d.ts",
17
+ "types": "./dist/server/index.d.ts",
18
18
  "import": "./dist/server.js"
19
19
  },
20
20
  "./client": {
21
- "types": "./dist/client.d.ts",
21
+ "types": "./dist/graphql/client.d.ts",
22
22
  "import": "./dist/client.js"
23
23
  },
24
+ "./data": {
25
+ "types": "./dist/data/index.d.ts",
26
+ "import": "./dist/data.js"
27
+ },
24
28
  "./media": {
25
- "types": "./dist/media.d.ts",
29
+ "types": "./dist/media/index.d.ts",
26
30
  "import": "./dist/media.js"
27
31
  },
28
32
  "./content": {
29
- "types": "./dist/content.d.ts",
33
+ "types": "./dist/content/index.d.ts",
30
34
  "import": "./dist/content.js"
31
35
  },
32
36
  "./auth": {
33
- "types": "./dist/auth.d.ts",
37
+ "types": "./dist/auth/index.d.ts",
34
38
  "import": "./dist/auth.js"
35
39
  },
40
+ "./auth/session": {
41
+ "types": "./dist/auth/session-client.d.ts",
42
+ "import": "./dist/auth-session.js"
43
+ },
36
44
  "./react": {
37
- "types": "./dist/react.d.ts",
45
+ "types": "./dist/react/index.d.ts",
38
46
  "import": "./dist/react.js"
39
47
  },
40
48
  "./next": {
41
- "types": "./dist/next.d.ts",
49
+ "types": "./dist/next/index.d.ts",
42
50
  "import": "./dist/next.js"
43
51
  },
44
52
  "./push": {
45
- "types": "./dist/push.d.ts",
53
+ "types": "./dist/push/index.d.ts",
46
54
  "import": "./dist/push.js"
47
55
  },
48
56
  "./realtime": {
49
- "types": "./dist/realtime.d.ts",
57
+ "types": "./dist/realtime/index.d.ts",
50
58
  "import": "./dist/realtime.js"
51
59
  },
52
60
  "./codegen": {
@@ -63,7 +71,8 @@
63
71
  }
64
72
  },
65
73
  "files": [
66
- "dist"
74
+ "dist",
75
+ "CHANGELOG.md"
67
76
  ],
68
77
  "keywords": [
69
78
  "foir",
@@ -136,8 +145,9 @@
136
145
  "node": ">=18.0.0"
137
146
  },
138
147
  "scripts": {
139
- "build": "tsup",
140
- "dev": "tsup --watch",
148
+ "build": "tsup && tsc -p tsconfig.build.json && node scripts/assert-export-types.mjs && node scripts/assert-public-exports.mjs",
149
+ "snapshot:exports": "node scripts/assert-public-exports.mjs --update",
150
+ "dev": "node scripts/dev-watch.mjs",
141
151
  "check-types": "tsc --noEmit",
142
152
  "test": "vitest run",
143
153
  "test:watch": "vitest watch",
package/dist/auth.d.ts DELETED
@@ -1,382 +0,0 @@
1
- import { TypedDocumentNode } from '@graphql-typed-document-node/core';
2
-
3
- /**
4
- * Foir GraphQL client — a single typed fetch wrapper for SSR/edge loaders.
5
- *
6
- * No GraphQL client (Apollo/urql) required: the Foir public API is a plain
7
- * `POST` with an `x-api-key` header, and the dominant consumers (Hydrogen on
8
- * Oxygen, React Router loaders, TanStack Start on CF Workers) fetch in
9
- * server-only loaders and render — so Apollo's `InMemoryCache`/`useQuery`
10
- * reactivity is dead weight, and a shared cache on a stateless multi-tenant
11
- * edge worker is a cross-tenant hazard. This helper carries only the
12
- * Foir-specific transport concerns (auth header, preview schema channel,
13
- * error shape). Consumers on a GraphQL client (Apollo / urql / react-query /
14
- * graphql-request) use the generated `TypedDocumentNode`s directly with their
15
- * own client and replicate the two Foir-specific headers below — the
16
- * `x-api-key` and, in preview, `X-Foir-Schema-Channel: draft`.
17
- *
18
- * Deduplicates the hand-copied `lib/foir/client.ts` that shipped identically
19
- * across the Foir storefronts.
20
- */
21
-
22
- interface FoirEnv {
23
- PLATFORM_API_URL?: string;
24
- PLATFORM_API_KEY?: string;
25
- PLATFORM_PREVIEW_API_KEY?: string;
26
- FOIR_PREVIEW?: string;
27
- }
28
-
29
- /**
30
- * Foir customer-auth GraphQL operations + result types.
31
- *
32
- * These are PLATFORM-STABLE primitives — `customerLogin`/`customerRegister`/
33
- * `customerRefreshToken`/OTP/provider login are fixed-shape operations served
34
- * by api-public for every project (gated per-project by the password/OTP/
35
- * provider flags, but identical in shape when present). Unlike per-project
36
- * model types, they don't vary by configured models, so the SDK owns the
37
- * documents directly rather than relying on the consumer's codegen.
38
- *
39
- * The project-specific customer PROFILE (e.g. `myProfile.isAdmin`) is NOT here
40
- * — that shape varies per project, so it stays consumer-injected (see
41
- * `requireAuth`'s `resolveUser`).
42
- */
43
-
44
- /** The common auth result every login/register/refresh mutation returns. */
45
- interface CustomerAuthResult {
46
- token: string;
47
- refreshToken: string;
48
- customerId: string;
49
- email: string;
50
- }
51
- /** Result of initiating a provider login — a redirect URL or an inline token. */
52
- interface ProviderLoginResult {
53
- /** OAuth/OIDC authorization URL to redirect the user to (redirect flows). */
54
- redirectUrl: string | null;
55
- /** Inline access token (non-redirect provider flows). */
56
- token: string | null;
57
- }
58
- /** A configured auth provider, as exposed to unauthenticated callers. */
59
- interface AuthProviderPublic {
60
- key: string;
61
- name: string;
62
- /** OAUTH2 | TOKEN_SSO | SAML | OIDC | EXTERNAL */
63
- type: string;
64
- isDefault: boolean | null;
65
- }
66
- interface ProviderLoginInput {
67
- providerKey: string;
68
- email?: string;
69
- returnTo?: string;
70
- /** Override the provider's default redirect_uri (must be allow-listed). */
71
- redirectUri?: string;
72
- }
73
- interface ProviderCallbackInput {
74
- providerKey: string;
75
- code?: string;
76
- state?: string;
77
- email?: string;
78
- idToken?: string;
79
- }
80
- interface ProviderOTPVerifyInput {
81
- providerKey: string;
82
- email: string;
83
- otp: string;
84
- }
85
- declare const CustomerLoginDocument: TypedDocumentNode<{
86
- customerLogin: CustomerAuthResult;
87
- }, {
88
- email: string;
89
- password: string;
90
- }>;
91
- declare const CustomerRegisterDocument: TypedDocumentNode<{
92
- customerRegister: CustomerAuthResult;
93
- }, {
94
- email: string;
95
- password: string;
96
- }>;
97
- declare const CustomerRefreshTokenDocument: TypedDocumentNode<{
98
- customerRefreshToken: CustomerAuthResult;
99
- }, {
100
- refreshToken: string;
101
- }>;
102
- declare const CustomerRequestOTPDocument: TypedDocumentNode<{
103
- customerRequestOTP: boolean;
104
- }, {
105
- email: string;
106
- }>;
107
- declare const CustomerLoginOTPDocument: TypedDocumentNode<{
108
- customerLoginOTP: CustomerAuthResult;
109
- }, {
110
- email: string;
111
- otp: string;
112
- }>;
113
- declare const CustomerLoginWithProviderDocument: TypedDocumentNode<{
114
- customerLoginWithProvider: ProviderLoginResult;
115
- }, {
116
- input: ProviderLoginInput;
117
- }>;
118
- declare const CustomerProviderCallbackDocument: TypedDocumentNode<{
119
- customerProviderCallback: CustomerAuthResult;
120
- }, {
121
- input: ProviderCallbackInput;
122
- }>;
123
- declare const CustomerProviderVerifyOTPDocument: TypedDocumentNode<{
124
- customerProviderVerifyOTP: CustomerAuthResult;
125
- }, {
126
- input: ProviderOTPVerifyInput;
127
- }>;
128
- declare const AuthProvidersDocument: TypedDocumentNode<{
129
- authProviders: AuthProviderPublic[];
130
- }, Record<string, never>>;
131
-
132
- /**
133
- * Foir customer auth — framework-agnostic flows, token lifecycle, and guards.
134
- *
135
- * Session I/O is injected via {@link SessionAdapter} (no Hydrogen/React-Router
136
- * coupling). Pre-login flows authenticate with the project `x-api-key`;
137
- * post-login reads add the customer bearer. The project-specific profile (e.g.
138
- * `isAdmin`) is resolved by a consumer-supplied `resolveUser` — the SDK never
139
- * hardcodes the per-project customer profile shape.
140
- */
141
-
142
- /** An access token + its rotating refresh token. */
143
- interface FoirAuthTokens {
144
- token: string;
145
- refreshToken: string;
146
- }
147
- /** The customer identity carried by an auth result. */
148
- interface FoirCustomer {
149
- id: string;
150
- email: string;
151
- }
152
- /** The resolved customer for a guarded surface. */
153
- interface FoirUser {
154
- id: string;
155
- email: string;
156
- isAdmin: boolean;
157
- }
158
- /**
159
- * Injected session store. Methods may be sync or async so consumers can plug
160
- * in a cookie session (Hydrogen/Remix), a KV store, etc.
161
- */
162
- interface SessionAdapter {
163
- get(key: string): string | null | undefined | Promise<string | null | undefined>;
164
- set(key: string, value: string): void | Promise<void>;
165
- unset(key: string): void | Promise<void>;
166
- }
167
- /** Login with email + password. Requires the project to have passwords enabled. */
168
- declare function customerLogin(env: FoirEnv, vars: {
169
- email: string;
170
- password: string;
171
- }): Promise<CustomerAuthResult>;
172
- /** Register a new customer with email + password. */
173
- declare function customerRegister(env: FoirEnv, vars: {
174
- email: string;
175
- password: string;
176
- }): Promise<CustomerAuthResult>;
177
- /** Request a one-time password be emailed to the customer. */
178
- declare function customerRequestOTP(env: FoirEnv, email: string): Promise<boolean>;
179
- /** Complete an OTP login. */
180
- declare function customerLoginOTP(env: FoirEnv, vars: {
181
- email: string;
182
- otp: string;
183
- }): Promise<CustomerAuthResult>;
184
- /** Rotate an access token using a refresh token. */
185
- declare function customerRefreshToken(env: FoirEnv, refreshToken: string): Promise<CustomerAuthResult>;
186
- /**
187
- * Initiate provider (OAuth/OIDC) login — returns a `redirectUrl` to send the
188
- * user to, or an inline `token` for non-redirect providers.
189
- */
190
- declare function customerLoginWithProvider(env: FoirEnv, input: ProviderLoginInput): Promise<ProviderLoginResult>;
191
- /** Complete a provider login from the OAuth callback (`code`/`state`). */
192
- declare function customerProviderCallback(env: FoirEnv, input: ProviderCallbackInput): Promise<CustomerAuthResult>;
193
- /** Verify an OTP issued by an external provider. */
194
- declare function customerProviderVerifyOTP(env: FoirEnv, input: ProviderOTPVerifyInput): Promise<CustomerAuthResult>;
195
- /** List the project's configured auth providers (for a login UI). */
196
- declare function getAuthProviders(env: FoirEnv): Promise<AuthProviderPublic[]>;
197
- /** Persist an auth result to the session; returns the customer identity. */
198
- declare function saveSession(adapter: SessionAdapter, result: CustomerAuthResult): Promise<FoirCustomer>;
199
- /** Clear all Foir auth keys from the session. */
200
- declare function clearSession(adapter: SessionAdapter): Promise<void>;
201
- /** Read the stored access token (null when signed out). */
202
- declare function getSessionToken(adapter: SessionAdapter): Promise<string | null>;
203
- /** Read the stored customer identity (null when signed out). */
204
- declare function getSessionCustomer(adapter: SessionAdapter): Promise<FoirCustomer | null>;
205
- /** Login with email + password and persist the session. */
206
- declare function loginWithPassword(adapter: SessionAdapter, env: FoirEnv, vars: {
207
- email: string;
208
- password: string;
209
- }): Promise<FoirCustomer>;
210
- /** Register with email + password and persist the session. */
211
- declare function registerWithPassword(adapter: SessionAdapter, env: FoirEnv, vars: {
212
- email: string;
213
- password: string;
214
- }): Promise<FoirCustomer>;
215
- /** Complete an OTP login and persist the session. */
216
- declare function loginWithOTP(adapter: SessionAdapter, env: FoirEnv, vars: {
217
- email: string;
218
- otp: string;
219
- }): Promise<FoirCustomer>;
220
- /**
221
- * Refresh the access token from the stored refresh token, persisting the
222
- * rotated pair. Returns the new tokens, or null when there's no refresh token
223
- * or the refresh fails (the session is cleared in that case).
224
- */
225
- declare function refreshSession(adapter: SessionAdapter, env: FoirEnv): Promise<FoirAuthTokens | null>;
226
- /**
227
- * Run a bearer-authed GraphQL operation with refresh-with-retry: uses the
228
- * stored access token, and on a 401-shaped failure (rejected/expired bearer)
229
- * attempts a single token refresh and retries once. Any other error — network
230
- * blips, server errors, validation — propagates without a retry so transient
231
- * mutation failures can't be duplicated. Throws the original error if there's
232
- * no refresh path.
233
- */
234
- declare function authedRequest<TData, TVariables>(adapter: SessionAdapter, env: FoirEnv, document: TypedDocumentNode<TData, TVariables>, variables?: TVariables): Promise<TData>;
235
- interface RequireAuthOptions {
236
- /**
237
- * Resolve the full {@link FoirUser} (including the project-specific
238
- * `isAdmin`) from the session identity. Typically runs the project's
239
- * `myProfile` query via {@link authedRequest}. Defaults to `isAdmin: false`.
240
- */
241
- resolveUser?: (ctx: {
242
- token: string;
243
- customer: FoirCustomer;
244
- }) => FoirUser | Promise<FoirUser>;
245
- /**
246
- * Called when there's no valid session — throw your framework's redirect
247
- * (e.g. `throw redirect(loginUrl)`). Must not return.
248
- */
249
- onUnauthenticated: () => never | Promise<never>;
250
- }
251
- /**
252
- * Require a signed-in customer. Resolves the {@link FoirUser} via
253
- * `resolveUser` (or a minimal identity), or invokes `onUnauthenticated` when
254
- * there's no session.
255
- */
256
- declare function requireAuth(adapter: SessionAdapter, options: RequireAuthOptions): Promise<FoirUser>;
257
- interface RequireAdminOptions extends RequireAuthOptions {
258
- /** Called when the user is authenticated but not an admin. Must not return. */
259
- onForbidden: () => never | Promise<never>;
260
- }
261
- /** Require a signed-in customer who is also an admin. */
262
- declare function requireAdmin(adapter: SessionAdapter, options: RequireAdminOptions): Promise<FoirUser>;
263
-
264
- /**
265
- * Hosted-login bridge — for storefronts that delegate sign-in to a Foir
266
- * white-label front door (the `customerLoginWithFoir` flow) instead of
267
- * rendering their own login UI.
268
- *
269
- * The front door sets a first-party session cookie on the registrable domain;
270
- * because that domain is shared, the cookie reaches the storefront, which
271
- * exchanges it server-side for a short-lived customer bearer + identity. The
272
- * cookie is the durable credential (re-exchanged on bearer expiry) — there's
273
- * no separate refresh token in this flow.
274
- *
275
- * Every URL/cookie/path is CONFIG, not a constant, so a domain restructure
276
- * is a config change, not a code change.
277
- */
278
- interface HostedLoginConfig {
279
- /** White-label front-door origin, e.g. `https://login.example.com`. */
280
- loginOrigin: string;
281
- /** Session cookie name set on the registrable domain. Default `foir_customer_session`. */
282
- cookieName?: string;
283
- /** Session-exchange path on the front door. Default `/session/token`. */
284
- sessionPath?: string;
285
- }
286
- /**
287
- * Build the redirect URL to the hosted front door, forwarding a best-effort
288
- * absolute `returnTo` (honoured if the front door supports it).
289
- */
290
- declare function hostedLoginUrl(config: HostedLoginConfig, returnTo?: string): string;
291
- /** The identity + bearer returned by a successful hosted-session exchange. */
292
- interface HostedSession {
293
- token: string;
294
- customerId: string;
295
- email: string;
296
- }
297
- /**
298
- * Exchange the hosted-login cookie on an incoming request for a customer
299
- * bearer + identity (server-to-server: forwards the cookie to the front
300
- * door's session endpoint with the CSRF header). Returns null when there's no
301
- * cookie or it's expired/revoked.
302
- */
303
- declare function fetchHostedSession(request: Request, config: HostedLoginConfig): Promise<HostedSession | null>;
304
-
305
- /**
306
- * Foir-hosted first-party sessions, from the browser — the model for a
307
- * backend-less SPA (Vite, CRA, a static site) that wants no tokens in JS.
308
- *
309
- * The sibling `fetchHostedSession` in ./hosted does the same exchange from a
310
- * SERVER: it reads the cookie off an incoming request and forwards it by hand.
311
- * A browser cannot set a `Cookie` header, so that function is unusable from an
312
- * SPA by construction. This one lets the browser send the cookie the only way it
313
- * can — `credentials: 'include'` — and keeps the resulting bearer in memory.
314
- *
315
- * How the pieces divide:
316
- *
317
- * cookie — long-lived (30d), HttpOnly, first-party on your registrable
318
- * domain, set by Foir when hosted login finalizes. JS cannot read
319
- * it, so XSS cannot exfiltrate it.
320
- * bearer — 15 minutes, held in a closure here. Never written to
321
- * localStorage/sessionStorage/IndexedDB, never in a cookie of ours.
322
- *
323
- * There is no refresh token in this flow. Re-exchanging the cookie IS the
324
- * refresh, which is why nothing here needs rotation or reuse-detection: the
325
- * durable credential is an opaque server-side session handle, revocable at any
326
- * time from the admin panel or `signOut()`.
327
- *
328
- * REQUIRES a verified custom login domain that shares a registrable domain with
329
- * your app (app.acme.com + login.acme.com). That is what makes the cookie
330
- * first-party and lets `SameSite=Lax` send it. The same endpoints exist on
331
- * api.foir.dev but a browser cannot reach them there — the cookie would be
332
- * cross-site, and that CORS surface deliberately refuses credentials.
333
- */
334
- interface FoirSessionConfig {
335
- /**
336
- * Your verified custom login domain, e.g. `https://login.acme.com`. Must share
337
- * a registrable domain with the app calling it.
338
- */
339
- loginOrigin: string;
340
- /** Session-exchange path. Default `/session/token`. */
341
- sessionPath?: string;
342
- /** Logout path. Default `/session/logout`. */
343
- logoutPath?: string;
344
- /** Seconds before expiry to re-exchange. Default 60. */
345
- refreshSkewSeconds?: number;
346
- /**
347
- * Called when the session goes away — the cookie expired, was revoked, or
348
- * `signOut()` ran. Use it to redirect to `signInUrl()`.
349
- */
350
- onSignOut?: () => void;
351
- }
352
- /** The identity behind the current session. */
353
- interface FoirSessionUser {
354
- customerId: string;
355
- email: string;
356
- }
357
- interface FoirSessionClient {
358
- /**
359
- * The current access token, exchanging or re-exchanging the cookie as needed.
360
- * Null when there is no session. Concurrent callers share one in-flight
361
- * exchange. Call this before each API request — do not cache the result.
362
- */
363
- getToken(): Promise<string | null>;
364
- /** The signed-in customer, or null. Exchanges if the session isn't loaded yet. */
365
- getUser(): Promise<FoirSessionUser | null>;
366
- /** Revoke the session server-side and clear the cookie. Idempotent. */
367
- signOut(): Promise<void>;
368
- /** Where to send the browser to sign in. */
369
- signInUrl(returnTo?: string): string;
370
- /** Observe session changes. Returns an unsubscribe function. */
371
- subscribe(listener: (user: FoirSessionUser | null) => void): () => void;
372
- /** Cancel the refresh timer. Call on unmount. */
373
- destroy(): void;
374
- }
375
- /**
376
- * Create a session client. Safe to call during SSR — it does nothing until a
377
- * method is invoked, and `getToken()` on the server returns null rather than
378
- * throwing (there is no cookie jar to draw on).
379
- */
380
- declare function createFoirSessionClient(config: FoirSessionConfig): FoirSessionClient;
381
-
382
- export { type AuthProviderPublic, AuthProvidersDocument, type CustomerAuthResult, CustomerLoginDocument, CustomerLoginOTPDocument, CustomerLoginWithProviderDocument, CustomerProviderCallbackDocument, CustomerProviderVerifyOTPDocument, CustomerRefreshTokenDocument, CustomerRegisterDocument, CustomerRequestOTPDocument, type FoirAuthTokens, type FoirCustomer, type FoirSessionClient, type FoirSessionConfig, type FoirSessionUser, type FoirUser, type HostedLoginConfig, type HostedSession, type ProviderCallbackInput, type ProviderLoginInput, type ProviderLoginResult, type ProviderOTPVerifyInput, type RequireAdminOptions, type RequireAuthOptions, type SessionAdapter, authedRequest, clearSession, createFoirSessionClient, customerLogin, customerLoginOTP, customerLoginWithProvider, customerProviderCallback, customerProviderVerifyOTP, customerRefreshToken, customerRegister, customerRequestOTP, fetchHostedSession, getAuthProviders, getSessionCustomer, getSessionToken, hostedLoginUrl, loginWithOTP, loginWithPassword, refreshSession, registerWithPassword, requireAdmin, requireAuth, saveSession };