everything-dev 2.0.0-rc.4 → 2.0.0-rc.6

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 (71) hide show
  1. package/dist/api-contract.cjs +3 -6
  2. package/dist/api-contract.cjs.map +1 -1
  3. package/dist/api-contract.mjs +3 -6
  4. package/dist/api-contract.mjs.map +1 -1
  5. package/dist/apps/upload/src/client.cjs +188 -0
  6. package/dist/apps/upload/src/client.cjs.map +1 -0
  7. package/dist/apps/upload/src/client.mjs +180 -0
  8. package/dist/apps/upload/src/client.mjs.map +1 -0
  9. package/dist/bundle-fs-resolve.cjs +37 -3
  10. package/dist/bundle-fs-resolve.cjs.map +1 -1
  11. package/dist/bundle-fs-resolve.mjs +37 -3
  12. package/dist/bundle-fs-resolve.mjs.map +1 -1
  13. package/dist/cdn-deploy.cjs +29 -11
  14. package/dist/cdn-deploy.cjs.map +1 -1
  15. package/dist/cdn-deploy.mjs +29 -11
  16. package/dist/cdn-deploy.mjs.map +1 -1
  17. package/dist/cli/infra.cjs +4 -2
  18. package/dist/cli/infra.cjs.map +1 -1
  19. package/dist/cli/infra.mjs +4 -2
  20. package/dist/cli/infra.mjs.map +1 -1
  21. package/dist/cli/init.cjs +0 -2
  22. package/dist/cli/init.cjs.map +1 -1
  23. package/dist/cli/init.d.cts +1 -1
  24. package/dist/cli/init.d.mts +1 -1
  25. package/dist/cli/init.mjs +0 -2
  26. package/dist/cli/init.mjs.map +1 -1
  27. package/dist/cli/sync.cjs +0 -1
  28. package/dist/cli/sync.cjs.map +1 -1
  29. package/dist/cli/sync.mjs +0 -1
  30. package/dist/cli/sync.mjs.map +1 -1
  31. package/dist/commands/deploy.cjs +0 -2
  32. package/dist/commands/deploy.cjs.map +1 -1
  33. package/dist/commands/deploy.mjs +0 -2
  34. package/dist/commands/deploy.mjs.map +1 -1
  35. package/dist/commands/upgrade.cjs +30 -7
  36. package/dist/commands/upgrade.cjs.map +1 -1
  37. package/dist/commands/upgrade.mjs +30 -7
  38. package/dist/commands/upgrade.mjs.map +1 -1
  39. package/dist/config.cjs.map +1 -1
  40. package/dist/config.mjs.map +1 -1
  41. package/dist/contract.meta.cjs +1 -1
  42. package/dist/contract.meta.cjs.map +1 -1
  43. package/dist/contract.meta.d.cts +1 -1
  44. package/dist/contract.meta.d.mts +1 -1
  45. package/dist/contract.meta.mjs +1 -1
  46. package/dist/contract.meta.mjs.map +1 -1
  47. package/dist/dev-program.cjs +2 -2
  48. package/dist/dev-program.cjs.map +1 -1
  49. package/dist/dev-program.mjs +2 -3
  50. package/dist/dev-program.mjs.map +1 -1
  51. package/dist/image-deploy.cjs +38 -14
  52. package/dist/image-deploy.cjs.map +1 -1
  53. package/dist/image-deploy.mjs +39 -15
  54. package/dist/image-deploy.mjs.map +1 -1
  55. package/dist/orchestrator.cjs +22 -18
  56. package/dist/orchestrator.cjs.map +1 -1
  57. package/dist/orchestrator.mjs +22 -18
  58. package/dist/orchestrator.mjs.map +1 -1
  59. package/dist/storage-upload.cjs +7 -68
  60. package/dist/storage-upload.cjs.map +1 -1
  61. package/dist/storage-upload.mjs +7 -68
  62. package/dist/storage-upload.mjs.map +1 -1
  63. package/dist/ui/api.cjs +11 -69
  64. package/dist/ui/api.cjs.map +1 -1
  65. package/dist/ui/api.d.cts +2 -4
  66. package/dist/ui/api.d.mts +2 -4
  67. package/dist/ui/api.mjs +11 -69
  68. package/dist/ui/api.mjs.map +1 -1
  69. package/dist/ui/auth.d.cts +43 -43
  70. package/dist/ui/auth.d.mts +43 -43
  71. package/package.json +3 -2
