@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.
- package/CHANGELOG.md +325 -0
- package/README.md +63 -0
- package/dist/auth/auth.d.ts +140 -0
- package/dist/auth/hosted.d.ts +40 -0
- package/dist/auth/index.d.ts +15 -0
- package/dist/auth/operations.d.ts +123 -0
- package/dist/auth/session-client.d.ts +76 -0
- package/dist/auth-session.js +137 -0
- package/dist/auth.js +238 -26
- package/dist/client/platform-client.d.ts +29 -0
- package/dist/client.js +281 -37
- package/dist/codegen/block-registry.d.ts +4 -8
- package/dist/codegen/block-scaffold.d.ts +4 -8
- package/dist/codegen/index.d.ts +8 -62
- package/dist/codegen/preset.d.ts +60 -0
- package/dist/codegen/shared.d.ts +25 -0
- package/dist/components/EditorProvider.d.ts +36 -0
- package/dist/content/content-renderer.d.ts +47 -0
- package/dist/content/index.d.ts +12 -0
- package/dist/content/richtext.d.ts +70 -0
- package/dist/data/builtins.d.ts +296 -0
- package/dist/data/client.d.ts +94 -0
- package/dist/data/index.d.ts +54 -0
- package/dist/data/manifest.d.ts +161 -0
- package/dist/data/schema.d.ts +243 -0
- package/dist/data/selection.d.ts +330 -0
- package/dist/data/transport.d.ts +107 -0
- package/dist/data.js +309 -0
- package/dist/graphql/cdn-route.d.ts +42 -0
- package/dist/graphql/client.d.ts +30 -0
- package/dist/graphql/content-client.d.ts +66 -0
- package/dist/graphql/foir-request.d.ts +47 -0
- package/dist/hooks/useAutoResize.d.ts +22 -0
- package/dist/hooks/useEditor.d.ts +16 -0
- package/dist/index.d.ts +10 -394
- package/dist/media/BlurhashCanvas.d.ts +10 -0
- package/dist/media/ProgressiveMedia.d.ts +56 -0
- package/dist/media/index.d.ts +14 -0
- package/dist/media/media.d.ts +139 -0
- package/dist/media/useProgressiveLoad.d.ts +23 -0
- package/dist/media.js +5 -0
- package/dist/next/cookieAdapter.d.ts +26 -0
- package/dist/next/index.d.ts +15 -0
- package/dist/next/middleware.d.ts +49 -0
- package/dist/next/session.d.ts +53 -0
- package/dist/next.js +230 -26
- package/dist/protocol/constants.d.ts +6 -0
- package/dist/protocol/messages.d.ts +155 -0
- package/dist/protocol/validation.d.ts +8 -0
- package/dist/push/index.d.ts +14 -0
- package/dist/push/operations.d.ts +22 -0
- package/dist/push/web-push.d.ts +39 -0
- package/dist/push.js +230 -26
- package/dist/react/FoirProvider.d.ts +116 -0
- package/dist/react/OAuthButtons.d.ts +20 -0
- package/dist/react/SignIn.d.ts +22 -0
- package/dist/react/UserButton.d.ts +13 -0
- package/dist/react/index.d.ts +19 -0
- package/dist/react/localStorageAdapter.d.ts +10 -0
- package/dist/react.js +230 -26
- package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
- package/dist/server/callback-client.d.ts +92 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/operation-types.d.ts +134 -0
- package/dist/server/require-scoped-token.d.ts +76 -0
- package/dist/server/server-client.d.ts +34 -0
- package/dist/server/types.d.ts +54 -0
- package/dist/server/verify-scoped-token.d.ts +100 -0
- package/dist/server/webhook-helpers.d.ts +36 -0
- package/dist/transport/foir-http.d.ts +217 -0
- package/dist/transport/retry.d.ts +61 -0
- package/dist/types/index.d.ts +124 -0
- package/package.json +23 -13
- package/dist/auth.d.ts +0 -382
- package/dist/client.d.ts +0 -135
- package/dist/content.d.ts +0 -96
- package/dist/media.d.ts +0 -234
- package/dist/next.d.ts +0 -178
- package/dist/push.d.ts +0 -109
- package/dist/react.d.ts +0 -319
- package/dist/server.d.ts +0 -534
- package/dist/shared-DkWlEd2Z.d.ts +0 -18
package/dist/client.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
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
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (!url) return void 0;
|
|
9
|
-
if (/^https?:\/\//i.test(url)) return url;
|
|
10
|
-
return cdnBase + url.replace(/^\/+/, "");
|
|
11
|
+
function resolveFoirApiKey(env) {
|
|
12
|
+
return isFoirPreview(env) ? env.PLATFORM_PREVIEW_API_KEY ?? env.PLATFORM_API_KEY : env.PLATFORM_API_KEY;
|
|
11
13
|
}
|
|
12
14
|
var FoirRequestError = class extends Error {
|
|
13
15
|
/** HTTP status code, when the request failed at the HTTP layer. */
|
|
@@ -22,41 +24,95 @@ var FoirRequestError = class extends Error {
|
|
|
22
24
|
this.status = options?.status;
|
|
23
25
|
this.graphQLErrors = options?.graphQLErrors;
|
|
24
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
|
+
}
|
|
25
63
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
64
|
+
function isFoirRateLimited(err) {
|
|
65
|
+
return err instanceof FoirRequestError && (err.status === 429 || err.code === "RATE_LIMITED");
|
|
66
|
+
}
|
|
67
|
+
function isFoirUnauthenticated(err) {
|
|
68
|
+
return err instanceof FoirRequestError && (err.status === 401 || err.code === "UNAUTHENTICATED");
|
|
69
|
+
}
|
|
70
|
+
function isFoirForbidden(err) {
|
|
71
|
+
return err instanceof FoirRequestError && (err.status === 403 || err.code === "PERMISSION_DENIED");
|
|
72
|
+
}
|
|
73
|
+
function isFoirNotFound(err) {
|
|
74
|
+
return err instanceof FoirRequestError && err.code === "NOT_FOUND";
|
|
75
|
+
}
|
|
76
|
+
function readFoirRateLimit(headers) {
|
|
77
|
+
const raw = {
|
|
78
|
+
limit: headers.get("X-RateLimit-Limit"),
|
|
79
|
+
remaining: headers.get("X-RateLimit-Remaining"),
|
|
80
|
+
resetAt: headers.get("X-RateLimit-Reset")
|
|
81
|
+
};
|
|
82
|
+
if (raw.limit === null || raw.remaining === null || raw.resetAt === null) {
|
|
83
|
+
return void 0;
|
|
33
84
|
}
|
|
85
|
+
const limit = Number(raw.limit);
|
|
86
|
+
const remaining = Number(raw.remaining);
|
|
87
|
+
const resetAt = Number(raw.resetAt);
|
|
88
|
+
if (!Number.isFinite(limit) || !Number.isFinite(remaining) || !Number.isFinite(resetAt)) {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
return { limit, remaining, resetAt };
|
|
92
|
+
}
|
|
93
|
+
function readFoirRetryAfterMs(headers) {
|
|
94
|
+
const seconds = Number(headers.get("Retry-After"));
|
|
95
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : void 0;
|
|
96
|
+
}
|
|
97
|
+
function buildFoirHeaders(input) {
|
|
34
98
|
const headers = {
|
|
35
99
|
"Content-Type": "application/json",
|
|
36
|
-
"x-api-key": apiKey
|
|
100
|
+
"x-api-key": input.apiKey
|
|
37
101
|
};
|
|
38
|
-
if (
|
|
102
|
+
if (input.draftChannel) {
|
|
39
103
|
headers["X-Foir-Schema-Channel"] = "draft";
|
|
40
104
|
}
|
|
41
|
-
if (
|
|
42
|
-
headers["Authorization"] = `Bearer ${
|
|
105
|
+
if (input.bearerToken) {
|
|
106
|
+
headers["Authorization"] = `Bearer ${input.bearerToken}`;
|
|
43
107
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
headers,
|
|
49
|
-
body: JSON.stringify({ query: print(document), variables })
|
|
50
|
-
});
|
|
51
|
-
} catch (err) {
|
|
52
|
-
throw new FoirRequestError("[foir] network error", { cause: err });
|
|
53
|
-
}
|
|
54
|
-
if (!res.ok) {
|
|
55
|
-
throw new FoirRequestError(`[foir] HTTP ${res.status} ${res.statusText}`, {
|
|
56
|
-
status: res.status
|
|
57
|
-
});
|
|
108
|
+
if (input.extra) {
|
|
109
|
+
for (const [key, value] of Object.entries(input.extra)) {
|
|
110
|
+
headers[key] = value;
|
|
111
|
+
}
|
|
58
112
|
}
|
|
59
|
-
|
|
113
|
+
return headers;
|
|
114
|
+
}
|
|
115
|
+
function readFoirEnvelope(json) {
|
|
60
116
|
if (json.errors?.length) {
|
|
61
117
|
const msg = json.errors.map((e) => e.message).join("; ");
|
|
62
118
|
if (json.data) {
|
|
@@ -73,15 +129,196 @@ async function foirRequest(env, document, variables, options) {
|
|
|
73
129
|
return json.data;
|
|
74
130
|
}
|
|
75
131
|
|
|
132
|
+
// src/graphql/cdn-route.ts
|
|
133
|
+
var CDN_PATH = "/cdn/graphql";
|
|
134
|
+
function isCdnEligible(env, documentText, bearerToken) {
|
|
135
|
+
if (bearerToken) return false;
|
|
136
|
+
if (isFoirPreview(env)) return false;
|
|
137
|
+
const apiKey = resolveFoirApiKey(env);
|
|
138
|
+
if (!apiKey?.startsWith("pk_")) return false;
|
|
139
|
+
return !/\bmutation\b/.test(documentText);
|
|
140
|
+
}
|
|
141
|
+
function buildCdnUrl(endpoint, apiKey, hash, variables) {
|
|
142
|
+
const url = new URL(endpoint);
|
|
143
|
+
url.pathname = url.pathname.replace(/\/graphql\/?$/, "") + CDN_PATH;
|
|
144
|
+
url.searchParams.set("key", apiKey);
|
|
145
|
+
url.searchParams.set("extensions", JSON.stringify(persistedQueryExtension(hash)));
|
|
146
|
+
if (variables && Object.keys(variables).length > 0) {
|
|
147
|
+
url.searchParams.set("variables", JSON.stringify(variables));
|
|
148
|
+
}
|
|
149
|
+
return url.toString();
|
|
150
|
+
}
|
|
151
|
+
function persistedQueryExtension(hash) {
|
|
152
|
+
return { persistedQuery: { version: 1, sha256Hash: hash } };
|
|
153
|
+
}
|
|
154
|
+
async function documentHash(documentText) {
|
|
155
|
+
const digest = await crypto.subtle.digest(
|
|
156
|
+
"SHA-256",
|
|
157
|
+
new TextEncoder().encode(documentText)
|
|
158
|
+
);
|
|
159
|
+
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
160
|
+
}
|
|
161
|
+
var PERSISTED_QUERY_NOT_FOUND = "PERSISTED_QUERY_NOT_FOUND";
|
|
162
|
+
var NOT_SHAREABLE = "NOT_SHAREABLE";
|
|
163
|
+
|
|
164
|
+
// src/transport/retry.ts
|
|
165
|
+
var DEFAULT_ATTEMPTS = 3;
|
|
166
|
+
var DEFAULT_BASE_DELAY_MS = 200;
|
|
167
|
+
var DEFAULT_MAX_DELAY_MS = 2e4;
|
|
168
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
169
|
+
function foirRetryDelayMs(error, attempt, policy = {}) {
|
|
170
|
+
if (!(error instanceof FoirRequestError) || !error.retryable) return null;
|
|
171
|
+
const max = policy.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
172
|
+
const stated = error.retryAfterMs;
|
|
173
|
+
if (stated !== void 0) return Math.min(stated, max);
|
|
174
|
+
const base = policy.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
175
|
+
return Math.min(base * 2 ** (attempt - 1), max);
|
|
176
|
+
}
|
|
177
|
+
async function withFoirRetry(fn, policy = {}) {
|
|
178
|
+
const attempts = Math.max(1, policy.attempts ?? DEFAULT_ATTEMPTS);
|
|
179
|
+
const sleep = policy.sleep ?? defaultSleep;
|
|
180
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
181
|
+
try {
|
|
182
|
+
return await fn();
|
|
183
|
+
} catch (error) {
|
|
184
|
+
const delayMs = attempt >= attempts ? null : foirRetryDelayMs(error, attempt, policy);
|
|
185
|
+
if (delayMs === null) throw error;
|
|
186
|
+
policy.onRetry?.({ attempt, delayMs, error });
|
|
187
|
+
await sleep(delayMs);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/graphql/foir-request.ts
|
|
193
|
+
var FOIR_CDN_BASE = "https://cdn.foir.dev/";
|
|
194
|
+
function absolutiseFoirMediaUrl(url, cdnBase = FOIR_CDN_BASE) {
|
|
195
|
+
if (!url) return void 0;
|
|
196
|
+
if (/^https?:\/\//i.test(url)) return url;
|
|
197
|
+
return cdnBase + url.replace(/^\/+/, "");
|
|
198
|
+
}
|
|
199
|
+
async function foirRequest(env, document, variables, options) {
|
|
200
|
+
const endpoint = env.PLATFORM_API_URL;
|
|
201
|
+
const apiKey = resolveFoirApiKey(env);
|
|
202
|
+
if (!endpoint || !apiKey) {
|
|
203
|
+
throw new FoirRequestError(
|
|
204
|
+
"[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const headers = buildFoirHeaders({
|
|
208
|
+
apiKey,
|
|
209
|
+
draftChannel: isFoirPreview(env),
|
|
210
|
+
bearerToken: options?.bearerToken
|
|
211
|
+
});
|
|
212
|
+
const documentText = print2(document);
|
|
213
|
+
let res;
|
|
214
|
+
try {
|
|
215
|
+
if (isCdnEligible(env, documentText, options?.bearerToken)) {
|
|
216
|
+
const hash = await documentHash(documentText);
|
|
217
|
+
let shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
|
|
218
|
+
method: "GET",
|
|
219
|
+
headers
|
|
220
|
+
});
|
|
221
|
+
if (await isPersistedQueryMiss(shared)) {
|
|
222
|
+
shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers,
|
|
225
|
+
body: JSON.stringify({
|
|
226
|
+
query: documentText,
|
|
227
|
+
variables,
|
|
228
|
+
extensions: persistedQueryExtension(hash)
|
|
229
|
+
})
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (await isSharedRefusal(shared)) {
|
|
233
|
+
res = await fetch(endpoint, {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers,
|
|
236
|
+
body: JSON.stringify({ query: documentText, variables })
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
res = shared;
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
res = await fetch(endpoint, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers,
|
|
245
|
+
body: JSON.stringify({ query: documentText, variables })
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
} catch (err) {
|
|
249
|
+
throw new FoirRequestError("[foir] network error", { cause: err });
|
|
250
|
+
}
|
|
251
|
+
options?.onRateLimit?.(readFoirRateLimit(res.headers));
|
|
252
|
+
if (!res.ok) {
|
|
253
|
+
const errors = await readErrorEnvelope(res);
|
|
254
|
+
const first = errors?.[0]?.message;
|
|
255
|
+
throw new FoirRequestError(
|
|
256
|
+
first ? `[foir] HTTP ${res.status} ${res.statusText}: ${first}` : `[foir] HTTP ${res.status} ${res.statusText}`,
|
|
257
|
+
{ status: res.status, graphQLErrors: errors }
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const json = await res.json();
|
|
261
|
+
return readFoirEnvelope(json);
|
|
262
|
+
}
|
|
263
|
+
async function readErrorEnvelope(res) {
|
|
264
|
+
let errors;
|
|
265
|
+
try {
|
|
266
|
+
const body = await res.json();
|
|
267
|
+
errors = body.errors;
|
|
268
|
+
} catch {
|
|
269
|
+
errors = void 0;
|
|
270
|
+
}
|
|
271
|
+
const headerRetryMs = readFoirRetryAfterMs(res.headers);
|
|
272
|
+
if (headerRetryMs === void 0) return errors;
|
|
273
|
+
const seconds = Math.round(headerRetryMs / 1e3);
|
|
274
|
+
if (!errors?.length) {
|
|
275
|
+
return [
|
|
276
|
+
{
|
|
277
|
+
message: res.statusText || `HTTP ${res.status}`,
|
|
278
|
+
extensions: { code: "RATE_LIMITED", retryAfter: seconds }
|
|
279
|
+
}
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
if (errors[0].extensions?.retryAfter === void 0) {
|
|
283
|
+
errors[0] = {
|
|
284
|
+
...errors[0],
|
|
285
|
+
extensions: { ...errors[0].extensions, retryAfter: seconds }
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return errors;
|
|
289
|
+
}
|
|
290
|
+
async function isSharedRefusal(res) {
|
|
291
|
+
if (res.ok) return false;
|
|
292
|
+
try {
|
|
293
|
+
const body = await res.clone().json();
|
|
294
|
+
return body.errors?.some((e) => e.extensions?.code === NOT_SHAREABLE) ?? false;
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function isPersistedQueryMiss(res) {
|
|
300
|
+
try {
|
|
301
|
+
const body = await res.clone().json();
|
|
302
|
+
return body.errors?.some(
|
|
303
|
+
(e) => e.extensions?.code === PERSISTED_QUERY_NOT_FOUND
|
|
304
|
+
) ?? false;
|
|
305
|
+
} catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
76
310
|
// src/graphql/content-client.ts
|
|
77
311
|
function createFoirContentClient(config) {
|
|
78
312
|
return {
|
|
79
313
|
async request(document, variables, options) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
314
|
+
return withFoirRetry(async () => {
|
|
315
|
+
const bound = config.getToken ? await config.getToken() : null;
|
|
316
|
+
return foirRequest(config.env, document, variables, {
|
|
317
|
+
...options,
|
|
318
|
+
bearerToken: options?.bearerToken ?? bound ?? void 0,
|
|
319
|
+
onRateLimit: options?.onRateLimit ?? config.onRateLimit
|
|
320
|
+
});
|
|
321
|
+
}, config.retry);
|
|
85
322
|
}
|
|
86
323
|
};
|
|
87
324
|
}
|
|
@@ -91,5 +328,12 @@ export {
|
|
|
91
328
|
absolutiseFoirMediaUrl,
|
|
92
329
|
createFoirContentClient,
|
|
93
330
|
foirRequest,
|
|
94
|
-
|
|
331
|
+
foirRetryDelayMs,
|
|
332
|
+
isFoirForbidden,
|
|
333
|
+
isFoirNotFound,
|
|
334
|
+
isFoirPreview,
|
|
335
|
+
isFoirRateLimited,
|
|
336
|
+
isFoirUnauthenticated,
|
|
337
|
+
readFoirRateLimit,
|
|
338
|
+
withFoirRetry
|
|
95
339
|
};
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { GraphQLSchema } from 'graphql';
|
|
2
|
-
import { B as BlockPluginConfig } from '../shared-DkWlEd2Z.js';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* `@foir/sdk/codegen/block-registry` — a graphql-codegen plugin.
|
|
6
3
|
*
|
|
@@ -16,12 +13,11 @@ import { B as BlockPluginConfig } from '../shared-DkWlEd2Z.js';
|
|
|
16
13
|
* types from the codegen output (`./graphql`) and `BlockComponent` from
|
|
17
14
|
* `@foir/sdk/content`, so it pairs directly with `createContentRenderer`.
|
|
18
15
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
import type { GraphQLSchema } from 'graphql';
|
|
17
|
+
import { type BlockPluginConfig } from './shared';
|
|
18
|
+
export interface BlockRegistryConfig extends BlockPluginConfig {
|
|
21
19
|
/** Name of the generated registry type. Default `BlockRegistry`. */
|
|
22
20
|
registryName?: string;
|
|
23
21
|
}
|
|
24
22
|
/** graphql-codegen plugin entrypoint. */
|
|
25
|
-
declare const plugin: (schema: GraphQLSchema, _documents: unknown, config?: BlockRegistryConfig) => string;
|
|
26
|
-
|
|
27
|
-
export { type BlockRegistryConfig, plugin };
|
|
23
|
+
export declare const plugin: (schema: GraphQLSchema, _documents: unknown, config?: BlockRegistryConfig) => string;
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { GraphQLSchema } from 'graphql';
|
|
2
|
-
import { B as BlockPluginConfig } from '../shared-DkWlEd2Z.js';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* `@foir/sdk/codegen/block-scaffold` — a one-shot graphql-codegen plugin.
|
|
6
3
|
*
|
|
@@ -13,8 +10,9 @@ import { B as BlockPluginConfig } from '../shared-DkWlEd2Z.js';
|
|
|
13
10
|
* in. Adding a new block type later surfaces as a compile error on
|
|
14
11
|
* `blockRegistry` (via the regenerated `BlockRegistry` type), not here.
|
|
15
12
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
import type { GraphQLSchema } from 'graphql';
|
|
14
|
+
import { type BlockPluginConfig } from './shared';
|
|
15
|
+
export interface BlockScaffoldConfig extends BlockPluginConfig {
|
|
18
16
|
/** Import path for the generated registry type. Default `./block-registry`. */
|
|
19
17
|
registryImport?: string;
|
|
20
18
|
/** Render-context type name threaded to every component. Default `unknown`. */
|
|
@@ -23,6 +21,4 @@ interface BlockScaffoldConfig extends BlockPluginConfig {
|
|
|
23
21
|
ctxImport?: string;
|
|
24
22
|
}
|
|
25
23
|
/** graphql-codegen plugin entrypoint. */
|
|
26
|
-
declare const plugin: (schema: GraphQLSchema, _documents: unknown, config?: BlockScaffoldConfig) => string;
|
|
27
|
-
|
|
28
|
-
export { type BlockScaffoldConfig, plugin };
|
|
24
|
+
export declare const plugin: (schema: GraphQLSchema, _documents: unknown, config?: BlockScaffoldConfig) => string;
|
package/dist/codegen/index.d.ts
CHANGED
|
@@ -1,65 +1,11 @@
|
|
|
1
|
-
import { CodegenConfig } from '@graphql-codegen/cli';
|
|
2
|
-
export { B as BlockPluginConfig } from '../shared-DkWlEd2Z.js';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
* `@foir/sdk/codegen` —
|
|
2
|
+
* `@foir/sdk/codegen` — Foir graphql-codegen preset + block-registry plugin.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
* AND wires the block-registry plugin so the exhaustive `BlockRegistry` type is
|
|
12
|
-
* regenerated alongside the typed documents.
|
|
13
|
-
*
|
|
14
|
-
* `foirScaffold()` is the separate one-shot that writes the ContentRenderer
|
|
15
|
-
* shell (run once; the dev owns the output).
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
interface FoirCodegenOptions {
|
|
19
|
-
/** Foir public API GraphQL endpoint. */
|
|
20
|
-
apiUrl: string;
|
|
21
|
-
/**
|
|
22
|
-
* Public (`pk_`) API key. Reads the published schema. Used as the fallback
|
|
23
|
-
* when no preview key is set.
|
|
24
|
-
*/
|
|
25
|
-
apiKey?: string;
|
|
26
|
-
/**
|
|
27
|
-
* Preview (`sk_`) key with `drafts:read`. Reads the DRAFT schema channel so
|
|
28
|
-
* freshly pushed (un-promoted) model changes are visible to codegen.
|
|
29
|
-
* Strongly preferred — a `pk_` key only sees the published schema.
|
|
30
|
-
*/
|
|
31
|
-
previewKey?: string;
|
|
32
|
-
/** Output directory for the generated client types. Default `./app/gql/foir/`. */
|
|
33
|
-
outputDir?: string;
|
|
34
|
-
/** Operation document glob(s). Default `app/graphql/foir/**\/*.graphql`. */
|
|
35
|
-
documents?: string | string[];
|
|
36
|
-
/** Block union name(s) for the registry. Default `['Block']`. */
|
|
37
|
-
blockUnions?: string[];
|
|
38
|
-
/** Generated registry file path. Default `<outputDir>block-registry.ts`. */
|
|
39
|
-
registryFile?: string;
|
|
40
|
-
/** Extra/override custom scalars merged onto the Foir defaults. */
|
|
41
|
-
scalars?: Record<string, unknown>;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Build the continuous Foir codegen config: typed documents + the exhaustive
|
|
45
|
-
* block registry. Drop it into `codegen.ts` and run `graphql-codegen`.
|
|
46
|
-
*/
|
|
47
|
-
declare function foirCodegen(options: FoirCodegenOptions): CodegenConfig;
|
|
48
|
-
interface FoirScaffoldOptions extends FoirCodegenOptions {
|
|
49
|
-
/** Output path for the scaffold. Default `<outputDir>ContentRenderer.tsx`. */
|
|
50
|
-
scaffoldFile?: string;
|
|
51
|
-
/** Render-context type threaded to every block component. Default `unknown`. */
|
|
52
|
-
ctxType?: string;
|
|
53
|
-
/** Optional ctx-type import line, e.g. `import type { Ctx } from '~/ctx'`. */
|
|
54
|
-
ctxImport?: string;
|
|
55
|
-
/** Import path for the generated registry type. Default `./block-registry`. */
|
|
56
|
-
registryImport?: string;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Build the one-shot scaffold config — writes a ContentRenderer shell with a
|
|
60
|
-
* typed stub per block type. Run once (`graphql-codegen --config scaffold.ts`),
|
|
61
|
-
* then own and implement the output.
|
|
4
|
+
* Build-time only. `@graphql-codegen/cli` + `@graphql-codegen/client-preset`
|
|
5
|
+
* are optional peer dependencies (the consumer's devDeps). The block-registry
|
|
6
|
+
* and block-scaffold plugins are wired by the preset and also available at
|
|
7
|
+
* `@foir/sdk/codegen/block-registry` / `@foir/sdk/codegen/block-scaffold`.
|
|
62
8
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
export
|
|
9
|
+
export { foirCodegen, foirScaffold } from './preset';
|
|
10
|
+
export type { FoirCodegenOptions, FoirScaffoldOptions } from './preset';
|
|
11
|
+
export type { BlockPluginConfig } from './shared';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@foir/sdk/codegen` — the Foir graphql-codegen preset.
|
|
3
|
+
*
|
|
4
|
+
* `foirCodegen()` returns a complete `@graphql-codegen/client-preset` config so
|
|
5
|
+
* a consumer's `codegen.ts` is ~3 lines instead of the ~109 every storefront
|
|
6
|
+
* hand-copies. It standardizes the Foir conventions (custom scalars,
|
|
7
|
+
* `enumValues: 'keep'`, the `X-Foir-Schema-Channel: draft` introspection header)
|
|
8
|
+
* AND wires the block-registry plugin so the exhaustive `BlockRegistry` type is
|
|
9
|
+
* regenerated alongside the typed documents.
|
|
10
|
+
*
|
|
11
|
+
* `foirScaffold()` is the separate one-shot that writes the ContentRenderer
|
|
12
|
+
* shell (run once; the dev owns the output).
|
|
13
|
+
*/
|
|
14
|
+
import type { CodegenConfig } from '@graphql-codegen/cli';
|
|
15
|
+
export interface FoirCodegenOptions {
|
|
16
|
+
/** Foir public API GraphQL endpoint. */
|
|
17
|
+
apiUrl: string;
|
|
18
|
+
/**
|
|
19
|
+
* Public (`pk_`) API key. Reads the published schema. Used as the fallback
|
|
20
|
+
* when no preview key is set.
|
|
21
|
+
*/
|
|
22
|
+
apiKey?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Preview (`sk_`) key with `drafts:read`. Reads the DRAFT schema channel so
|
|
25
|
+
* freshly pushed (un-promoted) model changes are visible to codegen.
|
|
26
|
+
* Strongly preferred — a `pk_` key only sees the published schema.
|
|
27
|
+
*/
|
|
28
|
+
previewKey?: string;
|
|
29
|
+
/** Output directory for the generated client types. Default `./app/gql/foir/`. */
|
|
30
|
+
outputDir?: string;
|
|
31
|
+
/** Operation document glob(s). Default `app/graphql/foir/**\/*.graphql`. */
|
|
32
|
+
documents?: string | string[];
|
|
33
|
+
/** Block union name(s) for the registry. Default `['Block']`. */
|
|
34
|
+
blockUnions?: string[];
|
|
35
|
+
/** Generated registry file path. Default `<outputDir>block-registry.ts`. */
|
|
36
|
+
registryFile?: string;
|
|
37
|
+
/** Extra/override custom scalars merged onto the Foir defaults. */
|
|
38
|
+
scalars?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build the continuous Foir codegen config: typed documents + the exhaustive
|
|
42
|
+
* block registry. Drop it into `codegen.ts` and run `graphql-codegen`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function foirCodegen(options: FoirCodegenOptions): CodegenConfig;
|
|
45
|
+
export interface FoirScaffoldOptions extends FoirCodegenOptions {
|
|
46
|
+
/** Output path for the scaffold. Default `<outputDir>ContentRenderer.tsx`. */
|
|
47
|
+
scaffoldFile?: string;
|
|
48
|
+
/** Render-context type threaded to every block component. Default `unknown`. */
|
|
49
|
+
ctxType?: string;
|
|
50
|
+
/** Optional ctx-type import line, e.g. `import type { Ctx } from '~/ctx'`. */
|
|
51
|
+
ctxImport?: string;
|
|
52
|
+
/** Import path for the generated registry type. Default `./block-registry`. */
|
|
53
|
+
registryImport?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build the one-shot scaffold config — writes a ContentRenderer shell with a
|
|
57
|
+
* typed stub per block type. Run once (`graphql-codegen --config scaffold.ts`),
|
|
58
|
+
* then own and implement the output.
|
|
59
|
+
*/
|
|
60
|
+
export declare function foirScaffold(options: FoirScaffoldOptions): CodegenConfig;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared introspection helpers for the Foir codegen plugins.
|
|
3
|
+
*/
|
|
4
|
+
import { type GraphQLSchema } from 'graphql';
|
|
5
|
+
export interface BlockPluginConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Union type name(s) to collect block members from. Defaults to `['Block']`
|
|
8
|
+
* (the canonical Foir block union). A project with a distinct content-field
|
|
9
|
+
* segment union lists it here too.
|
|
10
|
+
*/
|
|
11
|
+
unions?: string[];
|
|
12
|
+
/** Import path for the codegen-generated types. Default `./graphql`. */
|
|
13
|
+
graphqlImport?: string;
|
|
14
|
+
/** Import path for the SDK content primitives. Default `@foir/sdk/content`. */
|
|
15
|
+
sdkImport?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_UNIONS: string[];
|
|
18
|
+
export declare const DEFAULT_GRAPHQL_IMPORT = "./graphql";
|
|
19
|
+
export declare const DEFAULT_SDK_IMPORT = "@foir/sdk/content";
|
|
20
|
+
/**
|
|
21
|
+
* Collect the deduped, sorted set of object-type member names across the named
|
|
22
|
+
* union(s). Throws when none of the unions resolve to members — that almost
|
|
23
|
+
* always means a misconfigured union name or a schema that hasn't been pushed.
|
|
24
|
+
*/
|
|
25
|
+
export declare function collectBlockMembers(schema: GraphQLSchema, unionNames: string[]): string[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EditorProvider - React context provider for Foir editor iframes.
|
|
3
|
+
*
|
|
4
|
+
* Wraps your editor root to handle postMessage lifecycle, theme sync,
|
|
5
|
+
* and provide the platform client via context.
|
|
6
|
+
*/
|
|
7
|
+
import { type ReactNode } from 'react';
|
|
8
|
+
import type { EditorInit, EditorContextValue, LocaleContext } from '../types';
|
|
9
|
+
export declare const EditorContext: import("react").Context<EditorContextValue | null>;
|
|
10
|
+
export interface EditorProviderProps {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Parent window origin for postMessage validation.
|
|
14
|
+
* Must be an explicit origin (e.g., 'https://admin.example.com').
|
|
15
|
+
* Falls back to VITE_PARENT_ORIGIN or NEXT_PUBLIC_PARENT_ORIGIN env var.
|
|
16
|
+
*/
|
|
17
|
+
parentOrigin?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Platform API URL for GraphQL requests.
|
|
20
|
+
* Falls back to VITE_API_URL or NEXT_PUBLIC_API_URL env var.
|
|
21
|
+
*/
|
|
22
|
+
apiUrl?: string;
|
|
23
|
+
onInit?: (init: EditorInit) => void;
|
|
24
|
+
onValuesChanged?: (values: Record<string, unknown>) => void;
|
|
25
|
+
onVariantChanged?: (variantKey: string, values: Record<string, unknown>) => void;
|
|
26
|
+
onSaveResult?: (success: boolean, error?: string, versionId?: string) => void;
|
|
27
|
+
onPublishResult?: (success: boolean, error?: string) => void;
|
|
28
|
+
onThemeChanged?: (theme: 'light' | 'dark') => void;
|
|
29
|
+
onSelectionChanged?: (keys: string[], fields: Array<{
|
|
30
|
+
key: string;
|
|
31
|
+
label: string;
|
|
32
|
+
type: string;
|
|
33
|
+
}>) => void;
|
|
34
|
+
onLocaleChanged?: (locale: LocaleContext) => void;
|
|
35
|
+
}
|
|
36
|
+
export declare function EditorProvider({ children, parentOrigin: parentOriginProp, apiUrl: apiUrlProp, onInit, onValuesChanged, onVariantChanged, onSaveResult, onPublishResult, onThemeChanged, onSelectionChanged, onLocaleChanged, }: EditorProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createContentRenderer — the generic content-dispatch primitive.
|
|
3
|
+
*
|
|
4
|
+
* It does NOT know any concrete block types. Given a registry that maps each
|
|
5
|
+
* block `__typename` to a component, it renders a `flexible` field's block
|
|
6
|
+
* array: looks each block up by `__typename`, renders unknown types as null,
|
|
7
|
+
* keys by the block `_id`, assigns LCP `priority` to the first block that
|
|
8
|
+
* actually renders, and threads a consumer-defined `Ctx` to every component.
|
|
9
|
+
*
|
|
10
|
+
* The "switch" is a typed object lookup. Exhaustiveness is enforced where the
|
|
11
|
+
* registry is BUILT — against the exhaustive `BlockRegistry` type the codegen
|
|
12
|
+
* plugin (EID-92) generates per project, so omitting a block type is a compile
|
|
13
|
+
* error there, not a runtime surprise here. The consumer can supply different
|
|
14
|
+
* registries per surface (e.g. web vs email).
|
|
15
|
+
*/
|
|
16
|
+
import { type ReactNode } from 'react';
|
|
17
|
+
/** Minimal shape every block in a `flexible` field carries. */
|
|
18
|
+
export interface BlockLike {
|
|
19
|
+
__typename: string;
|
|
20
|
+
_id?: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** Props a block component receives from the content renderer. */
|
|
23
|
+
export interface BlockComponentProps<TBlock, TCtx = unknown> {
|
|
24
|
+
block: TBlock;
|
|
25
|
+
/** Consumer-defined render context (host product, locale, etc.). */
|
|
26
|
+
ctx: TCtx;
|
|
27
|
+
/** True only for the first block that renders — the LCP candidate. */
|
|
28
|
+
priority: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** A component that renders one block type. */
|
|
31
|
+
export type BlockComponent<TBlock, TCtx = unknown> = (props: BlockComponentProps<TBlock, TCtx>) => ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* A registry mapping `__typename` → block component. In practice this is the
|
|
34
|
+
* exhaustive `BlockRegistry` type the codegen plugin emits for the project;
|
|
35
|
+
* here it's the structural lower bound the renderer needs.
|
|
36
|
+
*/
|
|
37
|
+
export type BlockRegistry<TCtx = unknown> = Record<string, BlockComponent<any, TCtx>>;
|
|
38
|
+
export interface ContentRendererProps<TCtx> {
|
|
39
|
+
blocks: readonly BlockLike[] | null | undefined;
|
|
40
|
+
/** Consumer-defined context passed to every block component. */
|
|
41
|
+
ctx: TCtx;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build a `<ContentRenderer>` bound to a block registry. Returns a component
|
|
45
|
+
* that takes the `flexible` field's `blocks` array plus a `ctx`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createContentRenderer<TCtx = unknown>(registry: BlockRegistry<TCtx>): (props: ContentRendererProps<TCtx>) => ReactNode;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@foir/sdk/content` — generic content-rendering primitives.
|
|
3
|
+
*
|
|
4
|
+
* Ships ONLY the framework-agnostic primitives: the block-dispatch renderer
|
|
5
|
+
* and a Lexical richtext renderer with injected typography tokens. Concrete
|
|
6
|
+
* block components, host/Shopify context, and layout systems stay app-side.
|
|
7
|
+
* `react` is a peer dependency.
|
|
8
|
+
*/
|
|
9
|
+
export { createContentRenderer } from './content-renderer';
|
|
10
|
+
export type { BlockLike, BlockComponent, BlockComponentProps, BlockRegistry, ContentRendererProps, } from './content-renderer';
|
|
11
|
+
export { RichTextRenderer, RichTextTypographyProvider, useRichTextTypography, } from './richtext';
|
|
12
|
+
export type { RichTextRendererProps, TypographyToken, } from './richtext';
|