@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/dist/auth.js CHANGED
@@ -1,8 +1,16 @@
1
1
  // src/graphql/foir-request.ts
2
+ import { print as print2 } from "graphql";
3
+
4
+ // src/graphql/cdn-route.ts
2
5
  import { print } from "graphql";
6
+
7
+ // src/transport/foir-http.ts
3
8
  function isFoirPreview(env) {
4
9
  return env.FOIR_PREVIEW === "true";
5
10
  }
11
+ function resolveFoirApiKey(env) {
12
+ return isFoirPreview(env) ? env.PLATFORM_PREVIEW_API_KEY ?? env.PLATFORM_API_KEY : env.PLATFORM_API_KEY;
13
+ }
6
14
  var FoirRequestError = class extends Error {
7
15
  /** HTTP status code, when the request failed at the HTTP layer. */
8
16
  status;
@@ -16,41 +24,90 @@ var FoirRequestError = class extends Error {
16
24
  this.status = options?.status;
17
25
  this.graphQLErrors = options?.graphQLErrors;
18
26
  }
27
+ /**
28
+ * The code of the first error, if the response carried one.
29
+ *
30
+ * A getter rather than a stored field so it stays correct for an error
31
+ * constructed anywhere, including by a caller in a test.
32
+ */
33
+ get code() {
34
+ return this.graphQLErrors?.[0]?.extensions?.code;
35
+ }
36
+ /** The correlation id, if the response carried one. */
37
+ get requestId() {
38
+ return this.graphQLErrors?.[0]?.extensions?.requestId;
39
+ }
40
+ /**
41
+ * Whether retrying this request could succeed.
42
+ *
43
+ * The 429 is the one that has to be in here and is easy to leave out: it sits
44
+ * among the 4xx statuses that are otherwise permanent, so a policy written as
45
+ * "retry 5xx, never retry 4xx" excludes exactly the failure that clears on its
46
+ * own. A storefront wrote that policy, its key saturated, and every read
47
+ * failed on the first attempt for an hour.
48
+ */
49
+ get retryable() {
50
+ if (this.status === void 0) return true;
51
+ if (this.status === 429 || this.code === "RATE_LIMITED") return true;
52
+ if (this.status >= 500) return true;
53
+ return this.code === "UNAVAILABLE" || this.code === "TIMEOUT";
54
+ }
55
+ /**
56
+ * How long to wait before retrying, in milliseconds, when the server said.
57
+ * Undefined when it did not, which means the caller picks its own backoff.
58
+ */
59
+ get retryAfterMs() {
60
+ const seconds = this.graphQLErrors?.[0]?.extensions?.retryAfter;
61
+ return typeof seconds === "number" && seconds > 0 ? seconds * 1e3 : void 0;
62
+ }
19
63
  };