@@ -0,0 +1,188 @@
1
+ let effect = require("effect");
2
+ let zod = require("zod");
3
+ let every_plugin_version_manifest = require("every-plugin/version-manifest");
4
+
5
+ //#region ../../apps/upload/src/client.ts
6
+ const BundleFileSchema = zod.z.object({
7
+ path: zod.z.string().min(1).max(512),
8
+ contentBase64: zod.z.string().min(1)
9
+ });
10
+ const UploadRequestSchema = zod.z.object({
11
+ account: zod.z.string().min(1),
12
+ gateway: zod.z.string().min(1),
13
+ workspace: zod.z.string().min(1),
14
+ files: zod.z.array(BundleFileSchema).min(1).max(1e4)
15
+ });
16
+ const UploadResponseSchema = zod.z.object({
17
+ stored: zod.z.number().int(),
18
+ /** files recognized as byte-identical to what the store already holds (SRI metadata match) and skipped */
19
+ unchanged: zod.z.number().int().optional(),
20
+ totalBytes: zod.z.number().int(),
21
+ integrity: zod.z.record(zod.z.string(), zod.z.string()),
22
+ storage: zod.z.enum(["s3", "memory"]).optional().describe("Resolved bundle-storage backend: `s3` (R2/MinIO, persistent) or `memory` (ephemeral — bytes are lost on restart)")
23
+ });
24
+ const NamespaceQuerySchema = zod.z.object({
25
+ account: zod.z.string().min(1),
26
+ gateway: zod.z.string().min(1)
27
+ });
28
+ const ListBundlesQuerySchema = NamespaceQuerySchema.extend({ workspace: zod.z.string().min(1).optional() });
29
+ const BundleObjectSummarySchema = zod.z.object({
30
+ key: zod.z.string(),
31
+ size: zod.z.number().int(),
32
+ lastModified: zod.z.string().nullable().optional()
33
+ });
34
+ const ListBundlesResultSchema = zod.z.object({
35
+ storage: zod.z.enum(["s3", "memory"]),
36
+ prefix: zod.z.string(),
37
+ objects: zod.z.array(BundleObjectSummarySchema),
38
+ truncated: zod.z.boolean()
39
+ });
40
+ const ListVersionsQuerySchema = NamespaceQuerySchema.extend({ workspace: zod.z.string().min(1) });
41
+ const VersionSummarySchema = zod.z.object({
42
+ version: zod.z.string().regex(/^[0-9a-f]{16}$/),
43
+ entry: zod.z.string(),
44
+ entryIntegrity: zod.z.string(),
45
+ gitSha: zod.z.string().optional(),
46
+ builtAt: zod.z.string().optional(),
47
+ fileCount: zod.z.number().int().optional(),
48
+ lastModified: zod.z.string().nullable().optional()
49
+ });
50
+ const ListVersionsResultSchema = zod.z.object({
51
+ storage: zod.z.enum(["s3", "memory"]),
52
+ versions: zod.z.array(VersionSummarySchema),
53
+ truncated: zod.z.boolean()
54
+ });
55
+ const IntegrityReportQuerySchema = NamespaceQuerySchema.extend({
56
+ workspace: zod.z.string().min(1),
57
+ version: zod.z.string().regex(/^[0-9a-f]{16}$/)
58
+ });
59
+ const IntegrityReportResultSchema = zod.z.object({
60
+ storage: zod.z.enum(["s3", "memory"]),
61
+ manifest: every_plugin_version_manifest.WorkspaceVersionManifestSchema,
62
+ /** SRI recompute of the manifest's entry object against `entryIntegrity`;
63
+ * null when the entry object is missing from the store. */
64
+ entryVerified: zod.z.boolean().nullable()
65
+ });
66
+ const STATUS_ATTEMPTS = 3;
67
+ const TRANSPORT_ATTEMPTS = 5;
68
+ const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
69
+ 408,
70
+ 429,
71
+ 500,
72
+ 502,
73
+ 503,
74
+ 504
75
+ ]);
76
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
77
+ const backoffMs = (attempt) => 1e3 * attempt + Math.floor(Math.random() * 500);
78
+ var UploadTransferError = class UploadTransferError extends effect.Data.TaggedError("UploadTransferError") {
79
+ constructor(status, detail, note) {
80
+ super({
81
+ status,
82
+ detail,
83
+ note
84
+ });
85
+ }
86
+ get message() {
87
+ return `${this.detail}${this.note ?? ""}`;
88
+ }
89
+ withNote(note) {
90
+ return new UploadTransferError(this.status, this.detail, `${this.note ?? ""}${note}`);
91
+ }
92
+ };
93
+ function describeTransferFailure(status, detail) {
94
+ const trimmed = detail.slice(0, 300);
95
+ if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;
96
+ try {
97
+ const parsed = JSON.parse(detail);
98
+ if (parsed?.code === "INTERNAL_SERVER_ERROR" && parsed?.message === "Internal Server Error") return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;
99
+ } catch {}
100
+ return trimmed;
101
+ }
102
+ async function requestJson(config, label, path, parse, init) {
103
+ const fetchImpl = config.fetchImpl ?? fetch;
104
+ const headers = { ...init?.headers };
105
+ if (config.apiKey) headers["x-api-key"] = config.apiKey;
106
+ if (init?.body && !headers["content-type"]) headers["content-type"] = "application/json";
107
+ let lastError;
108
+ let transportFailures = 0;
109
+ let statusFailures = 0;
110
+ for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {
111
+ try {
112
+ const response = await fetchImpl(`${config.origin.replace(/\/$/, "")}${path}`, {
113
+ ...init,
114
+ headers: transportFailures > 0 ? {
115
+ ...headers,
116
+ connection: "close"
117
+ } : headers
118
+ });
119
+ if (response.ok) return parse(await response.json());
120
+ const detail = await response.text().catch(() => "");
121
+ lastError = new UploadTransferError(response.status, `[upload] ${label} failed: ${response.status} ${describeTransferFailure(response.status, detail)}`);
122
+ if (!RETRYABLE_STATUSES.has(response.status)) break;
123
+ statusFailures += 1;
124
+ if (statusFailures >= STATUS_ATTEMPTS) break;
125
+ } catch (error) {
126
+ transportFailures += 1;
127
+ lastError = new UploadTransferError(0, `[upload] ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
128
+ }
129
+ if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));
130
+ }
131
+ if (lastError instanceof UploadTransferError && lastError.status === 0) lastError = lastError.withNote(" (Bun fetch note: re-run with fetch verbose diagnostics for detail)");
132
+ throw lastError ?? /* @__PURE__ */ new Error(`[upload] ${label} failed`);
133
+ }
134
+ const parseJsonBody = (label, schema) => {
135
+ return (response) => {
136
+ try {
137
+ return schema.parse(response);
138
+ } catch (error) {
139
+ throw new UploadTransferError(0, `[upload] ${label} returned an unexpected payload: ${error instanceof Error ? error.message : String(error)}`);
140
+ }
141
+ };
142
+ };
143
+ function createUploadClient(config) {
144
+ return {
145
+ uploadBundle(input) {
146
+ const payload = UploadRequestSchema.parse(input);
147
+ return requestJson(config, `bundle upload for ${payload.workspace}`, "/api/storage/bundles", parseJsonBody("bundle upload", UploadResponseSchema), {
148
+ method: "POST",
149
+ body: JSON.stringify(payload)
150
+ });
151
+ },
152
+ listBundles(input) {
153
+ const query = new URLSearchParams({
154
+ account: input.account,
155
+ gateway: input.gateway
156
+ });
157
+ if (input.workspace) query.set("workspace", input.workspace);
158
+ return requestJson(config, "bundle listing", `/api/storage/bundles?${query.toString()}`, parseJsonBody("bundle listing", ListBundlesResultSchema), { method: "GET" });
159
+ },
160
+ listVersions(input) {
161
+ return requestJson(config, "version listing", `/api/storage/versions?${new URLSearchParams({
162
+ account: input.account,
163
+ gateway: input.gateway,
164
+ workspace: input.workspace
165
+ }).toString()}`, parseJsonBody("version listing", ListVersionsResultSchema), { method: "GET" });
166
+ },
167
+ integrityReport(input) {
168
+ return requestJson(config, "integrity report", `/api/storage/integrity?${new URLSearchParams({
169
+ account: input.account,
170
+ gateway: input.gateway,
171
+ workspace: input.workspace,
172
+ version: input.version
173
+ }).toString()}`, parseJsonBody("integrity report", IntegrityReportResultSchema), { method: "GET" });
174
+ }
175
+ };
176
+ }
177
+
178
+ //#endregion
179
+ exports.BundleFileSchema = BundleFileSchema;
180
+ exports.BundleObjectSummarySchema = BundleObjectSummarySchema;
181
+ exports.IntegrityReportResultSchema = IntegrityReportResultSchema;
182
+ exports.ListBundlesResultSchema = ListBundlesResultSchema;
183
+ exports.ListVersionsResultSchema = ListVersionsResultSchema;
184
+ exports.UploadRequestSchema = UploadRequestSchema;
185
+ exports.UploadResponseSchema = UploadResponseSchema;
186
+ exports.VersionSummarySchema = VersionSummarySchema;
187
+ exports.createUploadClient = createUploadClient;
188
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.cjs","names":["z","WorkspaceVersionManifestSchema","Data"],"sources":["../../../../../../apps/upload/src/client.ts"],"sourcesContent":["import { Data } from \"effect\";\nimport { WorkspaceVersionManifestSchema } from \"every-plugin/version-manifest\";\nimport { z } from \"zod\";\n\nexport const BundleFileSchema = z.object({\n path: z.string().min(1).max(512),\n contentBase64: z.string().min(1),\n});\n\nexport const UploadRequestSchema = z.object({\n account: z.string().min(1),\n gateway: z.string().min(1),\n workspace: z.string().min(1),\n files: z.array(BundleFileSchema).min(1).max(10_000),\n});\n\nexport const UploadResponseSchema = z.object({\n stored: z.number().int(),\n /** files recognized as byte-identical to what the store already holds (SRI metadata match) and skipped */\n unchanged: z.number().int().optional(),\n totalBytes: z.number().int(),\n integrity: z.record(z.string(), z.string()),\n storage: z\n .enum([\"s3\", \"memory\"])\n .optional()\n .describe(\n \"Resolved bundle-storage backend: `s3` (R2/MinIO, persistent) or `memory` (ephemeral — bytes are lost on restart)\",\n ),\n});\n\nconst NamespaceQuerySchema = z.object({\n account: z.string().min(1),\n gateway: z.string().min(1),\n});\n\nexport const ListBundlesQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1).optional(),\n});\n\nexport const BundleObjectSummarySchema = z.object({\n key: z.string(),\n size: z.number().int(),\n lastModified: z.string().nullable().optional(),\n});\n\nexport const ListBundlesResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n prefix: z.string(),\n objects: z.array(BundleObjectSummarySchema),\n truncated: z.boolean(),\n});\n\nexport const ListVersionsQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1),\n});\n\nexport const VersionSummarySchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n entry: z.string(),\n entryIntegrity: z.string(),\n gitSha: z.string().optional(),\n builtAt: z.string().optional(),\n fileCount: z.number().int().optional(),\n lastModified: z.string().nullable().optional(),\n});\n\nexport const ListVersionsResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n versions: z.array(VersionSummarySchema),\n truncated: z.boolean(),\n});\n\nexport const IntegrityReportQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1),\n version: z.string().regex(/^[0-9a-f]{16}$/),\n});\n\nexport const IntegrityReportResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n manifest: WorkspaceVersionManifestSchema,\n /** SRI recompute of the manifest's entry object against `entryIntegrity`;\n * null when the entry object is missing from the store. */\n entryVerified: z.boolean().nullable(),\n});\n\nexport type BundleFile = z.infer<typeof BundleFileSchema>;\nexport type UploadRequest = z.infer<typeof UploadRequestSchema>;\nexport type UploadResponse = z.infer<typeof UploadResponseSchema>;\nexport type ListBundlesQuery = z.infer<typeof ListBundlesQuerySchema>;\nexport type BundleObjectSummary = z.infer<typeof BundleObjectSummarySchema>;\nexport type ListBundlesResult = z.infer<typeof ListBundlesResultSchema>;\nexport type ListVersionsQuery = z.infer<typeof ListVersionsQuerySchema>;\nexport type VersionSummary = z.infer<typeof VersionSummarySchema>;\nexport type ListVersionsResult = z.infer<typeof ListVersionsResultSchema>;\nexport type IntegrityReportQuery = z.infer<typeof IntegrityReportQuerySchema>;\nexport type IntegrityReportResult = z.infer<typeof IntegrityReportResultSchema>;\n\nexport interface UploadClientConfig {\n origin: string;\n apiKey?: string;\n fetchImpl?: typeof fetch;\n}\n\nexport interface UploadClient {\n uploadBundle(input: UploadRequest): Promise<UploadResponse>;\n listBundles(input: ListBundlesQuery): Promise<ListBundlesResult>;\n listVersions(input: ListVersionsQuery): Promise<ListVersionsResult>;\n integrityReport(input: IntegrityReportQuery): Promise<IntegrityReportResult>;\n}\n\nconst STATUS_ATTEMPTS = 3;\nconst TRANSPORT_ATTEMPTS = 5;\nconst RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\nconst backoffMs = (attempt: number) => 1000 * attempt + Math.floor(Math.random() * 500);\n\nclass UploadTransferError extends Data.TaggedError(\"UploadTransferError\")<{\n readonly status: number;\n readonly detail: string;\n readonly note?: string;\n}> {\n constructor(status: number, detail: string, note?: string) {\n super({ status, detail, note });\n }\n\n override get message() {\n return `${this.detail}${this.note ?? \"\"}`;\n }\n\n withNote(note: string) {\n return new UploadTransferError(this.status, this.detail, `${this.note ?? \"\"}${note}`);\n }\n}\n\nfunction describeTransferFailure(status: number, detail: string): string {\n const trimmed = detail.slice(0, 300);\n if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) {\n return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;\n }\n try {\n const parsed = JSON.parse(detail) as { code?: unknown; message?: unknown };\n if (parsed?.code === \"INTERNAL_SERVER_ERROR\" && parsed?.message === \"Internal Server Error\") {\n return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;\n }\n } catch {\n // not a JSON error body\n }\n return trimmed;\n}\n\nasync function requestJson<T>(\n config: UploadClientConfig,\n label: string,\n path: string,\n parse: (value: unknown) => T,\n init?: RequestInit,\n): Promise<T> {\n const fetchImpl = config.fetchImpl ?? fetch;\n const headers: Record<string, string> = { ...(init?.headers as Record<string, string>) };\n if (config.apiKey) headers[\"x-api-key\"] = config.apiKey;\n if (init?.body && !headers[\"content-type\"]) headers[\"content-type\"] = \"application/json\";\n\n let lastError: UploadTransferError | Error | undefined;\n let transportFailures = 0;\n let statusFailures = 0;\n for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {\n try {\n const response = await fetchImpl(`${config.origin.replace(/\\/$/, \"\")}${path}`, {\n ...init,\n headers: transportFailures > 0 ? { ...headers, connection: \"close\" } : headers,\n });\n\n if (response.ok) {\n return parse(await response.json());\n }\n\n const detail = await response.text().catch(() => \"\");\n lastError = new UploadTransferError(\n response.status,\n `[upload] ${label} failed: ${response.status} ${describeTransferFailure(response.status, detail)}`,\n );\n if (!RETRYABLE_STATUSES.has(response.status)) break;\n statusFailures += 1;\n if (statusFailures >= STATUS_ATTEMPTS) break;\n } catch (error) {\n transportFailures += 1;\n lastError = new UploadTransferError(\n 0,\n `[upload] ${label} failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));\n }\n\n if (lastError instanceof UploadTransferError && lastError.status === 0) {\n lastError = lastError.withNote(\n \" (Bun fetch note: re-run with fetch verbose diagnostics for detail)\",\n );\n }\n throw lastError ?? new Error(`[upload] ${label} failed`);\n}\n\nconst parseJsonBody = <T>(label: string, schema: { parse: (value: unknown) => T }) => {\n return (response: unknown): T => {\n try {\n return schema.parse(response);\n } catch (error) {\n throw new UploadTransferError(\n 0,\n `[upload] ${label} returned an unexpected payload: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n };\n};\n\nexport function createUploadClient(config: UploadClientConfig): UploadClient {\n return {\n uploadBundle(input) {\n const payload = UploadRequestSchema.parse(input);\n return requestJson(\n config,\n `bundle upload for ${payload.workspace}`,\n \"/api/storage/bundles\",\n parseJsonBody(\"bundle upload\", UploadResponseSchema),\n { method: \"POST\", body: JSON.stringify(payload) },\n );\n },\n\n listBundles(input) {\n const query = new URLSearchParams({ account: input.account, gateway: input.gateway });\n if (input.workspace) query.set(\"workspace\", input.workspace);\n return requestJson(\n config,\n \"bundle listing\",\n `/api/storage/bundles?${query.toString()}`,\n parseJsonBody(\"bundle listing\", ListBundlesResultSchema),\n { method: \"GET\" },\n );\n },\n\n listVersions(input) {\n const query = new URLSearchParams({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n });\n return requestJson(\n config,\n \"version listing\",\n `/api/storage/versions?${query.toString()}`,\n parseJsonBody(\"version listing\", ListVersionsResultSchema),\n { method: \"GET\" },\n );\n },\n\n integrityReport(input) {\n const query = new URLSearchParams({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n version: input.version,\n });\n return requestJson(\n config,\n \"integrity report\",\n `/api/storage/integrity?${query.toString()}`,\n parseJsonBody(\"integrity report\", IntegrityReportResultSchema),\n { method: \"GET\" },\n );\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ import { Data } from "effect";
2
+ import { z } from "zod";
3
+ import { WorkspaceVersionManifestSchema } from "every-plugin/version-manifest";
4
+
5
+ //#region ../../apps/upload/src/client.ts
6
+ const BundleFileSchema = z.object({
7
+ path: z.string().min(1).max(512),
8
+ contentBase64: z.string().min(1)
9
+ });
10
+ const UploadRequestSchema = z.object({
11
+ account: z.string().min(1),
12
+ gateway: z.string().min(1),
13
+ workspace: z.string().min(1),
14
+ files: z.array(BundleFileSchema).min(1).max(1e4)
15
+ });
16
+ const UploadResponseSchema = z.object({
17
+ stored: z.number().int(),
18
+ /** files recognized as byte-identical to what the store already holds (SRI metadata match) and skipped */
19
+ unchanged: z.number().int().optional(),
20
+ totalBytes: z.number().int(),
21
+ integrity: z.record(z.string(), z.string()),
22
+ storage: z.enum(["s3", "memory"]).optional().describe("Resolved bundle-storage backend: `s3` (R2/MinIO, persistent) or `memory` (ephemeral — bytes are lost on restart)")
23
+ });
24
+ const NamespaceQuerySchema = z.object({
25
+ account: z.string().min(1),
26
+ gateway: z.string().min(1)
27
+ });
28
+ const ListBundlesQuerySchema = NamespaceQuerySchema.extend({ workspace: z.string().min(1).optional() });
29
+ const BundleObjectSummarySchema = z.object({
30
+ key: z.string(),
31
+ size: z.number().int(),
32
+ lastModified: z.string().nullable().optional()
33
+ });
34
+ const ListBundlesResultSchema = z.object({
35
+ storage: z.enum(["s3", "memory"]),
36
+ prefix: z.string(),
37
+ objects: z.array(BundleObjectSummarySchema),
38
+ truncated: z.boolean()
39
+ });
40
+ const ListVersionsQuerySchema = NamespaceQuerySchema.extend({ workspace: z.string().min(1) });
41
+ const VersionSummarySchema = z.object({
42
+ version: z.string().regex(/^[0-9a-f]{16}$/),
43
+ entry: z.string(),
44
+ entryIntegrity: z.string(),
45
+ gitSha: z.string().optional(),
46
+ builtAt: z.string().optional(),
47
+ fileCount: z.number().int().optional(),
48
+ lastModified: z.string().nullable().optional()
49
+ });
50
+ const ListVersionsResultSchema = z.object({
51
+ storage: z.enum(["s3", "memory"]),
52
+ versions: z.array(VersionSummarySchema),
53
+ truncated: z.boolean()
54
+ });
55
+ const IntegrityReportQuerySchema = NamespaceQuerySchema.extend({
56
+ workspace: z.string().min(1),
57
+ version: z.string().regex(/^[0-9a-f]{16}$/)
58
+ });
59
+ const IntegrityReportResultSchema = z.object({
60
+ storage: z.enum(["s3", "memory"]),
61
+ manifest: WorkspaceVersionManifestSchema,
62
+ /** SRI recompute of the manifest's entry object against `entryIntegrity`;
63
+ * null when the entry object is missing from the store. */
64
+ entryVerified: z.boolean().nullable()
65
+ });
66
+ const STATUS_ATTEMPTS = 3;
67
+ const TRANSPORT_ATTEMPTS = 5;
68
+ const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
69
+ 408,
70
+ 429,
71
+ 500,
72
+ 502,
73
+ 503,
74
+ 504
75
+ ]);
76
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
77
+ const backoffMs = (attempt) => 1e3 * attempt + Math.floor(Math.random() * 500);
78
+ var UploadTransferError = class UploadTransferError extends Data.TaggedError("UploadTransferError") {
79
+ constructor(status, detail, note) {
80
+ super({
81
+ status,
82
+ detail,
83
+ note
84
+ });
85
+ }
86
+ get message() {
87
+ return `${this.detail}${this.note ?? ""}`;
88
+ }
89
+ withNote(note) {
90
+ return new UploadTransferError(this.status, this.detail, `${this.note ?? ""}${note}`);
91
+ }
92
+ };
93
+ function describeTransferFailure(status, detail) {
94
+ const trimmed = detail.slice(0, 300);
95
+ if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;
96
+ try {
97
+ const parsed = JSON.parse(detail);
98
+ if (parsed?.code === "INTERNAL_SERVER_ERROR" && parsed?.message === "Internal Server Error") return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;
99
+ } catch {}
100
+ return trimmed;
101
+ }
102
+ async function requestJson(config, label, path, parse, init) {
103
+ const fetchImpl = config.fetchImpl ?? fetch;
104
+ const headers = { ...init?.headers };
105
+ if (config.apiKey) headers["x-api-key"] = config.apiKey;
106
+ if (init?.body && !headers["content-type"]) headers["content-type"] = "application/json";
107
+ let lastError;
108
+ let transportFailures = 0;
109
+ let statusFailures = 0;
110
+ for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {
111
+ try {
112
+ const response = await fetchImpl(`${config.origin.replace(/\/$/, "")}${path}`, {
113
+ ...init,
114
+ headers: transportFailures > 0 ? {
115
+ ...headers,
116
+ connection: "close"
117
+ } : headers
118
+ });
119
+ if (response.ok) return parse(await response.json());
120
+ const detail = await response.text().catch(() => "");
121
+ lastError = new UploadTransferError(response.status, `[upload] ${label} failed: ${response.status} ${describeTransferFailure(response.status, detail)}`);
122
+ if (!RETRYABLE_STATUSES.has(response.status)) break;
123
+ statusFailures += 1;
124
+ if (statusFailures >= STATUS_ATTEMPTS) break;
125
+ } catch (error) {
126
+ transportFailures += 1;
127
+ lastError = new UploadTransferError(0, `[upload] ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
128
+ }
129
+ if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));
130
+ }
131
+ if (lastError instanceof UploadTransferError && lastError.status === 0) lastError = lastError.withNote(" (Bun fetch note: re-run with fetch verbose diagnostics for detail)");
132
+ throw lastError ?? /* @__PURE__ */ new Error(`[upload] ${label} failed`);
133
+ }
134
+ const parseJsonBody = (label, schema) => {
135
+ return (response) => {
136
+ try {
137
+ return schema.parse(response);
138
+ } catch (error) {
139
+ throw new UploadTransferError(0, `[upload] ${label} returned an unexpected payload: ${error instanceof Error ? error.message : String(error)}`);
140
+ }
141
+ };
142
+ };
143
+ function createUploadClient(config) {
144
+ return {
145
+ uploadBundle(input) {
146
+ const payload = UploadRequestSchema.parse(input);
147
+ return requestJson(config, `bundle upload for ${payload.workspace}`, "/api/storage/bundles", parseJsonBody("bundle upload", UploadResponseSchema), {
148
+ method: "POST",
149
+ body: JSON.stringify(payload)
150
+ });
151
+ },
152
+ listBundles(input) {
153
+ const query = new URLSearchParams({
154
+ account: input.account,
155
+ gateway: input.gateway
156
+ });
157
+ if (input.workspace) query.set("workspace", input.workspace);
158
+ return requestJson(config, "bundle listing", `/api/storage/bundles?${query.toString()}`, parseJsonBody("bundle listing", ListBundlesResultSchema), { method: "GET" });
159
+ },
160
+ listVersions(input) {
161
+ return requestJson(config, "version listing", `/api/storage/versions?${new URLSearchParams({
162
+ account: input.account,
163
+ gateway: input.gateway,
164
+ workspace: input.workspace
165
+ }).toString()}`, parseJsonBody("version listing", ListVersionsResultSchema), { method: "GET" });
166
+ },
167
+ integrityReport(input) {
168
+ return requestJson(config, "integrity report", `/api/storage/integrity?${new URLSearchParams({
169
+ account: input.account,
170
+ gateway: input.gateway,
171
+ workspace: input.workspace,
172
+ version: input.version
173
+ }).toString()}`, parseJsonBody("integrity report", IntegrityReportResultSchema), { method: "GET" });
174
+ }
175
+ };
176
+ }
177
+
178
+ //#endregion
179
+ export { BundleFileSchema, BundleObjectSummarySchema, IntegrityReportResultSchema, ListBundlesResultSchema, ListVersionsResultSchema, UploadRequestSchema, UploadResponseSchema, VersionSummarySchema, createUploadClient };
180
+ //# sourceMappingURL=client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../../../../../../apps/upload/src/client.ts"],"sourcesContent":["import { Data } from \"effect\";\nimport { WorkspaceVersionManifestSchema } from \"every-plugin/version-manifest\";\nimport { z } from \"zod\";\n\nexport const BundleFileSchema = z.object({\n path: z.string().min(1).max(512),\n contentBase64: z.string().min(1),\n});\n\nexport const UploadRequestSchema = z.object({\n account: z.string().min(1),\n gateway: z.string().min(1),\n workspace: z.string().min(1),\n files: z.array(BundleFileSchema).min(1).max(10_000),\n});\n\nexport const UploadResponseSchema = z.object({\n stored: z.number().int(),\n /** files recognized as byte-identical to what the store already holds (SRI metadata match) and skipped */\n unchanged: z.number().int().optional(),\n totalBytes: z.number().int(),\n integrity: z.record(z.string(), z.string()),\n storage: z\n .enum([\"s3\", \"memory\"])\n .optional()\n .describe(\n \"Resolved bundle-storage backend: `s3` (R2/MinIO, persistent) or `memory` (ephemeral — bytes are lost on restart)\",\n ),\n});\n\nconst NamespaceQuerySchema = z.object({\n account: z.string().min(1),\n gateway: z.string().min(1),\n});\n\nexport const ListBundlesQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1).optional(),\n});\n\nexport const BundleObjectSummarySchema = z.object({\n key: z.string(),\n size: z.number().int(),\n lastModified: z.string().nullable().optional(),\n});\n\nexport const ListBundlesResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n prefix: z.string(),\n objects: z.array(BundleObjectSummarySchema),\n truncated: z.boolean(),\n});\n\nexport const ListVersionsQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1),\n});\n\nexport const VersionSummarySchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n entry: z.string(),\n entryIntegrity: z.string(),\n gitSha: z.string().optional(),\n builtAt: z.string().optional(),\n fileCount: z.number().int().optional(),\n lastModified: z.string().nullable().optional(),\n});\n\nexport const ListVersionsResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n versions: z.array(VersionSummarySchema),\n truncated: z.boolean(),\n});\n\nexport const IntegrityReportQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1),\n version: z.string().regex(/^[0-9a-f]{16}$/),\n});\n\nexport const IntegrityReportResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n manifest: WorkspaceVersionManifestSchema,\n /** SRI recompute of the manifest's entry object against `entryIntegrity`;\n * null when the entry object is missing from the store. */\n entryVerified: z.boolean().nullable(),\n});\n\nexport type BundleFile = z.infer<typeof BundleFileSchema>;\nexport type UploadRequest = z.infer<typeof UploadRequestSchema>;\nexport type UploadResponse = z.infer<typeof UploadResponseSchema>;\nexport type ListBundlesQuery = z.infer<typeof ListBundlesQuerySchema>;\nexport type BundleObjectSummary = z.infer<typeof BundleObjectSummarySchema>;\nexport type ListBundlesResult = z.infer<typeof ListBundlesResultSchema>;\nexport type ListVersionsQuery = z.infer<typeof ListVersionsQuerySchema>;\nexport type VersionSummary = z.infer<typeof VersionSummarySchema>;\nexport type ListVersionsResult = z.infer<typeof ListVersionsResultSchema>;\nexport type IntegrityReportQuery = z.infer<typeof IntegrityReportQuerySchema>;\nexport type IntegrityReportResult = z.infer<typeof IntegrityReportResultSchema>;\n\nexport interface UploadClientConfig {\n origin: string;\n apiKey?: string;\n fetchImpl?: typeof fetch;\n}\n\nexport interface UploadClient {\n uploadBundle(input: UploadRequest): Promise<UploadResponse>;\n listBundles(input: ListBundlesQuery): Promise<ListBundlesResult>;\n listVersions(input: ListVersionsQuery): Promise<ListVersionsResult>;\n integrityReport(input: IntegrityReportQuery): Promise<IntegrityReportResult>;\n}\n\nconst STATUS_ATTEMPTS = 3;\nconst TRANSPORT_ATTEMPTS = 5;\nconst RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\nconst backoffMs = (attempt: number) => 1000 * attempt + Math.floor(Math.random() * 500);\n\nclass UploadTransferError extends Data.TaggedError(\"UploadTransferError\")<{\n readonly status: number;\n readonly detail: string;\n readonly note?: string;\n}> {\n constructor(status: number, detail: string, note?: string) {\n super({ status, detail, note });\n }\n\n override get message() {\n return `${this.detail}${this.note ?? \"\"}`;\n }\n\n withNote(note: string) {\n return new UploadTransferError(this.status, this.detail, `${this.note ?? \"\"}${note}`);\n }\n}\n\nfunction describeTransferFailure(status: number, detail: string): string {\n const trimmed = detail.slice(0, 300);\n if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) {\n return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;\n }\n try {\n const parsed = JSON.parse(detail) as { code?: unknown; message?: unknown };\n if (parsed?.code === \"INTERNAL_SERVER_ERROR\" && parsed?.message === \"Internal Server Error\") {\n return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;\n }\n } catch {\n // not a JSON error body\n }\n return trimmed;\n}\n\nasync function requestJson<T>(\n config: UploadClientConfig,\n label: string,\n path: string,\n parse: (value: unknown) => T,\n init?: RequestInit,\n): Promise<T> {\n const fetchImpl = config.fetchImpl ?? fetch;\n const headers: Record<string, string> = { ...(init?.headers as Record<string, string>) };\n if (config.apiKey) headers[\"x-api-key\"] = config.apiKey;\n if (init?.body && !headers[\"content-type\"]) headers[\"content-type\"] = \"application/json\";\n\n let lastError: UploadTransferError | Error | undefined;\n let transportFailures = 0;\n let statusFailures = 0;\n for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {\n try {\n const response = await fetchImpl(`${config.origin.replace(/\\/$/, \"\")}${path}`, {\n ...init,\n headers: transportFailures > 0 ? { ...headers, connection: \"close\" } : headers,\n });\n\n if (response.ok) {\n return parse(await response.json());\n }\n\n const detail = await response.text().catch(() => \"\");\n lastError = new UploadTransferError(\n response.status,\n `[upload] ${label} failed: ${response.status} ${describeTransferFailure(response.status, detail)}`,\n );\n if (!RETRYABLE_STATUSES.has(response.status)) break;\n statusFailures += 1;\n if (statusFailures >= STATUS_ATTEMPTS) break;\n } catch (error) {\n transportFailures += 1;\n lastError = new UploadTransferError(\n 0,\n `[upload] ${label} failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));\n }\n\n if (lastError instanceof UploadTransferError && lastError.status === 0) {\n lastError = lastError.withNote(\n \" (Bun fetch note: re-run with fetch verbose diagnostics for detail)\",\n );\n }\n throw lastError ?? new Error(`[upload] ${label} failed`);\n}\n\nconst parseJsonBody = <T>(label: string, schema: { parse: (value: unknown) => T }) => {\n return (response: unknown): T => {\n try {\n return schema.parse(response);\n } catch (error) {\n throw new UploadTransferError(\n 0,\n `[upload] ${label} returned an unexpected payload: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n };\n};\n\nexport function createUploadClient(config: UploadClientConfig): UploadClient {\n return {\n uploadBundle(input) {\n const payload = UploadRequestSchema.parse(input);\n return requestJson(\n config,\n `bundle upload for ${payload.workspace}`,\n \"/api/storage/bundles\",\n parseJsonBody(\"bundle upload\", UploadResponseSchema),\n { method: \"POST\", body: JSON.stringify(payload) },\n );\n },\n\n listBundles(input) {\n const query = new URLSearchParams({ account: input.account, gateway: input.gateway });\n if (input.workspace) query.set(\"workspace\", input.workspace);\n return requestJson(\n config,\n \"bundle listing\",\n `/api/storage/bundles?${query.toString()}`,\n parseJsonBody(\"bundle listing\", ListBundlesResultSchema),\n { method: \"GET\" },\n );\n },\n\n listVersions(input) {\n const query = new URLSearchParams({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n });\n return requestJson(\n config,\n \"version listing\",\n `/api/storage/versions?${query.toString()}`,\n parseJsonBody(\"version listing\", ListVersionsResultSchema),\n { method: \"GET\" },\n );\n },\n\n integrityReport(input) {\n const query = new URLSearchParams({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n version: input.version,\n });\n return requestJson(\n config,\n \"integrity report\",\n `/api/storage/integrity?${query.toString()}`,\n parseJsonBody(\"integrity report\", IntegrityReportResultSchema),\n { method: \"GET\" },\n );\n },\n };\n}\n"],"mappings":"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"}
@@ -95,6 +95,40 @@ const staleResponse = (bytes, url) => {
95
95
  }
96
96
  });
97
97
  };
98
+ const FETCH_RETRY_ATTEMPTS = 3;
99
+ const FETCH_RETRY_BASE_DELAY_MS = 250;
100
+ const isTransientStatus = (status) => status === 429 || status >= 500;
101
+ function requestMethod(input, init) {
102
+ if (init?.method) return init.method.toUpperCase();
103
+ if (typeof input === "string" || input instanceof URL) return "GET";
104
+ return input.method.toUpperCase();
105
+ }
106
+ /**
107
+ * Bounded transient-failure retry for idempotent GETs crossing the
108
+ * interceptor (ADR 0021 amendment — the CDN-only image): a cold boot has no
109
+ * staged fallback, so one edge/registry hiccup during the load window would
110
+ * hard-fail the host or a plugin with no recourse. Network errors and
111
+ * 429/5xx responses retry twice (250ms, 750ms backoff) before propagating;
112
+ * non-transient statuses and non-GET requests pass through untouched. After
113
+ * exhausting attempts a stale-if-error cache still sees the final failure.
114
+ */
115
+ async function fetchWithTransientRetry(original, input, init) {
116
+ if (requestMethod(input, init) !== "GET") return original(input, init);
117
+ let lastError;
118
+ let lastResponse = null;
119
+ for (let attempt = 0; attempt < FETCH_RETRY_ATTEMPTS; attempt++) {
120
+ if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, FETCH_RETRY_BASE_DELAY_MS * 3 ** (attempt - 1)));
121
+ try {
122
+ const response = await original(input, init);
123
+ if (!isTransientStatus(response.status)) return response;
124
+ lastResponse = response;
125
+ } catch (error) {
126
+ lastError = error;
127
+ }
128
+ }
129
+ if (lastResponse) return lastResponse;
130
+ throw lastError;
131
+ }
98
132
  /**
99
133
  * Installs the resolver as a global fetch interceptor — the one seam every
100
134
  * boot-time consumer shares (config manifest discovery, contract-type
@@ -121,11 +155,11 @@ function installGlobalBundleFetch(options) {
121
155
  return yield* (yield* BundleResolver).lookup(input);
122
156
  }));
123
157
  if (response !== null) return response;
124
- return original(input, init);
158
+ return fetchWithTransientRetry(original, input, init);
125
159
  }
126
160
  if (cacheDir !== void 0 && require_bundle_cache.bundleCachePath(url, { cacheDir })) return (async () => {
127
161
  try {
128
- const response = await original(input, init);
162
+ const response = await fetchWithTransientRetry(original, input, init);
129
163
  if (response.ok) try {
130
164
  const bytes = new Uint8Array(await response.clone().arrayBuffer());
131
165
  await require_bundle_cache.writeBundleCache(url, bytes, { cacheDir });
@@ -141,7 +175,7 @@ function installGlobalBundleFetch(options) {
141
175
  throw error;
142
176
  }
143
177
  })();
144
- return original(input, init);
178
+ return fetchWithTransientRetry(original, input, init);
145
179
  });
146
180
  const originalPreconnect = original.preconnect;
147
181
  if (originalPreconnect) patched.preconnect = originalPreconnect.bind(original);
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-fs-resolve.cjs","names":["Schema","bundleUrlToStagedPath","path","Context","Effect","readFile","cacheControlOf","Layer","ManagedRuntime","bundleCachePath","writeBundleCache","readBundleCache","isAppDescriptorPath","readFileSync","existsSync","bundleCacheRoot"],"sources":["../src/bundle-fs-resolve.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Context, Effect, Layer, ManagedRuntime, Schema } from \"effect\";\nimport { cacheControlOf } from \"every-plugin/build/artifact-names\";\nimport {\n bundleCachePath,\n bundleCacheRoot,\n readBundleCache,\n writeBundleCache,\n} from \"./bundle-cache\";\nimport { bundleUrlToStagedPath } from \"./bundle-path\";\nimport { isAppDescriptorPath } from \"./config\";\n\n/**\n * Local-first bundle resolution (ADR 0011 amendment; unified policy, ADR 0021\n * amendment): a self-contained runtime image stages its own namespace under\n * `BOS_BUNDLE_DIR` as `bundles/<account>/<gateway>/<workspace>/…` and consumes\n * those bytes directly from disk; a miss falls through to the network, so a\n * partially-staged namespace boots over the wire instead of failing. The\n * registry tier (children, `BOS_BUNDLE_DIR` unset) keeps the network fetch —\n * the namespace guard below only ever matches the runtime's own\n * account/gateway, never another runtime's layout.\n */\n\nexport interface BundleNamespace {\n readonly bundleDir: string;\n readonly account: string;\n readonly gateway: string;\n}\n\nexport class BundleReadError extends Schema.TaggedError<BundleReadError>()(\"BundleReadError\", {\n path: Schema.String,\n cause: Schema.optional(Schema.Unknown),\n}) {}\n\nconst MIME_TYPES: Record<string, string> = {\n \".js\": \"text/javascript\",\n \".mjs\": \"text/javascript\",\n \".cjs\": \"text/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".map\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".ico\": \"image/x-icon\",\n \".html\": \"text/html\",\n \".htm\": \"text/html\",\n \".webmanifest\": \"application/manifest+json\",\n \".md\": \"text/markdown\",\n \".ts\": \"text/plain\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".otf\": \"font/otf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".wasm\": \"application/wasm\",\n \".txt\": \"text/plain\",\n \".xml\": \"application/xml\",\n};\n\n/**\n * Own-namespace path for a bundle URL: the shared URL→staged-path mapping,\n * narrowed to the runtime identity — a URL whose encoded namespace differs\n * from `namespace.account`/`namespace.gateway` must fall to the network/cache\n * path, never serve a sibling namespace's staged bytes.\n */\nexport function bundleUrlToLocalPath(url: string, namespace: BundleNamespace): string | null {\n const filePath = bundleUrlToStagedPath(url, namespace.bundleDir);\n if (!filePath) return null;\n\n const nsDir = path.resolve(namespace.bundleDir, namespace.account, namespace.gateway);\n if (filePath !== nsDir && !filePath.startsWith(nsDir + path.sep)) return null;\n\n return filePath;\n}\n\nexport class BundleResolver extends Context.Service<\n BundleResolver,\n {\n /** Resolve a fetch target from the staged namespace; null = miss or not our namespace — fall through to the network. */\n readonly lookup: (input: string | URL | Request) => Effect.Effect<Response | null>;\n }\n>()(\"everything-dev/bundle-fs-resolve/BundleResolver\") {\n static layer(namespace: BundleNamespace): Layer.Layer<BundleResolver> {\n const respond = Effect.fn(\"BundleResolver.respond\")(function* (\n url: string,\n ): Effect.fn.Return<Response | null, never> {\n const filePath = bundleUrlToLocalPath(url, namespace);\n if (!filePath) return null;\n const bytes = yield* Effect.tryPromise({\n try: () => readFile(filePath),\n catch: (cause) => new BundleReadError({ path: filePath, cause }),\n }).pipe(Effect.catchTag(\"BundleReadError\", () => Effect.succeed(null)));\n if (bytes === null) return null;\n const name = path.basename(filePath);\n const contentType =\n MIME_TYPES[path.extname(name).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(new Uint8Array(bytes), {\n headers: {\n \"content-type\": contentType,\n \"cache-control\": cacheControlOf(name),\n etag: `\"${Buffer.from(bytes.subarray(0, 4096)).toString(\"base64\").slice(0, 24)}\"`,\n },\n });\n });\n\n return Layer.effect(\n BundleResolver,\n Effect.succeed(\n BundleResolver.of({\n lookup: (input) => {\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n return respond(url);\n },\n }),\n ),\n );\n }\n}\n\nexport interface BundleFetchHandle {\n readonly uninstall: () => Promise<void>;\n}\n\nexport interface BundleFetchOptions {\n /** Own-namespace FS resolution — requires a staged `BOS_BUNDLE_DIR`. */\n namespace?: BundleNamespace;\n /**\n * Foreign-namespace stale-if-error cache (child tier): `/bundles/…` URLs\n * outside the own namespace write through to disk on success and serve\n * the last-known-good bytes when the origin fails. Enabled implicitly\n * when `namespace` is present (deployment contexts), or explicitly via\n * `BOS_BUNDLE_CACHE_DIR`.\n */\n cacheDir?: string;\n}\n\ntype FetchImpl = typeof globalThis.fetch;\n\nconst staleResponse = (bytes: Uint8Array, url: string): Response => {\n const name = url.split(\"?\")[0].split(\"/\").pop() ?? \"\";\n const contentType = MIME_TYPES[path.extname(name).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(bytes.slice().buffer, {\n status: 200,\n headers: { \"content-type\": contentType, \"x-bundle-cache\": \"stale\" },\n });\n};\n\n/**\n * Installs the resolver as a global fetch interceptor — the one seam every\n * boot-time consumer shares (config manifest discovery, contract-type\n * fetches, orchestrator host loading, MF remoteEntry/identity probes).\n * One layered resolution policy: staged disk → network → stale-if-error.\n * Own-namespace URLs resolve from the staged directory and fall through to\n * the original fetch on a miss (a partially-staged namespace boots over the\n * wire instead of failing); a miss never touches the write-through cache —\n * for the own namespace the staged disk IS the resilience artifact. Foreign\n * `/bundles/…` URLs get the network with the stale-if-error cache; every\n * other URL falls through to the original fetch untouched. Inert when\n * neither a staged namespace nor a cache is configured.\n */\nexport function installGlobalBundleFetch(options: BundleFetchOptions): BundleFetchHandle {\n const namespace = options.namespace;\n const cacheDir = options.cacheDir;\n const runtime = namespace ? ManagedRuntime.make(BundleResolver.layer(namespace)) : null;\n const original: FetchImpl = globalThis.fetch;\n const patched = (async (input: Parameters<FetchImpl>[0], init?: Parameters<FetchImpl>[1]) => {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n if (namespace && bundleUrlToLocalPath(url, namespace)) {\n if (!runtime) throw new Error(\"unreachable: resolver runtime missing\");\n const response = await runtime.runPromise(\n Effect.gen(function* () {\n const resolver = yield* BundleResolver;\n return yield* resolver.lookup(input);\n }),\n );\n if (response !== null) return response;\n return original(input, init);\n }\n\n if (cacheDir !== undefined && bundleCachePath(url, { cacheDir })) {\n return (async () => {\n try {\n const response = await original(input, init);\n if (response.ok) {\n try {\n const bytes = new Uint8Array(await response.clone().arrayBuffer());\n await writeBundleCache(url, bytes, { cacheDir });\n } catch {\n // write-through is best-effort\n }\n } else {\n const cached = await readBundleCache(url, { cacheDir });\n if (cached) return staleResponse(cached, url);\n }\n return response;\n } catch (error) {\n const cached = await readBundleCache(url, { cacheDir });\n if (cached) return staleResponse(cached, url);\n throw error;\n }\n })();\n }\n\n return original(input, init);\n }) as FetchImpl;\n const originalPreconnect = (original as Partial<FetchImpl>).preconnect;\n if (originalPreconnect) {\n patched.preconnect = originalPreconnect.bind(original);\n }\n globalThis.fetch = patched;\n return {\n uninstall: async () => {\n if (globalThis.fetch === patched) {\n globalThis.fetch = original;\n }\n if (runtime) await runtime.dispose();\n },\n };\n}\n\n/**\n * Boot-time installer for the CLI: derives the own namespace from\n * `BOS_BUNDLE_DIR` plus the runtime identity (`BOS_ACCOUNT`/`BOS_GATEWAY`\n * registry env taking precedence over the bos.config.json fields), and the\n * foreign-namespace cache from `BOS_BUNDLE_CACHE_DIR`. Tier auto-detection\n * (ADR 0021): an identity whose namespace is not actually staged under the\n * bundle dir drops to the registry tier — plain network fetch — instead of\n * deterministically 404ing own-namespace URLs against a foreign image's\n * staged bytes. Never throws — a missing or malformed identity leaves the\n * default network fetch in place.\n */\nexport function installBundleFetchFromEnv(input: {\n configPath?: string | null;\n}): BundleFetchHandle | null {\n const bundleDir = process.env.BOS_BUNDLE_DIR;\n const cacheDir = process.env.BOS_BUNDLE_CACHE_DIR;\n if (!bundleDir && cacheDir === undefined) return null;\n\n let configAccount: string | undefined;\n let configGateway: string | undefined;\n const configPath = input.configPath;\n if (configPath && !isAppDescriptorPath(configPath)) {\n try {\n const parsed = JSON.parse(readFileSync(configPath, \"utf8\")) as {\n account?: string;\n domain?: string;\n };\n configAccount = parsed.account;\n configGateway = parsed.domain;\n } catch {\n // unreadable config — identity may still come from the registry env\n }\n }\n\n const account = process.env.BOS_ACCOUNT ?? configAccount;\n const gateway = process.env.BOS_GATEWAY ?? configGateway;\n if (bundleDir && (!account || !gateway)) return null;\n\n let namespace: BundleNamespace | undefined;\n if (bundleDir && account && gateway) {\n if (existsSync(path.join(bundleDir, account, gateway))) {\n namespace = { bundleDir, account, gateway };\n } else {\n console.warn(\n `[bundles] staged namespace ${account}/${gateway} not found under ${bundleDir} — registry tier (network fetch)`,\n );\n }\n }\n return installGlobalBundleFetch({\n namespace,\n cacheDir: cacheDir ?? (namespace ? bundleCacheRoot() : undefined),\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bundle-fs-resolve.cjs","names":["Schema","bundleUrlToStagedPath","path","Context","Effect","readFile","cacheControlOf","Layer","ManagedRuntime","bundleCachePath","writeBundleCache","readBundleCache","isAppDescriptorPath","readFileSync","existsSync","bundleCacheRoot"],"sources":["../src/bundle-fs-resolve.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Context, Effect, Layer, ManagedRuntime, Schema } from \"effect\";\nimport { cacheControlOf } from \"every-plugin/build/artifact-names\";\nimport {\n bundleCachePath,\n bundleCacheRoot,\n readBundleCache,\n writeBundleCache,\n} from \"./bundle-cache\";\nimport { bundleUrlToStagedPath } from \"./bundle-path\";\nimport { isAppDescriptorPath } from \"./config\";\n\n/**\n * Local-first bundle resolution (ADR 0011 amendment; unified policy, ADR 0021\n * amendment): a self-contained runtime image stages its own namespace under\n * `BOS_BUNDLE_DIR` as `bundles/<account>/<gateway>/<workspace>/…` and consumes\n * those bytes directly from disk; a miss falls through to the network, so a\n * partially-staged namespace boots over the wire instead of failing. The\n * registry tier (children, `BOS_BUNDLE_DIR` unset) keeps the network fetch —\n * the namespace guard below only ever matches the runtime's own\n * account/gateway, never another runtime's layout.\n */\n\nexport interface BundleNamespace {\n readonly bundleDir: string;\n readonly account: string;\n readonly gateway: string;\n}\n\nexport class BundleReadError extends Schema.TaggedError<BundleReadError>()(\"BundleReadError\", {\n path: Schema.String,\n cause: Schema.optional(Schema.Unknown),\n}) {}\n\nconst MIME_TYPES: Record<string, string> = {\n \".js\": \"text/javascript\",\n \".mjs\": \"text/javascript\",\n \".cjs\": \"text/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".map\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".ico\": \"image/x-icon\",\n \".html\": \"text/html\",\n \".htm\": \"text/html\",\n \".webmanifest\": \"application/manifest+json\",\n \".md\": \"text/markdown\",\n \".ts\": \"text/plain\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".otf\": \"font/otf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".wasm\": \"application/wasm\",\n \".txt\": \"text/plain\",\n \".xml\": \"application/xml\",\n};\n\n/**\n * Own-namespace path for a bundle URL: the shared URL→staged-path mapping,\n * narrowed to the runtime identity — a URL whose encoded namespace differs\n * from `namespace.account`/`namespace.gateway` must fall to the network/cache\n * path, never serve a sibling namespace's staged bytes.\n */\nexport function bundleUrlToLocalPath(url: string, namespace: BundleNamespace): string | null {\n const filePath = bundleUrlToStagedPath(url, namespace.bundleDir);\n if (!filePath) return null;\n\n const nsDir = path.resolve(namespace.bundleDir, namespace.account, namespace.gateway);\n if (filePath !== nsDir && !filePath.startsWith(nsDir + path.sep)) return null;\n\n return filePath;\n}\n\nexport class BundleResolver extends Context.Service<\n BundleResolver,\n {\n /** Resolve a fetch target from the staged namespace; null = miss or not our namespace — fall through to the network. */\n readonly lookup: (input: string | URL | Request) => Effect.Effect<Response | null>;\n }\n>()(\"everything-dev/bundle-fs-resolve/BundleResolver\") {\n static layer(namespace: BundleNamespace): Layer.Layer<BundleResolver> {\n const respond = Effect.fn(\"BundleResolver.respond\")(function* (\n url: string,\n ): Effect.fn.Return<Response | null, never> {\n const filePath = bundleUrlToLocalPath(url, namespace);\n if (!filePath) return null;\n const bytes = yield* Effect.tryPromise({\n try: () => readFile(filePath),\n catch: (cause) => new BundleReadError({ path: filePath, cause }),\n }).pipe(Effect.catchTag(\"BundleReadError\", () => Effect.succeed(null)));\n if (bytes === null) return null;\n const name = path.basename(filePath);\n const contentType =\n MIME_TYPES[path.extname(name).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(new Uint8Array(bytes), {\n headers: {\n \"content-type\": contentType,\n \"cache-control\": cacheControlOf(name),\n etag: `\"${Buffer.from(bytes.subarray(0, 4096)).toString(\"base64\").slice(0, 24)}\"`,\n },\n });\n });\n\n return Layer.effect(\n BundleResolver,\n Effect.succeed(\n BundleResolver.of({\n lookup: (input) => {\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n return respond(url);\n },\n }),\n ),\n );\n }\n}\n\nexport interface BundleFetchHandle {\n readonly uninstall: () => Promise<void>;\n}\n\nexport interface BundleFetchOptions {\n /** Own-namespace FS resolution — requires a staged `BOS_BUNDLE_DIR`. */\n namespace?: BundleNamespace;\n /**\n * Foreign-namespace stale-if-error cache (child tier): `/bundles/…` URLs\n * outside the own namespace write through to disk on success and serve\n * the last-known-good bytes when the origin fails. Enabled implicitly\n * when `namespace` is present (deployment contexts), or explicitly via\n * `BOS_BUNDLE_CACHE_DIR`.\n */\n cacheDir?: string;\n}\n\ntype FetchImpl = typeof globalThis.fetch;\n\nconst staleResponse = (bytes: Uint8Array, url: string): Response => {\n const name = url.split(\"?\")[0].split(\"/\").pop() ?? \"\";\n const contentType = MIME_TYPES[path.extname(name).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(bytes.slice().buffer, {\n status: 200,\n headers: { \"content-type\": contentType, \"x-bundle-cache\": \"stale\" },\n });\n};\n\nconst FETCH_RETRY_ATTEMPTS = 3;\nconst FETCH_RETRY_BASE_DELAY_MS = 250;\n\nconst isTransientStatus = (status: number): boolean => status === 429 || status >= 500;\n\nfunction requestMethod(input: Parameters<FetchImpl>[0], init?: Parameters<FetchImpl>[1]): string {\n if (init?.method) return init.method.toUpperCase();\n if (typeof input === \"string\" || input instanceof URL) return \"GET\";\n return input.method.toUpperCase();\n}\n\n/**\n * Bounded transient-failure retry for idempotent GETs crossing the\n * interceptor (ADR 0021 amendment — the CDN-only image): a cold boot has no\n * staged fallback, so one edge/registry hiccup during the load window would\n * hard-fail the host or a plugin with no recourse. Network errors and\n * 429/5xx responses retry twice (250ms, 750ms backoff) before propagating;\n * non-transient statuses and non-GET requests pass through untouched. After\n * exhausting attempts a stale-if-error cache still sees the final failure.\n */\nasync function fetchWithTransientRetry(\n original: FetchImpl,\n input: Parameters<FetchImpl>[0],\n init?: Parameters<FetchImpl>[1],\n): Promise<Response> {\n if (requestMethod(input, init) !== \"GET\") return original(input, init);\n let lastError: unknown;\n let lastResponse: Response | null = null;\n for (let attempt = 0; attempt < FETCH_RETRY_ATTEMPTS; attempt++) {\n if (attempt > 0) {\n await new Promise((resolve) =>\n setTimeout(resolve, FETCH_RETRY_BASE_DELAY_MS * 3 ** (attempt - 1)),\n );\n }\n try {\n const response = await original(input, init);\n if (!isTransientStatus(response.status)) return response;\n lastResponse = response;\n } catch (error) {\n lastError = error;\n }\n }\n if (lastResponse) return lastResponse;\n throw lastError;\n}\n\n/**\n * Installs the resolver as a global fetch interceptor — the one seam every\n * boot-time consumer shares (config manifest discovery, contract-type\n * fetches, orchestrator host loading, MF remoteEntry/identity probes).\n * One layered resolution policy: staged disk → network → stale-if-error.\n * Own-namespace URLs resolve from the staged directory and fall through to\n * the original fetch on a miss (a partially-staged namespace boots over the\n * wire instead of failing); a miss never touches the write-through cache —\n * for the own namespace the staged disk IS the resilience artifact. Foreign\n * `/bundles/…` URLs get the network with the stale-if-error cache; every\n * other URL falls through to the original fetch untouched. Inert when\n * neither a staged namespace nor a cache is configured.\n */\nexport function installGlobalBundleFetch(options: BundleFetchOptions): BundleFetchHandle {\n const namespace = options.namespace;\n const cacheDir = options.cacheDir;\n const runtime = namespace ? ManagedRuntime.make(BundleResolver.layer(namespace)) : null;\n const original: FetchImpl = globalThis.fetch;\n const patched = (async (input: Parameters<FetchImpl>[0], init?: Parameters<FetchImpl>[1]) => {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\n if (namespace && bundleUrlToLocalPath(url, namespace)) {\n if (!runtime) throw new Error(\"unreachable: resolver runtime missing\");\n const response = await runtime.runPromise(\n Effect.gen(function* () {\n const resolver = yield* BundleResolver;\n return yield* resolver.lookup(input);\n }),\n );\n if (response !== null) return response;\n return fetchWithTransientRetry(original, input, init);\n }\n\n if (cacheDir !== undefined && bundleCachePath(url, { cacheDir })) {\n return (async () => {\n try {\n const response = await fetchWithTransientRetry(original, input, init);\n if (response.ok) {\n try {\n const bytes = new Uint8Array(await response.clone().arrayBuffer());\n await writeBundleCache(url, bytes, { cacheDir });\n } catch {\n // write-through is best-effort\n }\n } else {\n const cached = await readBundleCache(url, { cacheDir });\n if (cached) return staleResponse(cached, url);\n }\n return response;\n } catch (error) {\n const cached = await readBundleCache(url, { cacheDir });\n if (cached) return staleResponse(cached, url);\n throw error;\n }\n })();\n }\n\n return fetchWithTransientRetry(original, input, init);\n }) as FetchImpl;\n const originalPreconnect = (original as Partial<FetchImpl>).preconnect;\n if (originalPreconnect) {\n patched.preconnect = originalPreconnect.bind(original);\n }\n globalThis.fetch = patched;\n return {\n uninstall: async () => {\n if (globalThis.fetch === patched) {\n globalThis.fetch = original;\n }\n if (runtime) await runtime.dispose();\n },\n };\n}\n\n/**\n * Boot-time installer for the CLI: derives the own namespace from\n * `BOS_BUNDLE_DIR` plus the runtime identity (`BOS_ACCOUNT`/`BOS_GATEWAY`\n * registry env taking precedence over the bos.config.json fields), and the\n * foreign-namespace cache from `BOS_BUNDLE_CACHE_DIR`. Tier auto-detection\n * (ADR 0021): an identity whose namespace is not actually staged under the\n * bundle dir drops to the registry tier — plain network fetch — instead of\n * deterministically 404ing own-namespace URLs against a foreign image's\n * staged bytes. Never throws — a missing or malformed identity leaves the\n * default network fetch in place.\n */\nexport function installBundleFetchFromEnv(input: {\n configPath?: string | null;\n}): BundleFetchHandle | null {\n const bundleDir = process.env.BOS_BUNDLE_DIR;\n const cacheDir = process.env.BOS_BUNDLE_CACHE_DIR;\n if (!bundleDir && cacheDir === undefined) return null;\n\n let configAccount: string | undefined;\n let configGateway: string | undefined;\n const configPath = input.configPath;\n if (configPath && !isAppDescriptorPath(configPath)) {\n try {\n const parsed = JSON.parse(readFileSync(configPath, \"utf8\")) as {\n account?: string;\n domain?: string;\n };\n configAccount = parsed.account;\n configGateway = parsed.domain;\n } catch {\n // unreadable config — identity may still come from the registry env\n }\n }\n\n const account = process.env.BOS_ACCOUNT ?? configAccount;\n const gateway = process.env.BOS_GATEWAY ?? configGateway;\n if (bundleDir && (!account || !gateway)) return null;\n\n let namespace: BundleNamespace | undefined;\n if (bundleDir && account && gateway) {\n if (existsSync(path.join(bundleDir, account, gateway))) {\n namespace = { bundleDir, account, gateway };\n } else {\n console.warn(\n `[bundles] staged namespace ${account}/${gateway} not found under ${bundleDir} — registry tier (network fetch)`,\n );\n }\n }\n return installGlobalBundleFetch({\n namespace,\n cacheDir: cacheDir ?? (namespace ? bundleCacheRoot() : undefined),\n });\n}\n"],"mappings":"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"}
@@ -92,6 +92,40 @@ const staleResponse = (bytes, url) => {
92
92
  }
93
93
  });
