@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.
- package/CHANGELOG.md +1126 -0
- package/README.md +94 -0
- package/dist/auth/auth.d.ts +243 -0
- package/dist/auth/hosted.d.ts +40 -0
- package/dist/auth/index.d.ts +21 -0
- package/dist/auth/operations.d.ts +144 -0
- package/dist/auth/ops.d.ts +99 -0
- package/dist/auth/session-client.d.ts +76 -0
- package/dist/auth-session.js +137 -0
- package/dist/auth.js +973 -92
- package/dist/chunk-IGNLWT3H.js +35 -0
- package/dist/chunk-OHYVKTGB.js +45 -0
- package/dist/chunk-TAL5H6VA.js +39 -0
- package/dist/client/platform-client.d.ts +29 -0
- package/dist/client.js +275 -37
- package/dist/codegen/block-registry.d.ts +13 -23
- package/dist/codegen/block-registry.js +3 -31
- package/dist/codegen/block-scaffold.d.ts +12 -25
- package/dist/codegen/block-scaffold.js +3 -41
- package/dist/codegen/index.d.ts +12 -61
- package/dist/codegen/index.js +7 -11
- package/dist/codegen/item-registry.d.ts +28 -0
- package/dist/codegen/item-registry.js +7 -0
- package/dist/codegen/item-scaffold.d.ts +29 -0
- package/dist/codegen/item-scaffold.js +7 -0
- package/dist/codegen/preset.d.ts +67 -0
- package/dist/codegen/shared.d.ts +36 -0
- package/dist/components/EditorProvider.d.ts +36 -0
- package/dist/content/content-renderer.d.ts +113 -0
- package/dist/content/index.d.ts +18 -0
- package/dist/content/richtext.d.ts +118 -0
- package/dist/content.js +227 -80
- package/dist/data/builtins.d.ts +506 -0
- package/dist/data/client.d.ts +166 -0
- package/dist/data/document.d.ts +251 -0
- package/dist/data/index.d.ts +73 -0
- package/dist/data/manifest.d.ts +424 -0
- package/dist/data/schema.d.ts +313 -0
- package/dist/data/selection.d.ts +524 -0
- package/dist/data/subjects.d.ts +221 -0
- package/dist/data/subscription-document.d.ts +75 -0
- package/dist/data/subscriptions.d.ts +181 -0
- package/dist/data/transport.d.ts +325 -0
- package/dist/data/ws-transport.d.ts +197 -0
- package/dist/data.js +1683 -0
- package/dist/graphql/cdn-route.d.ts +26 -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 +18 -0
- package/dist/media/media.d.ts +139 -0
- package/dist/media/screen-capture.d.ts +81 -0
- package/dist/media/useProgressiveLoad.d.ts +23 -0
- package/dist/media.js +102 -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 +763 -78
- 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 +892 -78
- package/dist/react/FoirFeedback.d.ts +62 -0
- 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/feedback-notes.d.ts +101 -0
- package/dist/react/index.d.ts +25 -0
- package/dist/react/localStorageAdapter.d.ts +10 -0
- package/dist/react.js +1373 -91
- package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
- package/dist/realtime.js +1 -1
- package/dist/server/callback-client.d.ts +92 -0
- package/dist/server/events-client.d.ts +279 -0
- package/dist/server/index.d.ts +12 -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/server.js +298 -0
- package/dist/transport/foir-http.d.ts +234 -0
- package/dist/transport/persisted-query.d.ts +68 -0
- package/dist/transport/retry.d.ts +61 -0
- package/dist/types/index.d.ts +124 -0
- package/package.json +35 -13
- package/dist/auth.d.ts +0 -382
- package/dist/chunk-ADO5FJNM.js +0 -30
- 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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_GRAPHQL_IMPORT,
|
|
3
|
+
DEFAULT_SDK_IMPORT,
|
|
4
|
+
collectItemMembers
|
|
5
|
+
} from "./chunk-TAL5H6VA.js";
|
|
6
|
+
|
|
7
|
+
// src/codegen/item-registry.ts
|
|
8
|
+
var plugin = (schema, _documents, config = {}) => {
|
|
9
|
+
const members = collectItemMembers(schema, config.unions);
|
|
10
|
+
const graphqlImport = config.graphqlImport ?? DEFAULT_GRAPHQL_IMPORT;
|
|
11
|
+
const sdkImport = config.sdkImport ?? DEFAULT_SDK_IMPORT;
|
|
12
|
+
const registryName = config.registryName ?? "ItemRegistry";
|
|
13
|
+
return [
|
|
14
|
+
"/* eslint-disable */",
|
|
15
|
+
"// Generated by @foir/sdk/codegen/item-registry. Do not edit by hand.",
|
|
16
|
+
"// Regenerated on every codegen run \u2014 omitting an item type is a compile error.",
|
|
17
|
+
`import type { ItemComponent } from '${sdkImport}';`,
|
|
18
|
+
`import type {`,
|
|
19
|
+
...members.map((m) => ` ${m},`),
|
|
20
|
+
`} from '${graphqlImport}';`,
|
|
21
|
+
"",
|
|
22
|
+
`/**`,
|
|
23
|
+
` * Exhaustive map of every item \`__typename\` in the project schema to the`,
|
|
24
|
+
` * component that renders it. Pass an instance to \`createContentRenderer\`.`,
|
|
25
|
+
` */`,
|
|
26
|
+
`export type ${registryName}<Ctx = unknown> = {`,
|
|
27
|
+
...members.map((m) => ` ${m}: ItemComponent<${m}, Ctx>;`),
|
|
28
|
+
`};`,
|
|
29
|
+
""
|
|
30
|
+
].join("\n");
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
plugin
|
|
35
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_GRAPHQL_IMPORT,
|
|
3
|
+
DEFAULT_SDK_IMPORT,
|
|
4
|
+
collectItemMembers
|
|
5
|
+
} from "./chunk-TAL5H6VA.js";
|
|
6
|
+
|
|
7
|
+
// src/codegen/item-scaffold.ts
|
|
8
|
+
var plugin = (schema, _documents, config = {}) => {
|
|
9
|
+
const members = collectItemMembers(schema, config.unions);
|
|
10
|
+
const graphqlImport = config.graphqlImport ?? DEFAULT_GRAPHQL_IMPORT;
|
|
11
|
+
const sdkImport = config.sdkImport ?? DEFAULT_SDK_IMPORT;
|
|
12
|
+
const registryImport = config.registryImport ?? "./item-registry";
|
|
13
|
+
const ctx = config.ctxType ?? "unknown";
|
|
14
|
+
const stub = (m) => [
|
|
15
|
+
`function ${m}View(props: ItemComponentProps<${m}, ${ctx}>) {`,
|
|
16
|
+
` // TODO: render ${m} from props.item (props.ctx, props.priority available)`,
|
|
17
|
+
` void props;`,
|
|
18
|
+
` return null;`,
|
|
19
|
+
`}`
|
|
20
|
+
].join("\n");
|
|
21
|
+
return [
|
|
22
|
+
"/* eslint-disable */",
|
|
23
|
+
"// Scaffolded once by @foir/sdk/codegen/item-scaffold \u2014 you own this file.",
|
|
24
|
+
"// Implement each stub; the build stays green until you do.",
|
|
25
|
+
`import { createContentRenderer, type ItemComponentProps } from '${sdkImport}';`,
|
|
26
|
+
`import type { ItemRegistry } from '${registryImport}';`,
|
|
27
|
+
`import type {`,
|
|
28
|
+
...members.map((m) => ` ${m},`),
|
|
29
|
+
`} from '${graphqlImport}';`,
|
|
30
|
+
...config.ctxImport ? ["", config.ctxImport] : [],
|
|
31
|
+
"",
|
|
32
|
+
...members.map(stub),
|
|
33
|
+
"",
|
|
34
|
+
`export const itemRegistry: ItemRegistry<${ctx}> = {`,
|
|
35
|
+
...members.map((m) => ` ${m}: ${m}View,`),
|
|
36
|
+
`};`,
|
|
37
|
+
"",
|
|
38
|
+
`export const ContentRenderer = createContentRenderer<${ctx}>(itemRegistry);`,
|
|
39
|
+
""
|
|
40
|
+
].join("\n");
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
plugin
|
|
45
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/codegen/shared.ts
|
|
2
|
+
import { isUnionType } from "graphql";
|
|
3
|
+
var DEFAULT_GRAPHQL_IMPORT = "./graphql";
|
|
4
|
+
var DEFAULT_SDK_IMPORT = "@foir/sdk/content";
|
|
5
|
+
var PASSAGE = "Passage";
|
|
6
|
+
function collectItemMembers(schema, unionNames) {
|
|
7
|
+
const members = /* @__PURE__ */ new Set();
|
|
8
|
+
const missing = [];
|
|
9
|
+
if (unionNames && unionNames.length > 0) {
|
|
10
|
+
for (const name of unionNames) {
|
|
11
|
+
const type = schema.getType(name);
|
|
12
|
+
if (!type || !isUnionType(type)) {
|
|
13
|
+
missing.push(name);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
for (const member of type.getTypes()) members.add(member.name);
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
for (const type of Object.values(schema.getTypeMap())) {
|
|
20
|
+
if (!isUnionType(type)) continue;
|
|
21
|
+
const names = type.getTypes().map((m) => m.name);
|
|
22
|
+
if (!names.includes(PASSAGE)) continue;
|
|
23
|
+
for (const name of names) members.add(name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
members.delete(PASSAGE);
|
|
27
|
+
if (members.size === 0) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`[foir codegen] no item members found. ` + (unionNames && unionNames.length > 0 ? `Looked for union(s): ${unionNames.join(", ")}` + (missing.length ? ` (not found or not a union: ${missing.join(", ")})` : "") : `No prose entries union (a union containing Passage) exists in the schema`) + `. Run \`foir push\`, then confirm the schema has a prose field that places models \u2014 a prose field's entries union is named by its materialised member set.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return [...members].sort();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
DEFAULT_GRAPHQL_IMPORT,
|
|
37
|
+
DEFAULT_SDK_IMPORT,
|
|
38
|
+
collectItemMembers
|
|
39
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch-based GraphQL client for the EIDE platform API.
|
|
3
|
+
*
|
|
4
|
+
* Zero dependencies — uses the native Fetch API.
|
|
5
|
+
*/
|
|
6
|
+
import type { PlatformClient, PlatformClientOptions } from '../types';
|
|
7
|
+
/** Error thrown when a platform API request fails. */
|
|
8
|
+
export declare class PlatformClientError extends Error {
|
|
9
|
+
status?: number | undefined;
|
|
10
|
+
response?: unknown | undefined;
|
|
11
|
+
constructor(message: string, status?: number | undefined, response?: unknown | undefined);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a PlatformClient instance.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const client = createPlatformClient({
|
|
19
|
+
* apiUrl: 'https://api.example.com/graphql',
|
|
20
|
+
* apiKey: 'sk_test_...',
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* const { data } = await client.query(
|
|
24
|
+
* `query GetProduct($id: ID!) { product(id: $id) { title } }`,
|
|
25
|
+
* { id: '123' }
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function createPlatformClient(options: PlatformClientOptions): PlatformClient;
|
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,190 @@ async function foirRequest(env, document, variables, options) {
|
|
|
73
129
|
return json.data;
|
|
74
130
|
}
|
|
75
131
|
|
|
132
|
+
// src/transport/persisted-query.ts
|
|
133
|
+
var FOIR_CDN_GRAPHQL_PATH = "/cdn/graphql";
|
|
134
|
+
function persistedQueryExtension(hash) {
|
|
135
|
+
return { persistedQuery: { version: 1, sha256Hash: hash } };
|
|
136
|
+
}
|
|
137
|
+
function buildCdnUrl(endpoint, apiKey, hash, variables) {
|
|
138
|
+
const url = new URL(endpoint);
|
|
139
|
+
url.pathname = url.pathname.replace(/\/graphql\/?$/, "") + FOIR_CDN_GRAPHQL_PATH;
|
|
140
|
+
url.searchParams.set("key", apiKey);
|
|
141
|
+
url.searchParams.set("extensions", JSON.stringify(persistedQueryExtension(hash)));
|
|
142
|
+
if (variables && Object.keys(variables).length > 0) {
|
|
143
|
+
url.searchParams.set("variables", JSON.stringify(variables));
|
|
144
|
+
}
|
|
145
|
+
return url.toString();
|
|
146
|
+
}
|
|
147
|
+
async function documentHash(documentText) {
|
|
148
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(documentText));
|
|
149
|
+
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
150
|
+
}
|
|
151
|
+
var PERSISTED_QUERY_NOT_FOUND = "PERSISTED_QUERY_NOT_FOUND";
|
|
152
|
+
var NOT_SHAREABLE = "NOT_SHAREABLE";
|
|
153
|
+
async function hasFoirErrorCode(res, ...codes) {
|
|
154
|
+
try {
|
|
155
|
+
const body = await res.clone().json();
|
|
156
|
+
return body.errors?.some((e) => e.extensions?.code !== void 0 && codes.includes(e.extensions.code)) ?? false;
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/graphql/cdn-route.ts
|
|
163
|
+
function isCdnEligible(env, documentText, bearerToken) {
|
|
164
|
+
if (bearerToken) return false;
|
|
165
|
+
if (isFoirPreview(env)) return false;
|
|
166
|
+
const apiKey = resolveFoirApiKey(env);
|
|
167
|
+
if (!apiKey?.startsWith("pk_")) return false;
|
|
168
|
+
return !/\bmutation\b/.test(documentText);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/transport/retry.ts
|
|
172
|
+
var DEFAULT_ATTEMPTS = 3;
|
|
173
|
+
var DEFAULT_BASE_DELAY_MS = 200;
|
|
174
|
+
var DEFAULT_MAX_DELAY_MS = 2e4;
|
|
175
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
176
|
+
function foirRetryDelayMs(error, attempt, policy = {}) {
|
|
177
|
+
if (!(error instanceof FoirRequestError) || !error.retryable) return null;
|
|
178
|
+
const max = policy.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
179
|
+
const stated = error.retryAfterMs;
|
|
180
|
+
if (stated !== void 0) return Math.min(stated, max);
|
|
181
|
+
const base = policy.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
182
|
+
return Math.min(base * 2 ** (attempt - 1), max);
|
|
183
|
+
}
|
|
184
|
+
async function withFoirRetry(fn, policy = {}) {
|
|
185
|
+
const attempts = Math.max(1, policy.attempts ?? DEFAULT_ATTEMPTS);
|
|
186
|
+
const sleep = policy.sleep ?? defaultSleep;
|
|
187
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
188
|
+
try {
|
|
189
|
+
return await fn();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const delayMs = attempt >= attempts ? null : foirRetryDelayMs(error, attempt, policy);
|
|
192
|
+
if (delayMs === null) throw error;
|
|
193
|
+
policy.onRetry?.({ attempt, delayMs, error });
|
|
194
|
+
await sleep(delayMs);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/graphql/foir-request.ts
|
|
200
|
+
var FOIR_CDN_BASE = "https://cdn.foir.dev/";
|
|
201
|
+
function absolutiseFoirMediaUrl(url, cdnBase = FOIR_CDN_BASE) {
|
|
202
|
+
if (!url) return void 0;
|
|
203
|
+
if (/^https?:\/\//i.test(url)) return url;
|
|
204
|
+
return cdnBase + url.replace(/^\/+/, "");
|
|
205
|
+
}
|
|
206
|
+
async function foirRequest(env, document, variables, options) {
|
|
207
|
+
const endpoint = env.PLATFORM_API_URL;
|
|
208
|
+
const apiKey = resolveFoirApiKey(env);
|
|
209
|
+
if (!endpoint || !apiKey) {
|
|
210
|
+
throw new FoirRequestError(
|
|
211
|
+
"[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const headers = buildFoirHeaders({
|
|
215
|
+
apiKey,
|
|
216
|
+
draftChannel: isFoirPreview(env),
|
|
217
|
+
bearerToken: options?.bearerToken
|
|
218
|
+
});
|
|
219
|
+
const documentText = print2(document);
|
|
220
|
+
let res;
|
|
221
|
+
try {
|
|
222
|
+
if (isCdnEligible(env, documentText, options?.bearerToken)) {
|
|
223
|
+
const hash = await documentHash(documentText);
|
|
224
|
+
let shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
|
|
225
|
+
method: "GET",
|
|
226
|
+
headers
|
|
227
|
+
});
|
|
228
|
+
if (await isPersistedQueryMiss(shared)) {
|
|
229
|
+
shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers,
|
|
232
|
+
body: JSON.stringify({
|
|
233
|
+
query: documentText,
|
|
234
|
+
variables,
|
|
235
|
+
extensions: persistedQueryExtension(hash)
|
|
236
|
+
})
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (await isSharedRefusal(shared)) {
|
|
240
|
+
res = await fetch(endpoint, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers,
|
|
243
|
+
body: JSON.stringify({ query: documentText, variables })
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
res = shared;
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
res = await fetch(endpoint, {
|
|
250
|
+
method: "POST",
|
|
251
|
+
headers,
|
|
252
|
+
body: JSON.stringify({ query: documentText, variables })
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
throw new FoirRequestError("[foir] network error", { cause: err });
|
|
257
|
+
}
|
|
258
|
+
options?.onRateLimit?.(readFoirRateLimit(res.headers));
|
|
259
|
+
if (!res.ok) {
|
|
260
|
+
const errors = await readErrorEnvelope(res);
|
|
261
|
+
const first = errors?.[0]?.message;
|
|
262
|
+
throw new FoirRequestError(
|
|
263
|
+
first ? `[foir] HTTP ${res.status} ${res.statusText}: ${first}` : `[foir] HTTP ${res.status} ${res.statusText}`,
|
|
264
|
+
{ status: res.status, graphQLErrors: errors }
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const json = await res.json();
|
|
268
|
+
return readFoirEnvelope(json);
|
|
269
|
+
}
|
|
270
|
+
async function readErrorEnvelope(res) {
|
|
271
|
+
let errors;
|
|
272
|
+
try {
|
|
273
|
+
const body = await res.json();
|
|
274
|
+
errors = body.errors;
|
|
275
|
+
} catch {
|
|
276
|
+
errors = void 0;
|
|
277
|
+
}
|
|
278
|
+
const headerRetryMs = readFoirRetryAfterMs(res.headers);
|
|
279
|
+
if (headerRetryMs === void 0) return errors;
|
|
280
|
+
const seconds = Math.round(headerRetryMs / 1e3);
|
|
281
|
+
if (!errors?.length) {
|
|
282
|
+
return [
|
|
283
|
+
{
|
|
284
|
+
message: res.statusText || `HTTP ${res.status}`,
|
|
285
|
+
extensions: { code: "RATE_LIMITED", retryAfter: seconds }
|
|
286
|
+
}
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
if (errors[0].extensions?.retryAfter === void 0) {
|
|
290
|
+
errors[0] = {
|
|
291
|
+
...errors[0],
|
|
292
|
+
extensions: { ...errors[0].extensions, retryAfter: seconds }
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return errors;
|
|
296
|
+
}
|
|
297
|
+
async function isSharedRefusal(res) {
|
|
298
|
+
return hasFoirErrorCode(res, NOT_SHAREABLE);
|
|
299
|
+
}
|
|
300
|
+
async function isPersistedQueryMiss(res) {
|
|
301
|
+
return hasFoirErrorCode(res, PERSISTED_QUERY_NOT_FOUND);
|
|
302
|
+
}
|
|
303
|
+
|
|
76
304
|
// src/graphql/content-client.ts
|
|
77
305
|
function createFoirContentClient(config) {
|
|
78
306
|
return {
|
|
79
307
|
async request(document, variables, options) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
308
|
+
return withFoirRetry(async () => {
|
|
309
|
+
const bound = config.getToken ? await config.getToken() : null;
|
|
310
|
+
return foirRequest(config.env, document, variables, {
|
|
311
|
+
...options,
|
|
312
|
+
bearerToken: options?.bearerToken ?? bound ?? void 0,
|
|
313
|
+
onRateLimit: options?.onRateLimit ?? config.onRateLimit
|
|
314
|
+
});
|
|
315
|
+
}, config.retry);
|
|
85
316
|
}
|
|
86
317
|
};
|
|
87
318
|
}
|
|
@@ -91,5 +322,12 @@ export {
|
|
|
91
322
|
absolutiseFoirMediaUrl,
|
|
92
323
|
createFoirContentClient,
|
|
93
324
|
foirRequest,
|
|
94
|
-
|
|
325
|
+
foirRetryDelayMs,
|
|
326
|
+
isFoirForbidden,
|
|
327
|
+
isFoirNotFound,
|
|
328
|
+
isFoirPreview,
|
|
329
|
+
isFoirRateLimited,
|
|
330
|
+
isFoirUnauthenticated,
|
|
331
|
+
readFoirRateLimit,
|
|
332
|
+
withFoirRetry
|
|
95
333
|
};
|
|
@@ -1,27 +1,17 @@
|
|
|
1
|
-
import { GraphQLSchema } from 'graphql';
|
|
2
|
-
import { B as BlockPluginConfig } from '../shared-DkWlEd2Z.js';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
* `@foir/sdk/codegen/block-registry` —
|
|
2
|
+
* `@foir/sdk/codegen/block-registry` — DEPRECATED alias.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* exhaustiveness by construction, replacing the hand-synced switch the
|
|
13
|
-
* storefront's `BlockDigester` used to carry.
|
|
4
|
+
* `block` was one project's name for a flexible field, never a platform
|
|
5
|
+
* concept (TTS-1391). The plugin moved to `@foir/sdk/codegen/item-registry`;
|
|
6
|
+
* this path re-exports it unchanged so an existing `codegen.ts` keeps working.
|
|
7
|
+
* Repoint the `plugins:` entry when convenient — this will be removed in a
|
|
8
|
+
* future major.
|
|
14
9
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
10
|
+
* Note that the emitted type is now named `ItemRegistry` by default and its
|
|
11
|
+
* components are typed `ItemComponent`. If you import the generated
|
|
12
|
+
* `BlockRegistry` by name, either pass `registryName: 'BlockRegistry'` or
|
|
13
|
+
* update the import; `@foir/sdk/content` still exports `BlockRegistry` as a
|
|
14
|
+
* deprecated alias of `ItemRegistry`, so the types remain compatible.
|
|
18
15
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/** Name of the generated registry type. Default `BlockRegistry`. */
|
|
22
|
-
registryName?: string;
|
|
23
|
-
}
|
|
24
|
-
/** graphql-codegen plugin entrypoint. */
|
|
25
|
-
declare const plugin: (schema: GraphQLSchema, _documents: unknown, config?: BlockRegistryConfig) => string;
|
|
26
|
-
|
|
27
|
-
export { type BlockRegistryConfig, plugin };
|
|
16
|
+
export { plugin } from './item-registry';
|
|
17
|
+
export type { ItemRegistryConfig, BlockRegistryConfig, } from './item-registry';
|
|
@@ -1,35 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
collectBlockMembers
|
|
6
|
-
} from "../chunk-ADO5FJNM.js";
|
|
7
|
-
|
|
8
|
-
// src/codegen/block-registry.ts
|
|
9
|
-
var plugin = (schema, _documents, config = {}) => {
|
|
10
|
-
const members = collectBlockMembers(schema, config.unions ?? DEFAULT_UNIONS);
|
|
11
|
-
const graphqlImport = config.graphqlImport ?? DEFAULT_GRAPHQL_IMPORT;
|
|
12
|
-
const sdkImport = config.sdkImport ?? DEFAULT_SDK_IMPORT;
|
|
13
|
-
const registryName = config.registryName ?? "BlockRegistry";
|
|
14
|
-
return [
|
|
15
|
-
"/* eslint-disable */",
|
|
16
|
-
"// Generated by @foir/sdk/codegen/block-registry. Do not edit by hand.",
|
|
17
|
-
"// Regenerated on every codegen run \u2014 omitting a block type is a compile error.",
|
|
18
|
-
`import type { BlockComponent } from '${sdkImport}';`,
|
|
19
|
-
`import type {`,
|
|
20
|
-
...members.map((m) => ` ${m},`),
|
|
21
|
-
`} from '${graphqlImport}';`,
|
|
22
|
-
"",
|
|
23
|
-
`/**`,
|
|
24
|
-
` * Exhaustive map of every block \`__typename\` in the project schema to the`,
|
|
25
|
-
` * component that renders it. Pass an instance to \`createContentRenderer\`.`,
|
|
26
|
-
` */`,
|
|
27
|
-
`export type ${registryName}<Ctx = unknown> = {`,
|
|
28
|
-
...members.map((m) => ` ${m}: BlockComponent<${m}, Ctx>;`),
|
|
29
|
-
`};`,
|
|
30
|
-
""
|
|
31
|
-
].join("\n");
|
|
32
|
-
};
|
|
2
|
+
plugin
|
|
3
|
+
} from "../chunk-IGNLWT3H.js";
|
|
4
|
+
import "../chunk-TAL5H6VA.js";
|
|
33
5
|
export {
|
|
34
6
|
plugin
|
|
35
7
|
};
|
|
@@ -1,28 +1,15 @@
|
|
|
1
|
-
import { GraphQLSchema } from 'graphql';
|
|
2
|
-
import { B as BlockPluginConfig } from '../shared-DkWlEd2Z.js';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
* `@foir/sdk/codegen/block-scaffold` —
|
|
2
|
+
* `@foir/sdk/codegen/block-scaffold` — DEPRECATED alias.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
4
|
+
* `block` was one project's name for a flexible field, never a platform
|
|
5
|
+
* concept (TTS-1391). The plugin moved to `@foir/sdk/codegen/item-scaffold`;
|
|
6
|
+
* this path re-exports it unchanged so an existing `codegen.ts` keeps working.
|
|
7
|
+
* Repoint the `plugins:` entry when convenient — this will be removed in a
|
|
8
|
+
* future major.
|
|
9
|
+
*
|
|
10
|
+
* The scaffold is a one-shot generator, so a file scaffolded under the old name
|
|
11
|
+
* is yours and is untouched by this. Newly scaffolded files export
|
|
12
|
+
* `itemRegistry` rather than `blockRegistry` and take `props.item`.
|
|
15
13
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/** Import path for the generated registry type. Default `./block-registry`. */
|
|
19
|
-
registryImport?: string;
|
|
20
|
-
/** Render-context type name threaded to every component. Default `unknown`. */
|
|
21
|
-
ctxType?: string;
|
|
22
|
-
/** Optional import line for the ctx type, e.g. `import type { Ctx } from '~/ctx'`. */
|
|
23
|
-
ctxImport?: string;
|
|
24
|
-
}
|
|
25
|
-
/** graphql-codegen plugin entrypoint. */
|
|
26
|
-
declare const plugin: (schema: GraphQLSchema, _documents: unknown, config?: BlockScaffoldConfig) => string;
|
|
27
|
-
|
|
28
|
-
export { type BlockScaffoldConfig, plugin };
|
|
14
|
+
export { plugin } from './item-scaffold';
|
|
15
|
+
export type { ItemScaffoldConfig, BlockScaffoldConfig, } from './item-scaffold';
|
|
@@ -1,45 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
collectBlockMembers
|
|
6
|
-
} from "../chunk-ADO5FJNM.js";
|
|
7
|
-
|
|
8
|
-
// src/codegen/block-scaffold.ts
|
|
9
|
-
var plugin = (schema, _documents, config = {}) => {
|
|
10
|
-
const members = collectBlockMembers(schema, config.unions ?? DEFAULT_UNIONS);
|
|
11
|
-
const graphqlImport = config.graphqlImport ?? DEFAULT_GRAPHQL_IMPORT;
|
|
12
|
-
const sdkImport = config.sdkImport ?? DEFAULT_SDK_IMPORT;
|
|
13
|
-
const registryImport = config.registryImport ?? "./block-registry";
|
|
14
|
-
const ctx = config.ctxType ?? "unknown";
|
|
15
|
-
const stub = (m) => [
|
|
16
|
-
`function ${m}View(props: BlockComponentProps<${m}, ${ctx}>) {`,
|
|
17
|
-
` // TODO: render ${m} from props.block (props.ctx, props.priority available)`,
|
|
18
|
-
` void props;`,
|
|
19
|
-
` return null;`,
|
|
20
|
-
`}`
|
|
21
|
-
].join("\n");
|
|
22
|
-
return [
|
|
23
|
-
"/* eslint-disable */",
|
|
24
|
-
"// Scaffolded once by @foir/sdk/codegen/block-scaffold \u2014 you own this file.",
|
|
25
|
-
"// Implement each stub; the build stays green until you do.",
|
|
26
|
-
`import { createContentRenderer, type BlockComponentProps } from '${sdkImport}';`,
|
|
27
|
-
`import type { BlockRegistry } from '${registryImport}';`,
|
|
28
|
-
`import type {`,
|
|
29
|
-
...members.map((m) => ` ${m},`),
|
|
30
|
-
`} from '${graphqlImport}';`,
|
|
31
|
-
...config.ctxImport ? ["", config.ctxImport] : [],
|
|
32
|
-
"",
|
|
33
|
-
...members.map(stub),
|
|
34
|
-
"",
|
|
35
|
-
`export const blockRegistry: BlockRegistry<${ctx}> = {`,
|
|
36
|
-
...members.map((m) => ` ${m}: ${m}View,`),
|
|
37
|
-
`};`,
|
|
38
|
-
"",
|
|
39
|
-
`export const ContentRenderer = createContentRenderer<${ctx}>(blockRegistry);`,
|
|
40
|
-
""
|
|
41
|
-
].join("\n");
|
|
42
|
-
};
|
|
2
|
+
plugin
|
|
3
|
+
} from "../chunk-OHYVKTGB.js";
|
|
4
|
+
import "../chunk-TAL5H6VA.js";
|
|
43
5
|
export {
|
|
44
6
|
plugin
|
|
45
7
|
};
|