@foir/sdk 0.4.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 +290 -32
  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 -83
  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
package/dist/data.js ADDED
@@ -0,0 +1,309 @@
1
+ // src/transport/foir-http.ts
2
+ function isFoirPreview(env) {
3
+ return env.FOIR_PREVIEW === "true";
4
+ }
5
+ function resolveFoirApiKey(env) {
6
+ return isFoirPreview(env) ? env.PLATFORM_PREVIEW_API_KEY ?? env.PLATFORM_API_KEY : env.PLATFORM_API_KEY;
7
+ }
8
+ var FoirRequestError = class extends Error {
9
+ /** HTTP status code, when the request failed at the HTTP layer. */
10
+ status;
11
+ /** GraphQL errors, when the request failed with a no-data error envelope. */
12
+ graphQLErrors;
13
+ cause;
14
+ constructor(message, options) {
15
+ super(message);
16
+ this.name = "FoirRequestError";
17
+ this.cause = options?.cause;
18
+ this.status = options?.status;
19
+ this.graphQLErrors = options?.graphQLErrors;
20
+ }
21
+ /**
22
+ * The code of the first error, if the response carried one.
23
+ *
24
+ * A getter rather than a stored field so it stays correct for an error
25
+ * constructed anywhere, including by a caller in a test.
26
+ */
27
+ get code() {
28
+ return this.graphQLErrors?.[0]?.extensions?.code;
29
+ }
30
+ /** The correlation id, if the response carried one. */
31
+ get requestId() {
32
+ return this.graphQLErrors?.[0]?.extensions?.requestId;
33
+ }
34
+ /**
35
+ * Whether retrying this request could succeed.
36
+ *
37
+ * The 429 is the one that has to be in here and is easy to leave out: it sits
38
+ * among the 4xx statuses that are otherwise permanent, so a policy written as
39
+ * "retry 5xx, never retry 4xx" excludes exactly the failure that clears on its
40
+ * own. A storefront wrote that policy, its key saturated, and every read
41
+ * failed on the first attempt for an hour.
42
+ */
43
+ get retryable() {
44
+ if (this.status === void 0) return true;
45
+ if (this.status === 429 || this.code === "RATE_LIMITED") return true;
46
+ if (this.status >= 500) return true;
47
+ return this.code === "UNAVAILABLE" || this.code === "TIMEOUT";
48
+ }
49
+ /**
50
+ * How long to wait before retrying, in milliseconds, when the server said.
51
+ * Undefined when it did not, which means the caller picks its own backoff.
52
+ */
53
+ get retryAfterMs() {
54
+ const seconds = this.graphQLErrors?.[0]?.extensions?.retryAfter;
55
+ return typeof seconds === "number" && seconds > 0 ? seconds * 1e3 : void 0;
56
+ }
57
+ };
58
+ function buildFoirHeaders(input) {
59
+ const headers = {
60
+ "Content-Type": "application/json",
61
+ "x-api-key": input.apiKey
62
+ };
63
+ if (input.draftChannel) {
64
+ headers["X-Foir-Schema-Channel"] = "draft";
65
+ }
66
+ if (input.bearerToken) {
67
+ headers["Authorization"] = `Bearer ${input.bearerToken}`;
68
+ }
69
+ if (input.extra) {
70
+ for (const [key, value] of Object.entries(input.extra)) {
71
+ headers[key] = value;
72
+ }
73
+ }
74
+ return headers;
75
+ }
76
+ function readFoirEnvelope(json) {
77
+ if (json.errors?.length) {
78
+ const msg = json.errors.map((e) => e.message).join("; ");
79
+ if (json.data) {
80
+ console.warn(`[foir] GraphQL partial errors (data returned): ${msg}`);
81
+ } else {
82
+ throw new FoirRequestError(`[foir] GraphQL errors: ${msg}`, {
83
+ graphQLErrors: json.errors
84
+ });
85
+ }
86
+ }
87
+ if (!json.data) {
88
+ throw new FoirRequestError("[foir] empty GraphQL response");
89
+ }
90
+ return json.data;
91
+ }
92
+
93
+ // src/data/transport.ts
94
+ function foirDataEndpoint(apiUrl, path) {
95
+ const base = apiUrl.trim().replace(/\/+$/, "").replace(/\/graphql$/i, "");
96
+ if (!base) {
97
+ throw new FoirRequestError("[foir] apiUrl is empty \u2014 pass the Foir API origin");
98
+ }
99
+ return base + path;
100
+ }
101
+ async function resolveBearerToken(config) {
102
+ const source = config.bearerToken;
103
+ if (typeof source === "function") return await source() ?? void 0;
104
+ return source ?? void 0;
105
+ }
106
+ async function readErrorBody(res) {
107
+ try {
108
+ const json = await res.json();
109
+ return json?.errors?.length ? json.errors : void 0;
110
+ } catch {
111
+ return void 0;
112
+ }
113
+ }
114
+ async function postFoirOp(config, body) {
115
+ const url = foirDataEndpoint(config.apiUrl, "/v1/query");
116
+ const doFetch = config.fetch ?? globalThis.fetch;
117
+ const headers = buildFoirHeaders({
118
+ apiKey: config.apiKey,
119
+ draftChannel: config.channel === "draft",
120
+ bearerToken: await resolveBearerToken(config),
121
+ extra: config.headers
122
+ });
123
+ let res;
124
+ try {
125
+ res = await doFetch(url, {
126
+ method: "POST",
127
+ headers,
128
+ body: JSON.stringify(body),
129
+ ...config.requestInit
130
+ });
131
+ } catch (err) {
132
+ throw new FoirRequestError("[foir] network error", { cause: err });
133
+ }
134
+ if (!res.ok) {
135
+ const errors = await readErrorBody(res);
136
+ const detail = errors ? `: ${errors.map((e) => e.message).join("; ")}` : "";
137
+ throw new FoirRequestError(`[foir] HTTP ${res.status} ${res.statusText}${detail}`, {
138
+ status: res.status,
139
+ graphQLErrors: errors
140
+ });
141
+ }
142
+ const json = await res.json();
143
+ const data = readFoirEnvelope(json);
144
+ const roots = Object.keys(data);
145
+ if (roots.length !== 1) {
146
+ throw new FoirRequestError(
147
+ `[foir] expected exactly one root field in the /v1/query response, got ${roots.length}` + (roots.length ? ` (${roots.join(", ")})` : "")
148
+ );
149
+ }
150
+ return data[roots[0]];
151
+ }
152
+
153
+ // src/data/manifest.ts
154
+ async function fetchFoirManifest(config) {
155
+ const url = foirDataEndpoint(config.apiUrl, "/v1/manifest");
156
+ const doFetch = config.fetch ?? globalThis.fetch;
157
+ let res;
158
+ try {
159
+ res = await doFetch(url, {
160
+ method: "GET",
161
+ headers: buildFoirHeaders({
162
+ apiKey: config.apiKey,
163
+ draftChannel: config.channel === "draft",
164
+ bearerToken: await resolveBearerToken(config),
165
+ extra: config.headers
166
+ }),
167
+ ...config.requestInit
168
+ });
169
+ } catch (err) {
170
+ throw new FoirRequestError("[foir] network error", { cause: err });
171
+ }
172
+ if (!res.ok) {
173
+ throw new FoirRequestError(
174
+ `[foir] GET /v1/manifest failed: HTTP ${res.status} ${res.statusText}`,
175
+ { status: res.status }
176
+ );
177
+ }
178
+ return await res.json();
179
+ }
180
+
181
+ // src/data/client.ts
182
+ var NON_MODEL_PROPS = /* @__PURE__ */ new Set([
183
+ "then",
184
+ "catch",
185
+ "finally",
186
+ "toJSON",
187
+ "toString",
188
+ "valueOf",
189
+ "constructor",
190
+ "inspect",
191
+ "asymmetricMatch",
192
+ "$$typeof",
193
+ "_isMockFunction",
194
+ "nodeType"
195
+ ]);
196
+ function selectionPaths(value, verb, prefix = "") {
197
+ if (Array.isArray(value)) return value;
198
+ if (!value || typeof value !== "object") return [];
199
+ const out = [];
200
+ for (const [key, child] of Object.entries(value)) {
201
+ const path = prefix ? `${prefix}.${key}` : key;
202
+ if (child === true) {
203
+ out.push(path);
204
+ continue;
205
+ }
206
+ if (child && typeof child === "object" && verb in child) {
207
+ out.push(...selectionPaths(child[verb], verb, path));
208
+ }
209
+ }
210
+ return out;
211
+ }
212
+ function splitQuery(query) {
213
+ const args = {};
214
+ let include;
215
+ let omit;
216
+ let pick;
217
+ for (const [key, value] of Object.entries(query ?? {})) {
218
+ if (value === void 0) continue;
219
+ if (key === "include") {
220
+ include = value;
221
+ } else if (key === "omit") {
222
+ omit = selectionPaths(value, "omit");
223
+ } else if (key === "pick") {
224
+ pick = selectionPaths(value, "pick");
225
+ } else {
226
+ args[key] = value;
227
+ }
228
+ }
229
+ return { args, include, omit, pick };
230
+ }
231
+ function createFoirClient(config) {
232
+ if (!config?.apiUrl || !config?.apiKey) {
233
+ throw new Error("[foir] createFoirClient needs both apiUrl and apiKey");
234
+ }
235
+ let driftChecked = false;
236
+ const maybeCheckDrift = () => {
237
+ const generated = config.schemaVersion;
238
+ if (driftChecked) return;
239
+ if (generated === void 0) return;
240
+ if (config.checkSchemaDrift === false) return;
241
+ driftChecked = true;
242
+ void fetchFoirManifest(config).then((manifest) => {
243
+ if (manifest.schemaVersion === generated) return;
244
+ const drift = {
245
+ generated,
246
+ server: manifest.schemaVersion,
247
+ channel: manifest.channel
248
+ };
249
+ if (config.onSchemaDrift) {
250
+ config.onSchemaDrift(drift);
251
+ return;
252
+ }
253
+ console.warn(
254
+ `[foir] schema drift: types were generated from schemaVersion ${drift.generated}, the server is serving ${drift.server} (${drift.channel}). Re-run \`foir types\` \u2014 new models and fields will be missing, and removed ones will still type-check.`
255
+ );
256
+ }).catch(() => {
257
+ });
258
+ };
259
+ const run = async (body) => {
260
+ const result = await postFoirOp(config, body);
261
+ maybeCheckDrift();
262
+ return result;
263
+ };
264
+ const opProxy = (address) => new Proxy({}, {
265
+ get(_target, op) {
266
+ if (typeof op !== "string" || NON_MODEL_PROPS.has(op)) return void 0;
267
+ return (query) => {
268
+ const { args, include, omit, pick } = splitQuery(query);
269
+ return run({ ...address, op, args, include, omit, pick });
270
+ };
271
+ }
272
+ });
273
+ const nsProxy = new Proxy({}, {
274
+ get(_target, namespace) {
275
+ if (typeof namespace !== "string" || NON_MODEL_PROPS.has(namespace)) return void 0;
276
+ return opProxy({ namespace });
277
+ }
278
+ });
279
+ const raw = (op) => run(op);
280
+ return new Proxy({}, {
281
+ get(_target, prop) {
282
+ if (prop === "ns") return nsProxy;
283
+ if (prop === "raw") return raw;
284
+ if (typeof prop !== "string" || NON_MODEL_PROPS.has(prop)) return void 0;
285
+ return opProxy({ model: prop });
286
+ }
287
+ });
288
+ }
289
+ function createFoirClientFromEnv(env, overrides) {
290
+ const apiKey = resolveFoirApiKey(env);
291
+ if (!env.PLATFORM_API_URL || !apiKey) {
292
+ throw new FoirRequestError(
293
+ "[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
294
+ );
295
+ }
296
+ return createFoirClient({
297
+ ...overrides,
298
+ apiUrl: env.PLATFORM_API_URL,
299
+ apiKey,
300
+ channel: isFoirPreview(env) ? "draft" : "published"
301
+ });
302
+ }
303
+ export {
304
+ FoirRequestError,
305
+ createFoirClient,
306
+ createFoirClientFromEnv,
307
+ fetchFoirManifest,
308
+ foirDataEndpoint
309
+ };
@@ -0,0 +1,42 @@
1
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
2
+ import { type FoirEnv } from '../transport/foir-http';
3
+ /**
4
+ * Whether this read can be served by the shared endpoint.
5
+ *
6
+ * These mirror the conditions the server refuses on, so a request that would be
7
+ * turned away is not sent there in the first place. The server is still the
8
+ * authority — it refuses rather than falling back, and `foirRequest` retries on
9
+ * `/graphql` when it does — but every condition checked here is one round trip
10
+ * saved.
11
+ */
12
+ export declare function isCdnEligible(env: FoirEnv, documentText: string, bearerToken?: string): boolean;
13
+ /**
14
+ * Build the shared-endpoint URL for a read.
15
+ *
16
+ * The credential goes in the URL deliberately: a shared cache keys on the URL
17
+ * alone, so a key that rode only in a header would let two callers with
18
+ * different reach collide on one entry and be served each other's body.
19
+ */
20
+ export declare function buildCdnUrl(endpoint: string, apiKey: string, hash: string, variables?: unknown): string;
21
+ /** The APQ handshake object, in Apollo's shape because every client uses it. */
22
+ export declare function persistedQueryExtension(hash: string): {
23
+ persistedQuery: {
24
+ version: number;
25
+ sha256Hash: string;
26
+ };
27
+ };
28
+ /**
29
+ * SHA-256 of the document text, hex encoded — the identity a persisted query is
30
+ * stored and retrieved under.
31
+ *
32
+ * Web Crypto rather than a bundled implementation: it is present in every
33
+ * runtime this SDK targets (workers, edge, Node 18+) and adds nothing to the
34
+ * caller's bundle.
35
+ */
36
+ export declare function documentHash(documentText: string): Promise<string>;
37
+ /** The code the server answers with when it has never seen this hash. */
38
+ export declare const PERSISTED_QUERY_NOT_FOUND = "PERSISTED_QUERY_NOT_FOUND";
39
+ /** The code the shared endpoint refuses with, so a caller knows to retry. */
40
+ export declare const NOT_SHAREABLE = "NOT_SHAREABLE";
41
+ /** Print a document once, so eligibility and the URL agree on the same text. */
42
+ export declare function printDocument<TData, TVariables>(document: TypedDocumentNode<TData, TVariables>): string;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `@foir/sdk/client` — client-agnostic GraphQL transport for SSR/edge loaders.
3
+ *
4
+ * A thin auth transport, not a GraphQL client: it carries the Foir-specific
5
+ * `x-api-key` + preview schema channel and runs typed documents via `fetch`.
6
+ * Consumers that bring their own GraphQL client (Apollo/urql/react-query) use
7
+ * the generated `TypedDocumentNode`s directly and replicate those two headers.
8
+ */
9
+ export { foirRequest, isFoirPreview, absolutiseFoirMediaUrl, FOIR_CDN_BASE, FoirRequestError, } from './foir-request';
10
+ export type { FoirEnv, FoirRequestOptions } from './foir-request';
11
+ /**
12
+ * Failure classification. Branch on these rather than on `err.message`: the
13
+ * codes are a stable contract, the wording is not.
14
+ *
15
+ * The one worth knowing about is `isFoirRateLimited`. It is the only 4xx that
16
+ * clears on its own, and a client that lumps it in with the rest fails every
17
+ * read for as long as the key stays saturated — which looks, from the outside,
18
+ * exactly like a site with no content.
19
+ */
20
+ export { isFoirRateLimited, isFoirUnauthenticated, isFoirForbidden, isFoirNotFound, readFoirRateLimit, } from './foir-request';
21
+ export type { FoirErrorCode, FoirErrorExtensions, FoirGraphQLErrorItem, FoirRateLimitSnapshot, } from './foir-request';
22
+ /**
23
+ * Retry. `createFoirContentClient` applies this for you; these are exported for
24
+ * a caller running its own loop, so both use one definition of "worth retrying"
25
+ * rather than two that disagree about the 429.
26
+ */
27
+ export { withFoirRetry, foirRetryDelayMs } from './foir-request';
28
+ export type { FoirRetryPolicy } from './foir-request';
29
+ export { createFoirContentClient } from './content-client';
30
+ export type { FoirContentClient, FoirContentClientConfig, FoirTokenProvider, } from './content-client';
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Session-bound content client — forwards the customer bearer on every read
3
+ * when one is available, so auth/segment-targeted variants and customer-scoped
4
+ * records resolve out of the box, with no per-query wiring to forget.
5
+ *
6
+ * Resolution keys off the request's principal (see the platform's two-gate
7
+ * model): the `x-api-key` alone resolves the *anonymous baseline*
8
+ * (`context.authStatus = ""`); adding the customer bearer resolves the request
9
+ * as that customer (`context.authStatus = "authenticated"`, `customer_id` set).
10
+ *
11
+ * Caching: an api-key-only read is identical for every anonymous visitor and is
12
+ * shared-cacheable; a bearer-bearing read resolves per-customer and is not.
13
+ * Because the bearer only exists once a customer signs in, anonymous traffic
14
+ * keeps hitting the shared-cacheable baseline — only signed-in reads become
15
+ * per-customer. Bind this client wherever content is personalized (for most
16
+ * apps, everywhere the customer may be signed in) and use plain `foirRequest`
17
+ * for reads that must stay anonymous regardless of session.
18
+ */
19
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
20
+ import type { FoirEnv, FoirRequestOptions } from './foir-request';
21
+ import { type FoirRetryPolicy } from '../transport/retry';
22
+ import type { FoirRateLimitSnapshot } from '../transport/foir-http';
23
+ export type { FoirTokenProvider } from '../transport/foir-http';
24
+ import type { FoirTokenProvider } from '../transport/foir-http';
25
+ export interface FoirContentClientConfig {
26
+ env: FoirEnv;
27
+ /**
28
+ * Resolves the current customer bearer before every request. When it yields a
29
+ * token the token is forwarded, so `authStatus`/segment targeting and
30
+ * customer-scoped records resolve as that customer; when it yields
31
+ * null/undefined the read hits the anonymous baseline. Omit for a client that
32
+ * is always anonymous (equivalent to calling `foirRequest` directly).
33
+ */
34
+ getToken?: FoirTokenProvider;
35
+ /**
36
+ * Retry policy for transient failures — no response, 5xx, and 429.
37
+ *
38
+ * ON BY DEFAULT, which is the point of it being here. Retry that has to be
39
+ * opted into is retry each integrator writes for themselves, and the one
40
+ * detail they get wrong is the 429: it looks like the permanent 4xx family it
41
+ * sits in, and it is the only failure in that family that clears on its own.
42
+ * Pass `{ attempts: 1 }` to turn it off.
43
+ */
44
+ retry?: FoirRetryPolicy;
45
+ /**
46
+ * Called with the `X-RateLimit-*` headers of every response. Wire it to your
47
+ * logger and you will see a key approaching its ceiling instead of finding
48
+ * out when it hits one.
49
+ */
50
+ onRateLimit?: (snapshot: FoirRateLimitSnapshot | undefined) => void;
51
+ }
52
+ export interface FoirContentClient {
53
+ /**
54
+ * Run a typed document, forwarding the bound customer bearer when available.
55
+ * An explicit `options.bearerToken` still wins, so a single call can override
56
+ * the bound token (or force anonymous by passing `undefined` — the bound
57
+ * token only fills in when `options.bearerToken` is not supplied).
58
+ */
59
+ request<TData, TVariables>(document: TypedDocumentNode<TData, TVariables>, variables?: TVariables, options?: FoirRequestOptions): Promise<TData>;
60
+ }
61
+ /**
62
+ * Create a {@link FoirContentClient} bound to an env + token source. Every
63
+ * `request` forwards the customer bearer when `getToken` yields one, upgrading
64
+ * that read from the anonymous baseline to the signed-in customer.
65
+ */
66
+ export declare function createFoirContentClient(config: FoirContentClientConfig): FoirContentClient;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Foir GraphQL client — a single typed fetch wrapper for SSR/edge loaders.
3
+ *
4
+ * No GraphQL client (Apollo/urql) required: the Foir public API is a plain
5
+ * `POST` with an `x-api-key` header, and the dominant consumers (Hydrogen on
6
+ * Oxygen, React Router loaders, TanStack Start on CF Workers) fetch in
7
+ * server-only loaders and render — so Apollo's `InMemoryCache`/`useQuery`
8
+ * reactivity is dead weight, and a shared cache on a stateless multi-tenant
9
+ * edge worker is a cross-tenant hazard. This helper carries only the
10
+ * Foir-specific transport concerns (auth header, preview schema channel,
11
+ * error shape). Consumers on a GraphQL client (Apollo / urql / react-query /
12
+ * graphql-request) use the generated `TypedDocumentNode`s directly with their
13
+ * own client and replicate the two Foir-specific headers below — the
14
+ * `x-api-key` and, in preview, `X-Foir-Schema-Channel: draft`.
15
+ *
16
+ * Deduplicates the hand-copied `lib/foir/client.ts` that shipped identically
17
+ * across the Foir storefronts.
18
+ *
19
+ * The transport concerns themselves now live in `../transport/foir-http`, which
20
+ * has no `graphql` import, so `@foir/sdk/data` can reuse them without dragging
21
+ * `graphql` into a storefront bundle. Only `print` stayed here. Everything that
22
+ * module owns is re-exported below, unchanged, so this file's public surface is
23
+ * exactly what it always was.
24
+ */
25
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
26
+ import { type FoirEnv, type FoirRequestOptions } from '../transport/foir-http';
27
+ export { FoirRequestError, isFoirPreview, isFoirRateLimited, isFoirUnauthenticated, isFoirForbidden, isFoirNotFound, readFoirRateLimit, type FoirEnv, type FoirErrorCode, type FoirErrorExtensions, type FoirGraphQLErrorItem, type FoirRateLimitSnapshot, type FoirRequestOptions, } from '../transport/foir-http';
28
+ export { withFoirRetry, foirRetryDelayMs, type FoirRetryPolicy, } from '../transport/retry';
29
+ /** Default Foir CDN origin used to absolutise relative media URLs. */
30
+ export declare const FOIR_CDN_BASE = "https://cdn.foir.dev/";
31
+ /**
32
+ * Foir returns `hlsManifestUrl` as a relative path while every other media
33
+ * URL is absolute. Prefix relative paths with the CDN host so the browser
34
+ * doesn't request them from the consumer's own origin. Already-absolute URLs
35
+ * pass through untouched. `cdnBase` is overridable for non-default CDN hosts.
36
+ */
37
+ export declare function absolutiseFoirMediaUrl(url: string | null | undefined, cdnBase?: string): string | undefined;
38
+ /**
39
+ * Execute a typed GraphQL document against the Foir platform.
40
+ *
41
+ * Throws `FoirRequestError` on HTTP failure or GraphQL errors so the caller
42
+ * can decide whether to rethrow or fall back to local defaults. GraphQL can
43
+ * return partial data alongside errors; when data is present the errors are
44
+ * logged and the data returned (callers handle nulls), and we only throw when
45
+ * there's no data at all.
46
+ */
47
+ export declare function foirRequest<TData, TVariables>(env: FoirEnv, document: TypedDocumentNode<TData, TVariables>, variables?: TVariables, options?: FoirRequestOptions): Promise<TData>;
@@ -0,0 +1,22 @@
1
+ export interface UseAutoResizeOptions {
2
+ /** Minimum height to report (prevents too-small iframes). @default 400 */
3
+ minHeight?: number;
4
+ /** Debounce delay in milliseconds. @default 100 */
5
+ debounce?: number;
6
+ /** Whether auto-resize is enabled. @default true */
7
+ enabled?: boolean;
8
+ }
9
+ /**
10
+ * Automatically resize the host iframe based on content height.
11
+ *
12
+ * @returns A callback ref to attach to your root container element.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * function MyEditor() {
17
+ * const containerRef = useAutoResize({ minHeight: 600 });
18
+ * return <div ref={containerRef}>...</div>;
19
+ * }
20
+ * ```
21
+ */
22
+ export declare function useAutoResize(options?: UseAutoResizeOptions): (node: HTMLDivElement | null) => void;
@@ -0,0 +1,16 @@
1
+ import type { EditorContextValue } from '../types';
2
+ /**
3
+ * Access the Foir editor context.
4
+ *
5
+ * Must be used inside an `<EditorProvider>`.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * function MyEditor() {
10
+ * const { isReady, init, client, updateField, setDirty } = useEditor();
11
+ * if (!isReady) return <p>Loading...</p>;
12
+ * return <form>...</form>;
13
+ * }
14
+ * ```
15
+ */
16
+ export declare function useEditor(): EditorContextValue;