94
94
  };
95
+ const FETCH_RETRY_ATTEMPTS = 3;
96
+ const FETCH_RETRY_BASE_DELAY_MS = 250;
97
+ const isTransientStatus = (status) => status === 429 || status >= 500;
98
+ function requestMethod(input, init) {
99
+ if (init?.method) return init.method.toUpperCase();
100
+ if (typeof input === "string" || input instanceof URL) return "GET";
101
+ return input.method.toUpperCase();
102
+ }
103
+ /**
104
+ * Bounded transient-failure retry for idempotent GETs crossing the
105
+ * interceptor (ADR 0021 amendment — the CDN-only image): a cold boot has no
106
+ * staged fallback, so one edge/registry hiccup during the load window would
107
+ * hard-fail the host or a plugin with no recourse. Network errors and
108
+ * 429/5xx responses retry twice (250ms, 750ms backoff) before propagating;
109
+ * non-transient statuses and non-GET requests pass through untouched. After
110
+ * exhausting attempts a stale-if-error cache still sees the final failure.
111
+ */
112
+ async function fetchWithTransientRetry(original, input, init) {
113
+ if (requestMethod(input, init) !== "GET") return original(input, init);
114
+ let lastError;
115
+ let lastResponse = null;
116
+ for (let attempt = 0; attempt < FETCH_RETRY_ATTEMPTS; attempt++) {
117
+ if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, FETCH_RETRY_BASE_DELAY_MS * 3 ** (attempt - 1)));
118
+ try {
119
+ const response = await original(input, init);
120
+ if (!isTransientStatus(response.status)) return response;
121
+ lastResponse = response;
122
+ } catch (error) {
123
+ lastError = error;
124
+ }
125
+ }
126
+ if (lastResponse) return lastResponse;
127
+ throw lastError;
128
+ }
95
129
  /**
96
130
  * Installs the resolver as a global fetch interceptor — the one seam every
97
131
  * boot-time consumer shares (config manifest discovery, contract-type
@@ -118,11 +152,11 @@ function installGlobalBundleFetch(options) {
118
152
  return yield* (yield* BundleResolver).lookup(input);
119
153
  }));
120
154
  if (response !== null) return response;
121
- return original(input, init);
155
+ return fetchWithTransientRetry(original, input, init);
122
156
  }
123
157
  if (cacheDir !== void 0 && bundleCachePath(url, { cacheDir })) return (async () => {
124
158
  try {
125
- const response = await original(input, init);
159
+ const response = await fetchWithTransientRetry(original, input, init);
126
160
  if (response.ok) try {
127
161
  const bytes = new Uint8Array(await response.clone().arrayBuffer());
128
162
  await writeBundleCache(url, bytes, { cacheDir });
@@ -138,7 +172,7 @@ function installGlobalBundleFetch(options) {
138
172
  throw error;
139
173
  }
140
174
  })();
141
- return original(input, init);
175
+ return fetchWithTransientRetry(original, input, init);
142
176
  });
143
177
  const originalPreconnect = original.preconnect;
144
178
  if (originalPreconnect) patched.preconnect = originalPreconnect.bind(original);