20
- async function foirRequest(env, document, variables, options) {
21
- const endpoint = env.PLATFORM_API_URL;
22
- const apiKey = isFoirPreview(env) ? env.PLATFORM_PREVIEW_API_KEY ?? env.PLATFORM_API_KEY : env.PLATFORM_API_KEY;
23
- if (!endpoint || !apiKey) {
24
- throw new FoirRequestError(
25
- "[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
26
- );
64
+ function readFoirRateLimit(headers) {
65
+ const raw = {
66
+ limit: headers.get("X-RateLimit-Limit"),
67
+ remaining: headers.get("X-RateLimit-Remaining"),
68
+ resetAt: headers.get("X-RateLimit-Reset")
69
+ };
70
+ if (raw.limit === null || raw.remaining === null || raw.resetAt === null) {
71
+ return void 0;
27
72
  }
73
+ const limit = Number(raw.limit);
74
+ const remaining = Number(raw.remaining);
75
+ const resetAt = Number(raw.resetAt);
76
+ if (!Number.isFinite(limit) || !Number.isFinite(remaining) || !Number.isFinite(resetAt)) {
77
+ return void 0;
78
+ }
79
+ return { limit, remaining, resetAt };
80
+ }
81
+ function readFoirRetryAfterMs(headers) {
82
+ const seconds = Number(headers.get("Retry-After"));
83
+ return Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : void 0;
84
+ }
85
+ function buildFoirHeaders(input) {
28
86
  const headers = {
29
87
  "Content-Type": "application/json",
30
- "x-api-key": apiKey
88
+ "x-api-key": input.apiKey
31
89
  };
32
- if (isFoirPreview(env)) {
90
+ if (input.draftChannel) {
33
91
  headers["X-Foir-Schema-Channel"] = "draft";
34
92
  }
35
- if (options?.bearerToken) {
36
- headers["Authorization"] = `Bearer ${options.bearerToken}`;
93
+ if (input.bearerToken) {
94
+ headers["Authorization"] = `Bearer ${input.bearerToken}`;
37
95
  }
38
- let res;
39
- try {
40
- res = await fetch(endpoint, {
41
- method: "POST",
42
- headers,
43
- body: JSON.stringify({ query: print(document), variables })
44
- });
45
- } catch (err) {
46
- throw new FoirRequestError("[foir] network error", { cause: err });
96
+ if (input.extra) {
97
+ for (const [key, value] of Object.entries(input.extra)) {
98
+ headers[key] = value;
99
+ }
47
100
  }
48
- if (!res.ok) {
49
- throw new FoirRequestError(`[foir] HTTP ${res.status} ${res.statusText}`, {
50
- status: res.status
51
- });
101
+ return headers;
102
+ }
103
+ function foirApiEndpoint(apiUrl, path) {
104
+ const base = apiUrl.trim().replace(/\/+$/, "").replace(/\/graphql$/i, "");
105
+ if (!base) {
106
+ throw new FoirRequestError("[foir] apiUrl is empty \u2014 pass the Foir API origin");
52
107
  }
53
- const json = await res.json();
108
+ return base + path;
109
+ }
110
+ function readFoirEnvelope(json) {
54
111
  if (json.errors?.length) {
55
112
  const msg = json.errors.map((e) => e.message).join("; ");
56
113
  if (json.data) {
@@ -67,54 +124,811 @@ async function foirRequest(env, document, variables, options) {
67
124
  return json.data;
68
125
  }
69
126
 
70
- // src/auth/operations.ts
71
- import { parse } from "graphql";
72
- var AUTH_RESULT_FIELDS = "token refreshToken customerId email";
73
- var CustomerLoginDocument = parse(
74
- `mutation CustomerLogin($email: String!, $password: String!) {
75
- customerLogin(email: $email, password: $password) { ${AUTH_RESULT_FIELDS} }
76
- }`
77
- );
78
- var CustomerRegisterDocument = parse(
79
- `mutation CustomerRegister($email: String!, $password: String!) {
80
- customerRegister(email: $email, password: $password) { ${AUTH_RESULT_FIELDS} }
81
- }`
82
- );
83
- var CustomerRefreshTokenDocument = parse(
84
- `mutation CustomerRefreshToken($refreshToken: String!) {
85
- customerRefreshToken(refreshToken: $refreshToken) { ${AUTH_RESULT_FIELDS} }
86
- }`
87
- );
88
- var CustomerRequestOTPDocument = parse(
89
- `mutation CustomerRequestOTP($email: String!) {
90
- customerRequestOTP(email: $email)
91
- }`
92
- );
93
- var CustomerLoginOTPDocument = parse(
94
- `mutation CustomerLoginOTP($email: String!, $otp: String!) {
95
- customerLoginOTP(email: $email, otp: $otp) { ${AUTH_RESULT_FIELDS} }
96
- }`
97
- );
98
- var CustomerLoginWithProviderDocument = parse(
99
- `mutation CustomerLoginWithProvider($input: ProviderLoginInput!) {
100
- customerLoginWithProvider(input: $input) { redirectUrl token }
101
- }`
102
- );
103
- var CustomerProviderCallbackDocument = parse(
104
- `mutation CustomerProviderCallback($input: ProviderCallbackInput!) {
105
- customerProviderCallback(input: $input) { ${AUTH_RESULT_FIELDS} }
106
- }`
107
- );
108
- var CustomerProviderVerifyOTPDocument = parse(
109
- `mutation CustomerProviderVerifyOTP($input: ProviderOTPVerifyInput!) {
110
- customerProviderVerifyOTP(input: $input) { ${AUTH_RESULT_FIELDS} }
111
- }`
112
- );
113
- var AuthProvidersDocument = parse(
114
- `query AuthProviders {
115
- authProviders { key name type isDefault }
116
- }`
117
- );
127
+ // src/transport/persisted-query.ts
128
+ var FOIR_CDN_GRAPHQL_PATH = "/cdn/graphql";
129
+ function persistedQueryExtension(hash) {
130
+ return { persistedQuery: { version: 1, sha256Hash: hash } };
131
+ }
132
+ function buildCdnUrl(endpoint, apiKey, hash, variables) {
133
+ const url = new URL(endpoint);
134
+ url.pathname = url.pathname.replace(/\/graphql\/?$/, "") + FOIR_CDN_GRAPHQL_PATH;
135
+ url.searchParams.set("key", apiKey);
136
+ url.searchParams.set("extensions", JSON.stringify(persistedQueryExtension(hash)));
137
+ if (variables && Object.keys(variables).length > 0) {
138
+ url.searchParams.set("variables", JSON.stringify(variables));
139
+ }
140
+ return url.toString();
141
+ }
142
+ async function documentHash(documentText) {
143
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(documentText));
144
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
145
+ }
146
+ var PERSISTED_QUERY_NOT_FOUND = "PERSISTED_QUERY_NOT_FOUND";
147
+ var NOT_SHAREABLE = "NOT_SHAREABLE";
148
+ async function hasFoirErrorCode(res, ...codes) {
149
+ try {
150
+ const body = await res.clone().json();
151
+ return body.errors?.some((e) => e.extensions?.code !== void 0 && codes.includes(e.extensions.code)) ?? false;
152
+ } catch {
153
+ return false;
154
+ }
155
+ }
156
+
157
+ // src/graphql/cdn-route.ts
158
+ function isCdnEligible(env, documentText, bearerToken) {
159
+ if (bearerToken) return false;
160
+ if (isFoirPreview(env)) return false;
161
+ const apiKey = resolveFoirApiKey(env);
162
+ if (!apiKey?.startsWith("pk_")) return false;
163
+ return !/\bmutation\b/.test(documentText);
164
+ }
165
+
166
+ // src/graphql/foir-request.ts
167
+ async function foirRequest(env, document, variables, options) {
168
+ const endpoint = env.PLATFORM_API_URL;
169
+ const apiKey = resolveFoirApiKey(env);
170
+ if (!endpoint || !apiKey) {
171
+ throw new FoirRequestError(
172
+ "[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
173
+ );
174
+ }
175
+ const headers = buildFoirHeaders({
176
+ apiKey,
177
+ draftChannel: isFoirPreview(env),
178
+ bearerToken: options?.bearerToken
179
+ });
180
+ const documentText = print2(document);
181
+ let res;
182
+ try {
183
+ if (isCdnEligible(env, documentText, options?.bearerToken)) {
184
+ const hash = await documentHash(documentText);
185
+ let shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
186
+ method: "GET",
187
+ headers
188
+ });
189
+ if (await isPersistedQueryMiss(shared)) {
190
+ shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
191
+ method: "POST",
192
+ headers,
193
+ body: JSON.stringify({
194
+ query: documentText,
195
+ variables,
196
+ extensions: persistedQueryExtension(hash)
197
+ })
198
+ });
199
+ }
200
+ if (await isSharedRefusal(shared)) {
201
+ res = await fetch(endpoint, {
202
+ method: "POST",
203
+ headers,
204
+ body: JSON.stringify({ query: documentText, variables })
205
+ });
206
+ } else {
207
+ res = shared;
208
+ }
209
+ } else {
210
+ res = await fetch(endpoint, {
211
+ method: "POST",
212
+ headers,
213
+ body: JSON.stringify({ query: documentText, variables })
214
+ });
215
+ }
216
+ } catch (err) {
217
+ throw new FoirRequestError("[foir] network error", { cause: err });
218
+ }
219
+ options?.onRateLimit?.(readFoirRateLimit(res.headers));
220
+ if (!res.ok) {
221
+ const errors = await readErrorEnvelope(res);
222
+ const first = errors?.[0]?.message;
223
+ throw new FoirRequestError(
224
+ first ? `[foir] HTTP ${res.status} ${res.statusText}: ${first}` : `[foir] HTTP ${res.status} ${res.statusText}`,
225
+ { status: res.status, graphQLErrors: errors }
226
+ );
227
+ }
228
+ const json = await res.json();
229
+ return readFoirEnvelope(json);
230
+ }
231
+ async function readErrorEnvelope(res) {
232
+ let errors;
233
+ try {
234
+ const body = await res.json();
235
+ errors = body.errors;
236
+ } catch {
237
+ errors = void 0;
238
+ }
239
+ const headerRetryMs = readFoirRetryAfterMs(res.headers);
240
+ if (headerRetryMs === void 0) return errors;
241
+ const seconds = Math.round(headerRetryMs / 1e3);
242
+ if (!errors?.length) {
243
+ return [
244
+ {
245
+ message: res.statusText || `HTTP ${res.status}`,
246
+ extensions: { code: "RATE_LIMITED", retryAfter: seconds }
247
+ }
248
+ ];
249
+ }
250
+ if (errors[0].extensions?.retryAfter === void 0) {
251
+ errors[0] = {
252
+ ...errors[0],
253
+ extensions: { ...errors[0].extensions, retryAfter: seconds }
254
+ };
255
+ }
256
+ return errors;
257
+ }
258
+ async function isSharedRefusal(res) {
259
+ return hasFoirErrorCode(res, NOT_SHAREABLE);
260
+ }
261
+ async function isPersistedQueryMiss(res) {
262
+ return hasFoirErrorCode(res, PERSISTED_QUERY_NOT_FOUND);
263
+ }
264
+
265
+ // src/data/document.ts
266
+ function buildPathTrie(paths) {
267
+ const root = { self: false, children: /* @__PURE__ */ new Map() };
268
+ for (const path of paths) {
269
+ let node = root;
270
+ for (const segment of path.split(".")) {
271
+ if (!segment) continue;
272
+ let next = node.children.get(segment);
273
+ if (!next) {
274
+ next = { self: false, children: /* @__PURE__ */ new Map() };
275
+ node.children.set(segment, next);
276
+ }
277
+ node = next;
278
+ }
279
+ node.self = true;
280
+ }
281
+ return root;
282
+ }
283
+ function childFor(node, field) {
284
+ return node.children.get(field.key) ?? (field.alias ? node.children.get(field.alias) : void 0);
285
+ }
286
+ function segmentFor(node, field) {
287
+ return node.children.has(field.key) ? field.key : field.alias ?? field.key;
288
+ }
289
+ var PROTECTED_ROOT_FIELD = "_id";
290
+ var MAX_QUERY_COMPLEXITY = 1e4;
291
+ var MAX_EMITTED_FIELDS = 4 * MAX_QUERY_COMPLEXITY;
292
+ var newBudget = () => ({ fields: 0, emitted: 0, stopped: false });
293
+ function charge(ctx, n = 1) {
294
+ ctx.budget.fields += n;
295
+ ctx.budget.emitted += n;
296
+ }
297
+ function mayExpand(ctx, depth) {
298
+ if (depth <= 1) return false;
299
+ if (ctx.budget.emitted > MAX_EMITTED_FIELDS) {
300
+ ctx.budget.stopped = true;
301
+ return false;
302
+ }
303
+ return true;
304
+ }
305
+ function printFields(fields, ctx, omit, pick, isRoot, path, depth) {
306
+ const parts = [];
307
+ for (const field of fields) {
308
+ if (field.relation) {
309
+ const named = pick ? childFor(pick, field) : void 0;
310
+ if (named) {
311
+ const segment = segmentFor(pick, field);
312
+ throw new FoirRequestError(
313
+ `[foir] pick "${path}${segment}": "${segment}" is a relation, and relations are not part of the default document \u2014 no pick can select one. ` + (path ? (
314
+ // Nested: there is no `include` form that reaches here at all.
315
+ // `include` addresses the ROOT model's own relations, and (nested)
316
+ // a related model's — never one reached through a composite or a
317
+ // union member. Saying "use include" would send the caller to a
318
+ // call that refuses them a second time.
319
+ `A relation at this position cannot be reached by \`include\` either \u2014 that addresses the model's own relations, not one inside a composite or a union member. Read it through the field that owns it, or drop "${path}${segment}" from the pick`
320
+ ) : `Use include: { ${segment}: true } instead`)
321
+ );
322
+ }
323
+ continue;
324
+ }
325
+ const protectedField = isRoot && field.key === PROTECTED_ROOT_FIELD;
326
+ const omitChild = omit ? childFor(omit, field) : void 0;
327
+ if (omitChild?.self && !protectedField) continue;
328
+ let pickChild;
329
+ if (pick) {
330
+ pickChild = childFor(pick, field);
331
+ if (!pickChild && !protectedField) continue;
332
+ if (pickChild?.self) pickChild = void 0;
333
+ }
334
+ const head = field.alias ? `${field.alias}: ${field.key}` : field.key;
335
+ const childPath = `${path}${pick && childFor(pick, field) ? segmentFor(pick, field) : field.key}.`;
336
+ if (field.truncated) {
337
+ if (field.union && mayExpand(ctx, depth)) {
338
+ const body = printUnion(field.union, ctx, omitChild, pickChild, childPath, depth - 1);
339
+ if (body) {
340
+ charge(ctx);
341
+ parts.push(`${head} ${body}`);
342
+ continue;
343
+ }
344
+ }
345
+ charge(ctx, 2);
346
+ parts.push(`${head} { __typename }`);
347
+ continue;
348
+ }
349
+ if (field.model) {
350
+ const body = printModelTree(field.model, ctx, depth);
351
+ if (body) {
352
+ charge(ctx);
353
+ parts.push(`${head} { ${body} }`);
354
+ }
355
+ continue;
356
+ }
357
+ if (field.union) {
358
+ const body = printUnion(field.union, ctx, omitChild, pickChild, childPath, depth);
359
+ if (body) {
360
+ charge(ctx);
361
+ parts.push(`${head} ${body}`);
362
+ }
363
+ continue;
364
+ }
365
+ if (field.fields?.length) {
366
+ const body = printFields(field.fields, ctx, omitChild, pickChild, false, childPath, depth);
367
+ if (body) {
368
+ charge(ctx);
369
+ parts.push(`${head} { ${body} }`);
370
+ }
371
+ continue;
372
+ }
373
+ charge(ctx);
374
+ parts.push(head);
375
+ }
376
+ return parts.join(" ");
377
+ }
378
+ function printModelTree(modelKey, ctx, depth) {
379
+ const model = ctx.source.models[modelKey];
380
+ if (!model?.selection) {
381
+ throw new FoirRequestError(
382
+ `[foir] the result shape references the model "${modelKey}", which publishes no selection \u2014 re-run \`foir types\``
383
+ );
384
+ }
385
+ const narrowing = ctx.record?.modelKey === modelKey ? ctx.record : void 0;
386
+ const body = printFields(
387
+ model.selection.fields,
388
+ ctx,
389
+ narrowing?.omit,
390
+ narrowing?.pick,
391
+ true,
392
+ "",
393
+ depth
394
+ );
395
+ const includes = narrowing?.include ? printIncludes(ctx.source, model, modelKey, narrowing.include, ctx, depth) : "";
396
+ if (!body) {
397
+ return includes;
398
+ }
399
+ return includes ? `${body} ${includes}` : body;
400
+ }
401
+ var NO_FIELDS = { self: false, children: /* @__PURE__ */ new Map() };
402
+ function addressesMembers(node, union) {
403
+ for (const member of union.members) {
404
+ if (node.children.has(member.typeName)) return true;
405
+ }
406
+ return false;
407
+ }
408
+ function printUnion(unionName, ctx, omit, pick, path, depth) {
409
+ const union = ctx.unions[unionName];
410
+ if (!union) {
411
+ throw new FoirRequestError(
412
+ `[foir] the selection tree names the union "${unionName}", which is not in selectionUnions \u2014 re-run \`foir types\`, or pass selectionUnions alongside selections`
413
+ );
414
+ }
415
+ const perVariantPick = pick !== void 0 && addressesMembers(pick, union);
416
+ const perVariantOmit = omit !== void 0 && addressesMembers(omit, union);
417
+ charge(ctx);
418
+ const parts = ["__typename"];
419
+ const chargeBefore = ctx.budget.fields;
420
+ let widestMember = 0;
421
+ for (const member of union.members) {
422
+ let memberPick = pick;
423
+ if (perVariantPick) {
424
+ const named = pick.children.get(member.typeName);
425
+ memberPick = named === void 0 ? NO_FIELDS : named.self ? void 0 : named;
426
+ }
427
+ let memberOmit = omit;
428
+ if (perVariantOmit) {
429
+ const named = omit.children.get(member.typeName);
430
+ if (named?.self) continue;
431
+ memberOmit = named;
432
+ }
433
+ const memberPath = perVariantPick ? `${path}${member.typeName}.` : path;
434
+ const memberStart = ctx.budget.fields;
435
+ const body = printFields(member.fields, ctx, memberOmit, memberPick, false, memberPath, depth);
436
+ const memberCost = ctx.budget.fields - memberStart;
437
+ ctx.budget.fields = memberStart;
438
+ if (memberCost > widestMember) widestMember = memberCost;
439
+ if (body) parts.push(`... on ${member.typeName} { ${body} }`);
440
+ }
441
+ ctx.budget.fields = chargeBefore + widestMember;
442
+ return `{ ${parts.join(" ")} }`;
443
+ }
444
+ function printIncludes(source, model, modelKey, include, ctx, depth) {
445
+ const parts = [];
446
+ for (const [key, value] of Object.entries(include)) {
447
+ if (value === void 0 || value === false) continue;
448
+ const relation = model.relations?.find((r) => r.key === key);
449
+ if (!relation) {
450
+ throw new FoirRequestError(
451
+ `[foir] include "${key}": ${modelKey} has no such relation \u2014 re-run \`foir types\` if the model has changed`
452
+ );
453
+ }
454
+ const nested = value && typeof value === "object" && "include" in value ? value["include"] : void 0;
455
+ const body = relation.targets?.length ? printPolymorphicRelation(source, relation, ctx, nested, depth) : printRelationTarget(source, relation.target ?? "", ctx, nested, relation.key, depth);
456
+ charge(ctx, relation.connection ? 1 + CONNECTION_WRAPPER_FIELDS : 1);
457
+ parts.push(`${key} ${relation.connection ? wrapRelationConnection(body) : `{ ${body} }`}`);
458
+ }
459
+ return parts.join(" ");
460
+ }
461
+ var CONNECTION_WRAPPER_FIELDS = 9;
462
+ function wrapRelationConnection(body) {
463
+ return `{ edges { cursor node { ${body} } } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } totalCount }`;
464
+ }
465
+ function printRelationTarget(source, targetKey, ctx, nested, relationKey, depth) {
466
+ const target = source.models[targetKey];
467
+ if (!target?.selection) {
468
+ throw new FoirRequestError(
469
+ `[foir] include "${relationKey}": no selection published for its target model "${targetKey}" \u2014 re-run \`foir types\``
470
+ );
471
+ }
472
+ const bare = { source, unions: ctx.unions, budget: ctx.budget };
473
+ const body = printFields(target.selection.fields, bare, void 0, void 0, true, "", depth);
474
+ const includes = nested ? printIncludes(source, target, targetKey, nested, bare, depth) : "";
475
+ return includes ? `${body} ${includes}` : body;
476
+ }
477
+ function printPolymorphicRelation(source, relation, ctx, nested, depth) {
478
+ charge(ctx);
479
+ const parts = ["__typename"];
480
+ const chargeBefore = ctx.budget.fields;
481
+ let widestTarget = 0;
482
+ for (const targetKey of relation.targets ?? []) {
483
+ const target = source.models[targetKey];
484
+ if (!target?.selection) {
485
+ throw new FoirRequestError(
486
+ `[foir] include "${relation.key}": no selection published for its target model "${targetKey}" \u2014 re-run \`foir types\``
487
+ );
488
+ }
489
+ const targetStart = ctx.budget.fields;
490
+ const body = printRelationTarget(source, targetKey, ctx, nested, relation.key, depth);
491
+ const targetCost = ctx.budget.fields - targetStart;
492
+ ctx.budget.fields = targetStart;
493
+ if (targetCost > widestTarget) widestTarget = targetCost;
494
+ if (body) parts.push(`... on ${target.typeName} { ${body} }`);
495
+ }
496
+ ctx.budget.fields = chargeBefore + widestTarget;
497
+ return parts.join(" ");
498
+ }
499
+ var GRAPHQL_TYPE_REF = /^\[?\[?[_A-Za-z][_0-9A-Za-z]*!?\]?!?\]?!?$/;
500
+ function buildArguments(args, op, opLabel) {
501
+ const declared = op.args;
502
+ const definitions = [];
503
+ const call = [];
504
+ const variables = {};
505
+ for (const name of Object.keys(args).sort()) {
506
+ const value = args[name];
507
+ if (value === void 0) continue;
508
+ if (!declared?.includes(name)) {
509
+ throw new FoirRequestError(
510
+ `[foir] ${opLabel} does not take an argument "${name}" \u2014 it takes ${declared?.length ? declared.join(", ") : "no arguments"}`
511
+ );
512
+ }
513
+ const type = op.argTypes?.[name];
514
+ if (!type) {
515
+ throw new FoirRequestError(
516
+ `[foir] ${opLabel}: the manifest publishes no type for the argument "${name}", so it cannot be sent as a variable \u2014 and inferring one from the value would guess wrong on every enum. Re-run \`foir types\` against a server that publishes argTypes, or use foir.raw() with your own variable definitions`
517
+ );
518
+ }
519
+ if (!GRAPHQL_TYPE_REF.test(type)) {
520
+ throw new FoirRequestError(
521
+ `[foir] ${opLabel}: the manifest gives the argument "${name}" the type "${type}", which is not a GraphQL type reference`
522
+ );
523
+ }
524
+ definitions.push(`$${name}: ${type}`);
525
+ call.push(`${name}: $${name}`);
526
+ variables[name] = toVariableValue(value, `${opLabel} argument "${name}"`);
527
+ }
528
+ return {
529
+ definitions: definitions.length ? `(${definitions.join(", ")})` : "",
530
+ call: call.length ? `(${call.join(", ")})` : "",
531
+ variables
532
+ };
533
+ }
534
+ function toVariableValue(value, label) {
535
+ if (value === null) return null;
536
+ const kind = typeof value;
537
+ if (kind === "string" || kind === "boolean") return value;
538
+ if (kind === "number") {
539
+ if (!Number.isFinite(value)) {
540
+ throw new FoirRequestError(
541
+ `[foir] ${label} is ${String(value)}, which has no JSON form and cannot be sent`
542
+ );
543
+ }
544
+ return value;
545
+ }
546
+ if (kind === "bigint") {
547
+ const asNumber = Number(value);
548
+ if (!Number.isSafeInteger(asNumber)) {
549
+ throw new FoirRequestError(
550
+ `[foir] ${label} is the bigint ${String(value)}, which is outside the range JSON can carry exactly`
551
+ );
552
+ }
553
+ return asNumber;
554
+ }
555
+ if (value instanceof Date) {
556
+ if (Number.isNaN(value.getTime())) {
557
+ throw new FoirRequestError(`[foir] ${label} is an invalid Date`);
558
+ }
559
+ return value.toISOString();
560
+ }
561
+ if (Array.isArray(value)) {
562
+ return value.filter((item) => item !== void 0).map((item, index) => toVariableValue(item, `${label}[${index}]`));
563
+ }
564
+ if (kind === "object") {
565
+ const out = {};
566
+ for (const [key, child] of Object.entries(value)) {
567
+ if (child === void 0) continue;
568
+ out[key] = toVariableValue(child, `${label}.${key}`);
569
+ }
570
+ return out;
571
+ }
572
+ throw new FoirRequestError(
573
+ `[foir] ${label} is a ${kind}, which has no JSON form and cannot be sent`
574
+ );
575
+ }
576
+ function payloadRecordPaths(payload) {
577
+ if (payload.kind === "record") {
578
+ return payload.model ? [{ path: [], modelKey: payload.model }] : [];
579
+ }
580
+ const found = [];
581
+ const visit = (fields, prefix) => {
582
+ for (const field of fields ?? []) {
583
+ const at = [...prefix, field.alias ?? field.key];
584
+ if (field.model) found.push({ path: at, modelKey: field.model });
585
+ visit(field.fields, at);
586
+ }
587
+ };
588
+ visit(payload.fields, []);
589
+ return found;
590
+ }
591
+ function printPayload(payload, ctx, root, opLabel, depth) {
592
+ switch (payload.kind) {
593
+ case "leaf":
594
+ return void 0;
595
+ case "record": {
596
+ if (!payload.model) {
597
+ throw new FoirRequestError(
598
+ `[foir] ${opLabel}: the result shape says it is a record but names no model \u2014 re-run \`foir types\``
599
+ );
600
+ }
601
+ return printModelTree(payload.model, ctx, depth);
602
+ }
603
+ case "union": {
604
+ if (!payload.union) {
605
+ throw new FoirRequestError(
606
+ `[foir] ${opLabel}: the result shape says it is a union but names none \u2014 re-run \`foir types\``
607
+ );
608
+ }
609
+ return printUnion(payload.union, ctx, root.omit, root.pick, "", depth).slice(2, -2);
610
+ }
611
+ case "object": {
612
+ if (!payload.fields?.length) {
613
+ throw new FoirRequestError(
614
+ `[foir] ${opLabel}: the result shape is an object with no fields \u2014 re-run \`foir types\``
615
+ );
616
+ }
617
+ return printFields(payload.fields, ctx, root.omit, root.pick, false, "", depth);
618
+ }
619
+ default:
620
+ throw new FoirRequestError(
621
+ `[foir] ${opLabel}: the result shape has the kind "${String(payload.kind)}", which this client does not know how to print \u2014 upgrade @foir/sdk`
622
+ );
623
+ }
624
+ }
625
+ var DEFAULT_DEPTH = 1;
626
+ function planFoirNamespaceCall(source, call) {
627
+ const namespace = source.namespaces?.[call.namespace];
628
+ if (!namespace) {
629
+ throw new FoirRequestError(
630
+ `[foir] no namespace "${call.namespace}" in this credential's surface \u2014 re-run \`foir types\``
631
+ );
632
+ }
633
+ const opDef = namespace.ops?.[call.op];
634
+ if (!opDef) {
635
+ throw new FoirRequestError(
636
+ `[foir] the namespace "${call.namespace}" has no op "${call.op}" for this credential \u2014 re-run \`foir types\``
637
+ );
638
+ }
639
+ return planCall(
640
+ source,
641
+ opDef,
642
+ `${call.namespace}.${call.op}`,
643
+ operationName(call.namespace, call.op),
644
+ call
645
+ );
646
+ }
647
+ function planCall(source, opDef, opLabel, name, call) {
648
+ const payload = opDef.payload;
649
+ if (!payload) {
650
+ throw new FoirRequestError(
651
+ `[foir] ${opLabel}: the manifest publishes no result shape for this op, so there is nothing to print \u2014 and synthesising one client-side is exactly the guess this client refuses to make. Re-run \`foir types\` against a server that publishes op payloads`
652
+ );
653
+ }
654
+ if (payload.error) {
655
+ throw new FoirRequestError(
656
+ `[foir] ${opLabel}: the server could not resolve a result shape for this op: ${payload.error}`
657
+ );
658
+ }
659
+ if (call.omit && call.pick) {
660
+ throw new FoirRequestError(
661
+ `[foir] ${opLabel}: "omit" and "pick" are mutually exclusive \u2014 pass one or the other`
662
+ );
663
+ }
664
+ if (call.omit?.includes(PROTECTED_ROOT_FIELD)) {
665
+ throw new FoirRequestError(
666
+ `[foir] omit "${PROTECTED_ROOT_FIELD}": "${PROTECTED_ROOT_FIELD}" is required for correctness and cannot be removed`
667
+ );
668
+ }
669
+ const depth = call.depth ?? DEFAULT_DEPTH;
670
+ if (!Number.isInteger(depth) || depth < DEFAULT_DEPTH) {
671
+ throw new FoirRequestError(
672
+ `[foir] ${opLabel}: depth must be a whole number of union levels, ${DEFAULT_DEPTH} or more \u2014 got ${String(call.depth)}. ${DEFAULT_DEPTH} is the complete-by-default document; each level above it expands one further union`
673
+ );
674
+ }
675
+ const omit = call.omit?.length ? buildPathTrie(call.omit) : void 0;
676
+ const pick = call.pick?.length ? buildPathTrie(call.pick) : void 0;
677
+ const include = call.include && Object.keys(call.include).length ? call.include : void 0;
678
+ const narrowed = Boolean(omit || pick || include);
679
+ const recordPaths = payloadRecordPaths(payload);
680
+ const records = new Set(recordPaths.map((entry) => entry.modelKey));
681
+ if (records.size > 1 && narrowed) {
682
+ throw new FoirRequestError(
683
+ `[foir] ${opLabel}: the result carries records of more than one model (${[...records].sort().join(", ")}), so "include"/"pick"/"omit" do not say which one they mean \u2014 call it without them and narrow with foir.raw()`
684
+ );
685
+ }
686
+ const recordModel = records.size === 1 ? [...records][0] : void 0;
687
+ if (include && !recordModel) {
688
+ throw new FoirRequestError(
689
+ `[foir] ${opLabel}: "include" names relations of a record, and this op returns none`
690
+ );
691
+ }
692
+ const ctx = {
693
+ source,
694
+ unions: source.selectionUnions ?? {},
695
+ record: recordModel ? { modelKey: recordModel, omit, pick, include } : void 0,
696
+ budget: newBudget()
697
+ };
698
+ const root = recordModel ? {} : { omit, pick };
699
+ const selection = printPayload(payload, ctx, root, opLabel, depth);
700
+ if (selection === "") {
701
+ throw new FoirRequestError(
702
+ `[foir] ${opLabel}: the selection is empty after applying pick/omit \u2014 a GraphQL selection set cannot be empty`
703
+ );
704
+ }
705
+ charge(ctx);
706
+ if (depth > DEFAULT_DEPTH && (ctx.budget.stopped || ctx.budget.fields > MAX_QUERY_COMPLEXITY)) {
707
+ const reason = ctx.budget.stopped ? `expands to more than ${MAX_EMITTED_FIELDS} fields to print, which is past what this client will build in one call` : `makes this document select ${ctx.budget.fields} fields, over the server's limit of ${MAX_QUERY_COMPLEXITY}`;
708
+ throw new FoirRequestError(
709
+ `[foir] ${opLabel}: depth ${depth} ${reason}. Each level re-expands every stopping point inside the expansion, so spend the level on less: narrow with "pick"/"omit" \u2014 including per-member, pick: { <field>: { on: { <MemberType>: [...] } } } \u2014 or ask for depth ${depth - 1}`
710
+ );
711
+ }
712
+ const args = buildArguments(call.args, opDef, opLabel);
713
+ const keyword = opDef.mutation ? "mutation" : "query";
714
+ const body = selection === void 0 ? "" : ` { ${selection} }`;
715
+ return {
716
+ document: `${keyword} ${name}${args.definitions} { ${opDef.field}${args.call}${body} }`,
717
+ variables: args.variables,
718
+ mutation: opDef.mutation === true,
719
+ rootField: opDef.field,
720
+ fields: ctx.budget.fields,
721
+ records: recordPaths
722
+ };
723
+ }
724
+ function operationName(scope, op) {
725
+ const parts = `${scope} ${op}`.split(/[^0-9A-Za-z]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1));
726
+ const name = parts.join("");
727
+ return /^[_A-Za-z]/.test(name) ? name : `Foir${name}`;
728
+ }
729
+
730
+ // src/data/transport.ts
731
+ function foirDataEndpoint(apiUrl, path) {
732
+ return foirApiEndpoint(apiUrl, path);
733
+ }
734
+ async function resolveBearerToken(config) {
735
+ const source = config.bearerToken;
736
+ if (typeof source === "function") return await source() ?? void 0;
737
+ return source ?? void 0;
738
+ }
739
+ function foirRoute(input) {
740
+ if (!input.apiKey.startsWith("pk_")) return { lane: "origin", reason: "key" };
741
+ if (input.bearerToken) return { lane: "origin", reason: "bearer" };
742
+ if (input.mutation) return { lane: "origin", reason: "operation" };
743
+ if (input.channel === "draft") return { lane: "origin", reason: "channel" };
744
+ return { lane: "shared" };
745
+ }
746
+ async function executeFoirDocument(config, plan) {
747
+ const data = await runFoirDocument(config, plan.document, plan.mutation, plan.variables);
748
+ if (!(plan.rootField in data)) {
749
+ throw new FoirRequestError(
750
+ `[foir] the response has no "${plan.rootField}" field` + (Object.keys(data).length ? ` (got ${Object.keys(data).join(", ")})` : "")
751
+ );
752
+ }
753
+ return data[plan.rootField];
754
+ }
755
+ async function runFoirDocument(config, document, mutation, variables) {
756
+ const doFetch = config.fetch ?? globalThis.fetch;
757
+ const endpoint = foirDataEndpoint(config.apiUrl, "/graphql");
758
+ const bearerToken = await resolveBearerToken(config);
759
+ const headers = buildFoirHeaders({
760
+ apiKey: config.apiKey,
761
+ draftChannel: config.channel === "draft",
762
+ bearerToken,
763
+ extra: config.headers
764
+ });
765
+ const hash = await documentHash(document);
766
+ const route = foirRoute({
767
+ apiKey: config.apiKey,
768
+ bearerToken,
769
+ mutation,
770
+ channel: config.channel
771
+ });
772
+ const post = (body) => doFetch(endpoint, {
773
+ method: "POST",
774
+ headers,
775
+ body: JSON.stringify(body),
776
+ ...config.requestInit
777
+ });
778
+ const extensions = persistedQueryExtension(hash);
779
+ let res;
780
+ try {
781
+ if (route.lane === "shared") {
782
+ res = await doFetch(buildCdnUrl(endpoint, config.apiKey, hash, variables), {
783
+ method: "GET",
784
+ headers,
785
+ ...config.requestInit
786
+ });
787
+ if (await hasFoirErrorCode(res, PERSISTED_QUERY_NOT_FOUND, NOT_SHAREABLE)) {
788
+ res = await post({ query: document, variables, extensions });
789
+ }
790
+ } else {
791
+ res = await post({ variables, extensions });
792
+ if (await hasFoirErrorCode(res, PERSISTED_QUERY_NOT_FOUND)) {
793
+ res = await post({ query: document, variables, extensions });
794
+ }
795
+ }
796
+ } catch (err) {
797
+ throw new FoirRequestError("[foir] network error", { cause: err });
798
+ }
799
+ if (!res.ok) {
800
+ const errors = await readErrorBody(res);
801
+ const detail = errors ? `: ${errors.map((e) => e.message).join("; ")}` : "";
802
+ throw new FoirRequestError(`[foir] HTTP ${res.status} ${res.statusText}${detail}`, {
803
+ status: res.status,
804
+ graphQLErrors: errors
805
+ });
806
+ }
807
+ const json = await res.json();
808
+ return readFoirEnvelope(json);
809
+ }
810
+ async function readErrorBody(res) {
811
+ try {
812
+ const json = await res.json();
813
+ return json?.errors?.length ? json.errors : void 0;
814
+ } catch {
815
+ return void 0;
816
+ }
817
+ }
818
+
819
+ // src/data/manifest.ts
820
+ async function fetchFoirManifest(config) {
821
+ const url = foirDataEndpoint(config.apiUrl, "/v1/manifest");
822
+ const doFetch = config.fetch ?? globalThis.fetch;
823
+ let res;
824
+ try {
825
+ res = await doFetch(url, {
826
+ method: "GET",
827
+ headers: buildFoirHeaders({
828
+ apiKey: config.apiKey,
829
+ draftChannel: config.channel === "draft",
830
+ bearerToken: await resolveBearerToken(config),
831
+ extra: config.headers
832
+ }),
833
+ ...config.requestInit
834
+ });
835
+ } catch (err) {
836
+ throw new FoirRequestError("[foir] network error", { cause: err });
837
+ }
838
+ if (!res.ok) {
839
+ throw new FoirRequestError(
840
+ `[foir] GET /v1/manifest failed: HTTP ${res.status} ${res.statusText}`,
841
+ { status: res.status }
842
+ );
843
+ }
844
+ return await res.json();
845
+ }
846
+
847
+ // src/auth/ops.ts
848
+ var AUTH_OPS = {
849
+ // Pre-credential reads a login screen renders itself from.
850
+ authConfig: "authConfig",
851
+ authProviders: "authProviders",
852
+ defaultAuthProvider: "defaultAuthProvider",
853
+ customerSignupContext: "customerSignupContext",
854
+ // Post-credential self reads.
855
+ currentUser: "currentUser",
856
+ myProviderToken: "myProviderToken",
857
+ // Methods.
858
+ customerLogin: "customerLogin",
859
+ customerRegister: "customerRegister",
860
+ customerRequestOTP: "customerRequestOTP",
861
+ customerLoginOTP: "customerLoginOTP",
862
+ // Session lifecycle.
863
+ customerRefreshToken: "customerRefreshToken",
864
+ customerLogout: "customerLogout",
865
+ customerJoinWaitlist: "customerJoinWaitlist",
866
+ // Password management and account lifecycle — the block that had no path.
867
+ customerRequestPasswordReset: "customerRequestPasswordReset",
868
+ customerResetPassword: "customerResetPassword",
869
+ customerUpdatePassword: "customerUpdatePassword",
870
+ customerVerifyEmail: "customerVerifyEmail",
871
+ deleteMyAccount: "deleteMyAccount",
872
+ // External identity providers.
873
+ customerLoginWithProvider: "customerLoginWithProvider",
874
+ customerProviderCallback: "customerProviderCallback",
875
+ customerProviderVerifyOTP: "customerProviderVerifyOTP"
876
+ };
877
+ function authClientConfig(env, options) {
878
+ const apiKey = resolveFoirApiKey(env);
879
+ if (!apiKey) {
880
+ throw new FoirRequestError(
881
+ "[foir] no api key configured: set PLATFORM_API_KEY (or PLATFORM_PREVIEW_API_KEY under FOIR_PREVIEW=true)"
882
+ );
883
+ }
884
+ const apiUrl = env.PLATFORM_API_URL;
885
+ if (!apiUrl) {
886
+ throw new FoirRequestError("[foir] no api url configured: set PLATFORM_API_URL");
887
+ }
888
+ return {
889
+ apiUrl,
890
+ apiKey,
891
+ channel: isFoirPreview(env) ? "draft" : "published",
892
+ bearerToken: options?.bearerToken,
893
+ headers: options?.headers,
894
+ fetch: options?.fetch
895
+ };
896
+ }
897
+ var MANIFEST_CACHE_LIMIT = 8;
898
+ var manifestCache = /* @__PURE__ */ new Map();
899
+ function authManifest(config) {
900
+ const key = `${config.apiUrl}\0${config.apiKey}\0${config.channel ?? "published"}`;
901
+ const held = manifestCache.get(key);
902
+ if (held) return held;
903
+ const pending = fetchFoirManifest(config).catch((err) => {
904
+ manifestCache.delete(key);
905
+ throw err;
906
+ });
907
+ manifestCache.set(key, pending);
908
+ while (manifestCache.size > MANIFEST_CACHE_LIMIT) {
909
+ const oldest = manifestCache.keys().next();
910
+ if (oldest.done) break;
911
+ manifestCache.delete(oldest.value);
912
+ }
913
+ return pending;
914
+ }
915
+ async function authOp(env, op, args, options) {
916
+ const config = authClientConfig(env, options);
917
+ const manifest = await authManifest(config);
918
+ const plan = planFoirNamespaceCall(manifest, {
919
+ namespace: "auth",
920
+ op,
921
+ args: args ? stripUndefined(args) : {}
922
+ });
923
+ return executeFoirDocument(config, plan);
924
+ }
925
+ function stripUndefined(args) {
926
+ const out = {};
927
+ for (const [key, value] of Object.entries(args)) {
928
+ if (value !== void 0) out[key] = value;
929
+ }
930
+ return out;
931
+ }
118
932
 
119
933
  // src/auth/auth.ts
120
934
  var TOKEN_KEY = "foir:token";
@@ -122,31 +936,67 @@ var REFRESH_KEY = "foir:refreshToken";
122
936
  var CUSTOMER_ID_KEY = "foir:customerId";
123
937
  var EMAIL_KEY = "foir:email";
124
938
  async function customerLogin(env, vars) {
125
- return (await foirRequest(env, CustomerLoginDocument, vars)).customerLogin;
939
+ return authOp(env, AUTH_OPS.customerLogin, vars);
126
940
  }
127
941
  async function customerRegister(env, vars) {
128
- return (await foirRequest(env, CustomerRegisterDocument, vars)).customerRegister;
942
+ return authOp(env, AUTH_OPS.customerRegister, vars);
129
943
  }
130
944
  async function customerRequestOTP(env, email) {
131
- return (await foirRequest(env, CustomerRequestOTPDocument, { email })).customerRequestOTP;
945
+ return authOp(env, AUTH_OPS.customerRequestOTP, { email });
132
946
  }
133
947
  async function customerLoginOTP(env, vars) {
134
- return (await foirRequest(env, CustomerLoginOTPDocument, vars)).customerLoginOTP;
948
+ return authOp(env, AUTH_OPS.customerLoginOTP, vars);
135
949
  }
136
950
  async function customerRefreshToken(env, refreshToken) {
137
- return (await foirRequest(env, CustomerRefreshTokenDocument, { refreshToken })).customerRefreshToken;
951
+ return authOp(env, AUTH_OPS.customerRefreshToken, { refreshToken });
138
952
  }
139
953
  async function customerLoginWithProvider(env, input) {
140
- return (await foirRequest(env, CustomerLoginWithProviderDocument, { input })).customerLoginWithProvider;
954
+ return authOp(env, AUTH_OPS.customerLoginWithProvider, { input });
141
955
  }
142
956
  async function customerProviderCallback(env, input) {
143
- return (await foirRequest(env, CustomerProviderCallbackDocument, { input })).customerProviderCallback;
957
+ return authOp(env, AUTH_OPS.customerProviderCallback, { input });
144
958
  }
145
959
  async function customerProviderVerifyOTP(env, input) {
146
- return (await foirRequest(env, CustomerProviderVerifyOTPDocument, { input })).customerProviderVerifyOTP;
960
+ return authOp(env, AUTH_OPS.customerProviderVerifyOTP, { input });
147
961
  }
148
962
  async function getAuthProviders(env) {
149
- return (await foirRequest(env, AuthProvidersDocument, {})).authProviders;
963
+ return authOp(env, AUTH_OPS.authProviders);
964
+ }
965
+ async function getAuthConfig(env) {
966
+ return authOp(env, AUTH_OPS.authConfig);
967
+ }
968
+ async function getDefaultAuthProvider(env) {
969
+ return authOp(env, AUTH_OPS.defaultAuthProvider);
970
+ }
971
+ async function customerSignupContext(env) {
972
+ return authOp(env, AUTH_OPS.customerSignupContext);
973
+ }
974
+ async function customerJoinWaitlist(env, email) {
975
+ return authOp(env, AUTH_OPS.customerJoinWaitlist, { email });
976
+ }
977
+ async function customerRequestPasswordReset(env, email) {
978
+ return authOp(env, AUTH_OPS.customerRequestPasswordReset, { email });
979
+ }
980
+ async function customerResetPassword(env, vars) {
981
+ return authOp(env, AUTH_OPS.customerResetPassword, vars);
982
+ }
983
+ async function customerVerifyEmail(env, token) {
984
+ return authOp(env, AUTH_OPS.customerVerifyEmail, { token });
985
+ }
986
+ async function customerUpdatePassword(env, vars, options) {
987
+ return authOp(env, AUTH_OPS.customerUpdatePassword, vars, options);
988
+ }
989
+ async function currentUser(env, options) {
990
+ return authOp(env, AUTH_OPS.currentUser, void 0, options);
991
+ }
992
+ async function myProviderToken(env, providerKey, options) {
993
+ return authOp(env, AUTH_OPS.myProviderToken, { providerKey }, options);
994
+ }
995
+ async function customerLogout(env, options) {
996
+ return authOp(env, AUTH_OPS.customerLogout, void 0, options);
997
+ }
998
+ async function deleteMyAccount(env, vars, options) {
999
+ return authOp(env, AUTH_OPS.deleteMyAccount, vars, options);
150
1000
  }
151
1001
  async function saveSession(adapter, result) {
152
1002
  await adapter.set(TOKEN_KEY, result.token);
@@ -179,6 +1029,19 @@ async function registerWithPassword(adapter, env, vars) {
179
1029
  async function loginWithOTP(adapter, env, vars) {
180
1030
  return saveSession(adapter, await customerLoginOTP(env, vars));
181
1031
  }
1032
+ async function resetPasswordAndSignIn(adapter, env, vars) {
1033
+ return saveSession(adapter, await customerResetPassword(env, vars));
1034
+ }
1035
+ async function signOut(adapter, env) {
1036
+ const token = await getSessionToken(adapter);
1037
+ if (token) {
1038
+ try {
1039
+ await customerLogout(env, { bearerToken: token });
1040
+ } catch {
1041
+ }
1042
+ }
1043
+ await clearSession(adapter);
1044
+ }
182
1045
  async function refreshSession(adapter, env) {
183
1046
  const refreshToken = await adapter.get(REFRESH_KEY) ?? null;
184
1047
  if (!refreshToken) return null;
@@ -213,6 +1076,17 @@ async function authedRequest(adapter, env, document, variables) {
213
1076
  });
214
1077
  }
215
1078
  }
1079
+ async function authedOp(adapter, env, op, args) {
1080
+ const token = await getSessionToken(adapter);
1081
+ try {
1082
+ return await authOp(env, op, args, { bearerToken: token ?? void 0 });
1083
+ } catch (err) {
1084
+ if (!isAuthExpiredError(err)) throw err;
1085
+ const refreshed = await refreshSession(adapter, env);
1086
+ if (!refreshed) throw err;
1087
+ return authOp(env, op, args, { bearerToken: refreshed.token });
1088
+ }
1089
+ }
216
1090
  async function requireAuth(adapter, options) {
217
1091
  const token = await getSessionToken(adapter);
218
1092
  const customer = await getSessionCustomer(adapter);
@@ -410,36 +1284,43 @@ function trimSlash(s) {
410
1284
  return s.replace(/\/+$/, "");
411
1285
  }
412
1286
  export {
413
- AuthProvidersDocument,
414
- CustomerLoginDocument,
415
- CustomerLoginOTPDocument,
416
- CustomerLoginWithProviderDocument,
417
- CustomerProviderCallbackDocument,
418
- CustomerProviderVerifyOTPDocument,
419
- CustomerRefreshTokenDocument,
420
- CustomerRegisterDocument,
421
- CustomerRequestOTPDocument,
1287
+ AUTH_OPS,
1288
+ authedOp,
422
1289
  authedRequest,
423
1290
  clearSession,
424
1291
  createFoirSessionClient,
1292
+ currentUser,
1293
+ customerJoinWaitlist,
425
1294
  customerLogin,
426
1295
  customerLoginOTP,
427
1296
  customerLoginWithProvider,
1297
+ customerLogout,
428
1298
  customerProviderCallback,
429
1299
  customerProviderVerifyOTP,
430
1300
  customerRefreshToken,
431
1301
  customerRegister,
432
1302
  customerRequestOTP,
1303
+ customerRequestPasswordReset,
1304
+ customerResetPassword,
1305
+ customerSignupContext,
1306
+ customerUpdatePassword,
1307
+ customerVerifyEmail,
1308
+ deleteMyAccount,
433
1309
  fetchHostedSession,
1310
+ getAuthConfig,
434
1311
  getAuthProviders,
1312
+ getDefaultAuthProvider,
435
1313
  getSessionCustomer,
436
1314
  getSessionToken,
437
1315
  hostedLoginUrl,
438
1316
  loginWithOTP,
439
1317
  loginWithPassword,
1318
+ myProviderToken,
440
1319
  refreshSession,
441
1320
  registerWithPassword,
442
1321
  requireAdmin,
443
1322
  requireAuth,
444
- saveSession
1323
+ resetPasswordAndSignIn,
1324
+ saveSession,
1325
+ signOut
445
1326
  };