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.
- package/dist/api-contract.cjs +3 -6
- package/dist/api-contract.cjs.map +1 -1
- package/dist/api-contract.mjs +3 -6
- package/dist/api-contract.mjs.map +1 -1
- package/dist/apps/upload/src/client.cjs +188 -0
- package/dist/apps/upload/src/client.cjs.map +1 -0
- package/dist/apps/upload/src/client.mjs +180 -0
- package/dist/apps/upload/src/client.mjs.map +1 -0
- package/dist/bundle-fs-resolve.cjs +37 -3
- package/dist/bundle-fs-resolve.cjs.map +1 -1
- package/dist/bundle-fs-resolve.mjs +37 -3
- package/dist/bundle-fs-resolve.mjs.map +1 -1
- package/dist/cdn-deploy.cjs +29 -11
- package/dist/cdn-deploy.cjs.map +1 -1
- package/dist/cdn-deploy.mjs +29 -11
- package/dist/cdn-deploy.mjs.map +1 -1
- package/dist/cli/infra.cjs +4 -2
- package/dist/cli/infra.cjs.map +1 -1
- package/dist/cli/infra.mjs +4 -2
- package/dist/cli/infra.mjs.map +1 -1
- package/dist/cli/init.cjs +0 -2
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +1 -1
- package/dist/cli/init.d.mts +1 -1
- package/dist/cli/init.mjs +0 -2
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/sync.cjs +0 -1
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +0 -1
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/commands/deploy.cjs +0 -2
- package/dist/commands/deploy.cjs.map +1 -1
- package/dist/commands/deploy.mjs +0 -2
- package/dist/commands/deploy.mjs.map +1 -1
- package/dist/commands/upgrade.cjs +30 -7
- package/dist/commands/upgrade.cjs.map +1 -1
- package/dist/commands/upgrade.mjs +30 -7
- package/dist/commands/upgrade.mjs.map +1 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.mjs.map +1 -1
- package/dist/contract.meta.cjs +1 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +1 -1
- package/dist/contract.meta.d.mts +1 -1
- package/dist/contract.meta.mjs +1 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/dev-program.cjs +2 -2
- package/dist/dev-program.cjs.map +1 -1
- package/dist/dev-program.mjs +2 -3
- package/dist/dev-program.mjs.map +1 -1
- package/dist/image-deploy.cjs +38 -14
- package/dist/image-deploy.cjs.map +1 -1
- package/dist/image-deploy.mjs +39 -15
- package/dist/image-deploy.mjs.map +1 -1
- package/dist/orchestrator.cjs +22 -18
- package/dist/orchestrator.cjs.map +1 -1
- package/dist/orchestrator.mjs +22 -18
- package/dist/orchestrator.mjs.map +1 -1
- package/dist/storage-upload.cjs +7 -68
- package/dist/storage-upload.cjs.map +1 -1
- package/dist/storage-upload.mjs +7 -68
- package/dist/storage-upload.mjs.map +1 -1
- package/dist/ui/api.cjs +11 -69
- package/dist/ui/api.cjs.map +1 -1
- package/dist/ui/api.d.cts +2 -4
- package/dist/ui/api.d.mts +2 -4
- package/dist/ui/api.mjs +11 -69
- package/dist/ui/api.mjs.map +1 -1
- package/dist/ui/auth.d.cts +43 -43
- package/dist/ui/auth.d.mts +43 -43
- package/package.json +3 -2
package/dist/storage-upload.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
const require_client = require('./apps/upload/src/client.cjs');
|
|
1
2
|
let node_path = require("node:path");
|
|
2
|
-
let effect = require("effect");
|
|
3
3
|
let node_fs_promises = require("node:fs/promises");
|
|
4
4
|
|
|
5
5
|
//#region src/storage-upload.ts
|
|
@@ -23,47 +23,12 @@ async function walkDist(dir, baseDir) {
|
|
|
23
23
|
async function collectDistFiles(distDir) {
|
|
24
24
|
return walkDist(distDir, distDir);
|
|
25
25
|
}
|
|
26
|
-
const STATUS_ATTEMPTS = 3;
|
|
27
|
-
const TRANSPORT_ATTEMPTS = 5;
|
|
28
|
-
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
29
|
-
408,
|
|
30
|
-
429,
|
|
31
|
-
500,
|
|
32
|
-
502,
|
|
33
|
-
503,
|
|
34
|
-
504
|
|
35
|
-
]);
|
|
36
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
|
-
const backoffMs = (attempt) => 1e3 * attempt + Math.floor(Math.random() * 500);
|
|
38
|
-
var BundleUploadError = class BundleUploadError extends effect.Data.TaggedError("BundleUploadError") {
|
|
39
|
-
constructor(status, detail, note) {
|
|
40
|
-
super({
|
|
41
|
-
status,
|
|
42
|
-
detail,
|
|
43
|
-
note
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
get message() {
|
|
47
|
-
return `${this.detail}${this.note ?? ""}`;
|
|
48
|
-
}
|
|
49
|
-
withNote(note) {
|
|
50
|
-
return new BundleUploadError(this.status, this.detail, `${this.note ?? ""}${note}`);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
function describeUploadFailure(status, detail) {
|
|
54
|
-
const trimmed = detail.slice(0, 300);
|
|
55
|
-
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)`;
|
|
56
|
-
try {
|
|
57
|
-
const parsed = JSON.parse(detail);
|
|
58
|
-
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`;
|
|
59
|
-
} catch {}
|
|
60
|
-
return trimmed;
|
|
61
|
-
}
|
|
62
26
|
async function uploadBundle(input) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
27
|
+
return await require_client.createUploadClient({
|
|
28
|
+
origin: input.origin,
|
|
29
|
+
apiKey: input.apiKey,
|
|
30
|
+
fetchImpl: input.fetchImpl
|
|
31
|
+
}).uploadBundle({
|
|
67
32
|
account: input.account,
|
|
68
33
|
gateway: input.gateway,
|
|
69
34
|
workspace: input.workspace,
|
|
@@ -72,33 +37,6 @@ async function uploadBundle(input) {
|
|
|
72
37
|
contentBase64: Buffer.from(file.bytes).toString("base64")
|
|
73
38
|
}))
|
|
74
39
|
});
|
|
75
|
-
let lastError;
|
|
76
|
-
let transportFailures = 0;
|
|
77
|
-
let statusFailures = 0;
|
|
78
|
-
for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {
|
|
79
|
-
try {
|
|
80
|
-
const response = await fetchImpl(`${input.origin.replace(/\/$/, "")}/api/storage/bundles`, {
|
|
81
|
-
method: "POST",
|
|
82
|
-
headers: transportFailures > 0 ? {
|
|
83
|
-
...headers,
|
|
84
|
-
connection: "close"
|
|
85
|
-
} : headers,
|
|
86
|
-
body
|
|
87
|
-
});
|
|
88
|
-
if (response.ok) return await response.json();
|
|
89
|
-
const detail = await response.text().catch(() => "");
|
|
90
|
-
lastError = new BundleUploadError(response.status, `[publish] bundle upload for ${input.workspace} failed: ${response.status} ${describeUploadFailure(response.status, detail)}`);
|
|
91
|
-
if (!RETRYABLE_STATUSES.has(response.status)) break;
|
|
92
|
-
statusFailures += 1;
|
|
93
|
-
if (statusFailures >= STATUS_ATTEMPTS) break;
|
|
94
|
-
} catch (error) {
|
|
95
|
-
transportFailures += 1;
|
|
96
|
-
lastError = new BundleUploadError(0, `[publish] bundle upload for ${input.workspace} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
97
|
-
}
|
|
98
|
-
if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));
|
|
99
|
-
}
|
|
100
|
-
if (lastError instanceof BundleUploadError && lastError.status === 0) lastError = lastError.withNote(" (Bun fetch note: re-run with fetch verbose diagnostics for detail)");
|
|
101
|
-
throw lastError ?? /* @__PURE__ */ new Error(`[publish] bundle upload for ${input.workspace} failed`);
|
|
102
40
|
}
|
|
103
41
|
async function uploadWorkspaceDist(input) {
|
|
104
42
|
const maxRequestBytes = input.maxRequestBytes ?? (Number(process.env.BOS_MAX_BUNDLE_UPLOAD_BYTES) || 67108864);
|
|
@@ -122,6 +60,7 @@ async function uploadWorkspaceDist(input) {
|
|
|
122
60
|
});
|
|
123
61
|
merged.stored += result.stored;
|
|
124
62
|
merged.totalBytes += result.totalBytes;
|
|
63
|
+
merged.unchanged = (merged.unchanged ?? 0) + (result.unchanged ?? 0);
|
|
125
64
|
Object.assign(merged.integrity, result.integrity);
|
|
126
65
|
merged.storage = result.storage;
|
|
127
66
|
batch = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-upload.cjs","names":["readdir","join","stat","relative","sep","readFile","Data"],"sources":["../src/storage-upload.ts"],"sourcesContent":["import { readdir, readFile, stat } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\nimport { Data } from \"effect\";\n\nexport interface DistFile {\n path: string;\n bytes: Uint8Array;\n}\n\nexport interface BundleUploadResult {\n stored: number;\n totalBytes: number;\n integrity: Record<string, string>;\n storage?: \"s3\" | \"memory\";\n}\n\nasync function walkDist(dir: string, baseDir: string): Promise<DistFile[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: DistFile[] = [];\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await walkDist(fullPath, baseDir)));\n continue;\n }\n const info = await stat(fullPath);\n if (!info.isFile()) continue;\n files.push({\n path: relative(baseDir, fullPath).split(sep).join(\"/\"),\n bytes: new Uint8Array(await readFile(fullPath)),\n });\n }\n return files;\n}\n\nexport async function collectDistFiles(distDir: string): Promise<DistFile[]> {\n return walkDist(distDir, distDir);\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 BundleUploadError extends Data.TaggedError(\"BundleUploadError\")<{\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 BundleUploadError(this.status, this.detail, `${this.note ?? \"\"}${note}`);\n }\n}\n\nfunction describeUploadFailure(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\nexport async function uploadBundle(input: {\n origin: string;\n apiKey?: string;\n account: string;\n gateway: string;\n workspace: string;\n files: DistFile[];\n fetchImpl?: typeof fetch;\n}): Promise<BundleUploadResult> {\n const fetchImpl = input.fetchImpl ?? fetch;\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n if (input.apiKey) headers[\"x-api-key\"] = input.apiKey;\n\n const body = JSON.stringify({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n files: input.files.map((file) => ({\n path: file.path,\n contentBase64: Buffer.from(file.bytes).toString(\"base64\"),\n })),\n });\n\n let lastError: BundleUploadError | 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(`${input.origin.replace(/\\/$/, \"\")}/api/storage/bundles`, {\n method: \"POST\",\n headers:\n transportFailures > 0\n ? // a transport failure killed the pooled keepalive socket — do not\n // reuse it (the \"socket connection closed unexpectedly\" incident)\n { ...headers, connection: \"close\" }\n : headers,\n body,\n });\n\n if (response.ok) {\n return (await response.json()) as BundleUploadResult;\n }\n\n const detail = await response.text().catch(() => \"\");\n lastError = new BundleUploadError(\n response.status,\n `[publish] bundle upload for ${input.workspace} failed: ${response.status} ${describeUploadFailure(response.status, detail)}`,\n );\n // Retries are safe: the storage route stores by account/gateway/\n // workspace/path, so re-posting a batch overwrites idempotently.\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 BundleUploadError(\n 0,\n `[publish] bundle upload for ${input.workspace} 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 BundleUploadError && 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(`[publish] bundle upload for ${input.workspace} failed`);\n}\n\nexport async function uploadWorkspaceDist(input: {\n origin: string;\n apiKey?: string;\n account: string;\n gateway: string;\n workspace: string;\n files: DistFile[];\n maxRequestBytes?: number;\n}): Promise<BundleUploadResult> {\n const maxRequestBytes =\n input.maxRequestBytes ?? (Number(process.env.BOS_MAX_BUNDLE_UPLOAD_BYTES) || 64 * 1024 * 1024);\n\n const merged: BundleUploadResult = { stored: 0, totalBytes: 0, integrity: {} };\n let batch: DistFile[] = [];\n let batchBytes = 0;\n const overheadPerFile = (file: DistFile): number =>\n Math.ceil(file.bytes.byteLength / 3) * 4 + file.path.length + 64;\n\n const flush = async () => {\n if (batch.length === 0) return;\n const result = await uploadBundle({\n origin: input.origin,\n apiKey: input.apiKey,\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n files: batch,\n });\n merged.stored += result.stored;\n merged.totalBytes += result.totalBytes;\n Object.assign(merged.integrity, result.integrity);\n merged.storage = result.storage;\n batch = [];\n batchBytes = 0;\n };\n\n for (const file of input.files) {\n const cost = overheadPerFile(file);\n if (cost > maxRequestBytes) {\n throw new Error(\n `[publish] ${input.workspace}/${file.path} (${file.bytes.byteLength} bytes) exceeds the per-request upload ceiling`,\n );\n }\n if (batchBytes + cost > maxRequestBytes) {\n await flush();\n }\n batch.push(file);\n batchBytes += cost;\n }\n await flush();\n\n return merged;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage-upload.cjs","names":["readdir","join","stat","relative","sep","readFile","createUploadClient"],"sources":["../src/storage-upload.ts"],"sourcesContent":["import { readdir, readFile, stat } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\nimport { createUploadClient } from \"@everything-dev/upload-app/client\";\n\nexport interface DistFile {\n path: string;\n bytes: Uint8Array;\n}\n\nexport interface BundleUploadResult {\n stored: number;\n unchanged?: number;\n totalBytes: number;\n integrity: Record<string, string>;\n storage?: \"s3\" | \"memory\";\n}\n\nasync function walkDist(dir: string, baseDir: string): Promise<DistFile[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: DistFile[] = [];\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await walkDist(fullPath, baseDir)));\n continue;\n }\n const info = await stat(fullPath);\n if (!info.isFile()) continue;\n files.push({\n path: relative(baseDir, fullPath).split(sep).join(\"/\"),\n bytes: new Uint8Array(await readFile(fullPath)),\n });\n }\n return files;\n}\n\nexport async function collectDistFiles(distDir: string): Promise<DistFile[]> {\n return walkDist(distDir, distDir);\n}\n\nexport async function uploadBundle(input: {\n origin: string;\n apiKey?: string;\n account: string;\n gateway: string;\n workspace: string;\n files: DistFile[];\n fetchImpl?: typeof fetch;\n}): Promise<BundleUploadResult> {\n const client = createUploadClient({\n origin: input.origin,\n apiKey: input.apiKey,\n fetchImpl: input.fetchImpl,\n });\n const result = await client.uploadBundle({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n files: input.files.map((file) => ({\n path: file.path,\n contentBase64: Buffer.from(file.bytes).toString(\"base64\"),\n })),\n });\n return result;\n}\n\nexport async function uploadWorkspaceDist(input: {\n origin: string;\n apiKey?: string;\n account: string;\n gateway: string;\n workspace: string;\n files: DistFile[];\n maxRequestBytes?: number;\n}): Promise<BundleUploadResult> {\n const maxRequestBytes =\n input.maxRequestBytes ?? (Number(process.env.BOS_MAX_BUNDLE_UPLOAD_BYTES) || 64 * 1024 * 1024);\n\n const merged: BundleUploadResult = { stored: 0, totalBytes: 0, integrity: {} };\n let batch: DistFile[] = [];\n let batchBytes = 0;\n const overheadPerFile = (file: DistFile): number =>\n Math.ceil(file.bytes.byteLength / 3) * 4 + file.path.length + 64;\n\n const flush = async () => {\n if (batch.length === 0) return;\n const result = await uploadBundle({\n origin: input.origin,\n apiKey: input.apiKey,\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n files: batch,\n });\n merged.stored += result.stored;\n merged.totalBytes += result.totalBytes;\n merged.unchanged = (merged.unchanged ?? 0) + (result.unchanged ?? 0);\n Object.assign(merged.integrity, result.integrity);\n merged.storage = result.storage;\n batch = [];\n batchBytes = 0;\n };\n\n for (const file of input.files) {\n const cost = overheadPerFile(file);\n if (cost > maxRequestBytes) {\n throw new Error(\n `[publish] ${input.workspace}/${file.path} (${file.bytes.byteLength} bytes) exceeds the per-request upload ceiling`,\n );\n }\n if (batchBytes + cost > maxRequestBytes) {\n await flush();\n }\n batch.push(file);\n batchBytes += cost;\n }\n await flush();\n\n return merged;\n}\n"],"mappings":";;;;;AAiBA,eAAe,SAAS,KAAa,SAAsC;CACzE,MAAM,UAAU,UAAMA,0BAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAC1D,MAAM,QAAoB,CAAC;CAC3B,KAAK,MAAM,SAAS,QAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC,GAAG;EACxE,MAAM,eAAWC,gBAAK,KAAK,MAAM,IAAI;EACrC,IAAI,MAAM,YAAY,GAAG;GACvB,MAAM,KAAK,GAAI,MAAM,SAAS,UAAU,OAAO,CAAE;GACjD;EACF;EAEA,IAAI,EAAC,UADcC,uBAAK,QAAQ,EACvB,CAAC,OAAO,GAAG;EACpB,MAAM,KAAK;GACT,UAAMC,oBAAS,SAAS,QAAQ,CAAC,CAAC,MAAMC,aAAG,CAAC,CAAC,KAAK,GAAG;GACrD,OAAO,IAAI,WAAW,UAAMC,2BAAS,QAAQ,CAAC;EAChD,CAAC;CACH;CACA,OAAO;AACT;AAEA,eAAsB,iBAAiB,SAAsC;CAC3E,OAAO,SAAS,SAAS,OAAO;AAClC;AAEA,eAAsB,aAAa,OAQH;CAe9B,OAAO,MAdQC,kCAAmB;EAChC,QAAQ,MAAM;EACd,QAAQ,MAAM;EACd,WAAW,MAAM;CACnB,CAC0B,CAAC,CAAC,aAAa;EACvC,SAAS,MAAM;EACf,SAAS,MAAM;EACf,WAAW,MAAM;EACjB,OAAO,MAAM,MAAM,KAAK,UAAU;GAChC,MAAM,KAAK;GACX,eAAe,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC,SAAS,QAAQ;EAC1D,EAAE;CACJ,CAAC;AAEH;AAEA,eAAsB,oBAAoB,OAQV;CAC9B,MAAM,kBACJ,MAAM,oBAAoB,OAAO,QAAQ,IAAI,2BAA2B,KAAK;CAE/E,MAAM,SAA6B;EAAE,QAAQ;EAAG,YAAY;EAAG,WAAW,CAAC;CAAE;CAC7E,IAAI,QAAoB,CAAC;CACzB,IAAI,aAAa;CACjB,MAAM,mBAAmB,SACvB,KAAK,KAAK,KAAK,MAAM,aAAa,CAAC,IAAI,IAAI,KAAK,KAAK,SAAS;CAEhE,MAAM,QAAQ,YAAY;EACxB,IAAI,MAAM,WAAW,GAAG;EACxB,MAAM,SAAS,MAAM,aAAa;GAChC,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,SAAS,MAAM;GACf,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,OAAO;EACT,CAAC;EACD,OAAO,UAAU,OAAO;EACxB,OAAO,cAAc,OAAO;EAC5B,OAAO,aAAa,OAAO,aAAa,MAAM,OAAO,aAAa;EAClE,OAAO,OAAO,OAAO,WAAW,OAAO,SAAS;EAChD,OAAO,UAAU,OAAO;EACxB,QAAQ,CAAC;EACT,aAAa;CACf;CAEA,KAAK,MAAM,QAAQ,MAAM,OAAO;EAC9B,MAAM,OAAO,gBAAgB,IAAI;EACjC,IAAI,OAAO,iBACT,MAAM,IAAI,MACR,aAAa,MAAM,UAAU,GAAG,KAAK,KAAK,IAAI,KAAK,MAAM,WAAW,+CACtE;EAEF,IAAI,aAAa,OAAO,iBACtB,MAAM,MAAM;EAEd,MAAM,KAAK,IAAI;EACf,cAAc;CAChB;CACA,MAAM,MAAM;CAEZ,OAAO;AACT"}
|
package/dist/storage-upload.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { createUploadClient } from "./apps/upload/src/client.mjs";
|
|
1
2
|
import { join, relative, sep } from "node:path";
|
|
2
|
-
import { Data } from "effect";
|
|
3
3
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
4
4
|
|
|
5
5
|
//#region src/storage-upload.ts
|
|
@@ -23,47 +23,12 @@ async function walkDist(dir, baseDir) {
|
|
|
23
23
|
async function collectDistFiles(distDir) {
|
|
24
24
|
return walkDist(distDir, distDir);
|
|
25
25
|
}
|
|
26
|
-
const STATUS_ATTEMPTS = 3;
|
|
27
|
-
const TRANSPORT_ATTEMPTS = 5;
|
|
28
|
-
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
29
|
-
408,
|
|
30
|
-
429,
|
|
31
|
-
500,
|
|
32
|
-
502,
|
|
33
|
-
503,
|
|
34
|
-
504
|
|
35
|
-
]);
|
|
36
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
|
-
const backoffMs = (attempt) => 1e3 * attempt + Math.floor(Math.random() * 500);
|
|
38
|
-
var BundleUploadError = class BundleUploadError extends Data.TaggedError("BundleUploadError") {
|
|
39
|
-
constructor(status, detail, note) {
|
|
40
|
-
super({
|
|
41
|
-
status,
|
|
42
|
-
detail,
|
|
43
|
-
note
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
get message() {
|
|
47
|
-
return `${this.detail}${this.note ?? ""}`;
|
|
48
|
-
}
|
|
49
|
-
withNote(note) {
|
|
50
|
-
return new BundleUploadError(this.status, this.detail, `${this.note ?? ""}${note}`);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
function describeUploadFailure(status, detail) {
|
|
54
|
-
const trimmed = detail.slice(0, 300);
|
|
55
|
-
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)`;
|
|
56
|
-
try {
|
|
57
|
-
const parsed = JSON.parse(detail);
|
|
58
|
-
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`;
|
|
59
|
-
} catch {}
|
|
60
|
-
return trimmed;
|
|
61
|
-
}
|
|
62
26
|
async function uploadBundle(input) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
27
|
+
return await createUploadClient({
|
|
28
|
+
origin: input.origin,
|
|
29
|
+
apiKey: input.apiKey,
|
|
30
|
+
fetchImpl: input.fetchImpl
|
|
31
|
+
}).uploadBundle({
|
|
67
32
|
account: input.account,
|
|
68
33
|
gateway: input.gateway,
|
|
69
34
|
workspace: input.workspace,
|
|
@@ -72,33 +37,6 @@ async function uploadBundle(input) {
|
|
|
72
37
|
contentBase64: Buffer.from(file.bytes).toString("base64")
|
|
73
38
|
}))
|
|
74
39
|
});
|
|
75
|
-
let lastError;
|
|
76
|
-
let transportFailures = 0;
|
|
77
|
-
let statusFailures = 0;
|
|
78
|
-
for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {
|
|
79
|
-
try {
|
|
80
|
-
const response = await fetchImpl(`${input.origin.replace(/\/$/, "")}/api/storage/bundles`, {
|
|
81
|
-
method: "POST",
|
|
82
|
-
headers: transportFailures > 0 ? {
|
|
83
|
-
...headers,
|
|
84
|
-
connection: "close"
|
|
85
|
-
} : headers,
|
|
86
|
-
body
|
|
87
|
-
});
|
|
88
|
-
if (response.ok) return await response.json();
|
|
89
|
-
const detail = await response.text().catch(() => "");
|
|
90
|
-
lastError = new BundleUploadError(response.status, `[publish] bundle upload for ${input.workspace} failed: ${response.status} ${describeUploadFailure(response.status, detail)}`);
|
|
91
|
-
if (!RETRYABLE_STATUSES.has(response.status)) break;
|
|
92
|
-
statusFailures += 1;
|
|
93
|
-
if (statusFailures >= STATUS_ATTEMPTS) break;
|
|
94
|
-
} catch (error) {
|
|
95
|
-
transportFailures += 1;
|
|
96
|
-
lastError = new BundleUploadError(0, `[publish] bundle upload for ${input.workspace} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
97
|
-
}
|
|
98
|
-
if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));
|
|
99
|
-
}
|
|
100
|
-
if (lastError instanceof BundleUploadError && lastError.status === 0) lastError = lastError.withNote(" (Bun fetch note: re-run with fetch verbose diagnostics for detail)");
|
|
101
|
-
throw lastError ?? /* @__PURE__ */ new Error(`[publish] bundle upload for ${input.workspace} failed`);
|
|
102
40
|
}
|
|
103
41
|
async function uploadWorkspaceDist(input) {
|
|
104
42
|
const maxRequestBytes = input.maxRequestBytes ?? (Number(process.env.BOS_MAX_BUNDLE_UPLOAD_BYTES) || 67108864);
|
|
@@ -122,6 +60,7 @@ async function uploadWorkspaceDist(input) {
|
|
|
122
60
|
});
|
|
123
61
|
merged.stored += result.stored;
|
|
124
62
|
merged.totalBytes += result.totalBytes;
|
|
63
|
+
merged.unchanged = (merged.unchanged ?? 0) + (result.unchanged ?? 0);
|
|
125
64
|
Object.assign(merged.integrity, result.integrity);
|
|
126
65
|
merged.storage = result.storage;
|
|
127
66
|
batch = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-upload.mjs","names":[],"sources":["../src/storage-upload.ts"],"sourcesContent":["import { readdir, readFile, stat } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\nimport { Data } from \"effect\";\n\nexport interface DistFile {\n path: string;\n bytes: Uint8Array;\n}\n\nexport interface BundleUploadResult {\n stored: number;\n totalBytes: number;\n integrity: Record<string, string>;\n storage?: \"s3\" | \"memory\";\n}\n\nasync function walkDist(dir: string, baseDir: string): Promise<DistFile[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: DistFile[] = [];\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await walkDist(fullPath, baseDir)));\n continue;\n }\n const info = await stat(fullPath);\n if (!info.isFile()) continue;\n files.push({\n path: relative(baseDir, fullPath).split(sep).join(\"/\"),\n bytes: new Uint8Array(await readFile(fullPath)),\n });\n }\n return files;\n}\n\nexport async function collectDistFiles(distDir: string): Promise<DistFile[]> {\n return walkDist(distDir, distDir);\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 BundleUploadError extends Data.TaggedError(\"BundleUploadError\")<{\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 BundleUploadError(this.status, this.detail, `${this.note ?? \"\"}${note}`);\n }\n}\n\nfunction describeUploadFailure(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\nexport async function uploadBundle(input: {\n origin: string;\n apiKey?: string;\n account: string;\n gateway: string;\n workspace: string;\n files: DistFile[];\n fetchImpl?: typeof fetch;\n}): Promise<BundleUploadResult> {\n const fetchImpl = input.fetchImpl ?? fetch;\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n if (input.apiKey) headers[\"x-api-key\"] = input.apiKey;\n\n const body = JSON.stringify({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n files: input.files.map((file) => ({\n path: file.path,\n contentBase64: Buffer.from(file.bytes).toString(\"base64\"),\n })),\n });\n\n let lastError: BundleUploadError | 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(`${input.origin.replace(/\\/$/, \"\")}/api/storage/bundles`, {\n method: \"POST\",\n headers:\n transportFailures > 0\n ? // a transport failure killed the pooled keepalive socket — do not\n // reuse it (the \"socket connection closed unexpectedly\" incident)\n { ...headers, connection: \"close\" }\n : headers,\n body,\n });\n\n if (response.ok) {\n return (await response.json()) as BundleUploadResult;\n }\n\n const detail = await response.text().catch(() => \"\");\n lastError = new BundleUploadError(\n response.status,\n `[publish] bundle upload for ${input.workspace} failed: ${response.status} ${describeUploadFailure(response.status, detail)}`,\n );\n // Retries are safe: the storage route stores by account/gateway/\n // workspace/path, so re-posting a batch overwrites idempotently.\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 BundleUploadError(\n 0,\n `[publish] bundle upload for ${input.workspace} 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 BundleUploadError && 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(`[publish] bundle upload for ${input.workspace} failed`);\n}\n\nexport async function uploadWorkspaceDist(input: {\n origin: string;\n apiKey?: string;\n account: string;\n gateway: string;\n workspace: string;\n files: DistFile[];\n maxRequestBytes?: number;\n}): Promise<BundleUploadResult> {\n const maxRequestBytes =\n input.maxRequestBytes ?? (Number(process.env.BOS_MAX_BUNDLE_UPLOAD_BYTES) || 64 * 1024 * 1024);\n\n const merged: BundleUploadResult = { stored: 0, totalBytes: 0, integrity: {} };\n let batch: DistFile[] = [];\n let batchBytes = 0;\n const overheadPerFile = (file: DistFile): number =>\n Math.ceil(file.bytes.byteLength / 3) * 4 + file.path.length + 64;\n\n const flush = async () => {\n if (batch.length === 0) return;\n const result = await uploadBundle({\n origin: input.origin,\n apiKey: input.apiKey,\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n files: batch,\n });\n merged.stored += result.stored;\n merged.totalBytes += result.totalBytes;\n Object.assign(merged.integrity, result.integrity);\n merged.storage = result.storage;\n batch = [];\n batchBytes = 0;\n };\n\n for (const file of input.files) {\n const cost = overheadPerFile(file);\n if (cost > maxRequestBytes) {\n throw new Error(\n `[publish] ${input.workspace}/${file.path} (${file.bytes.byteLength} bytes) exceeds the per-request upload ceiling`,\n );\n }\n if (batchBytes + cost > maxRequestBytes) {\n await flush();\n }\n batch.push(file);\n batchBytes += cost;\n }\n await flush();\n\n return merged;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage-upload.mjs","names":[],"sources":["../src/storage-upload.ts"],"sourcesContent":["import { readdir, readFile, stat } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\nimport { createUploadClient } from \"@everything-dev/upload-app/client\";\n\nexport interface DistFile {\n path: string;\n bytes: Uint8Array;\n}\n\nexport interface BundleUploadResult {\n stored: number;\n unchanged?: number;\n totalBytes: number;\n integrity: Record<string, string>;\n storage?: \"s3\" | \"memory\";\n}\n\nasync function walkDist(dir: string, baseDir: string): Promise<DistFile[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: DistFile[] = [];\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await walkDist(fullPath, baseDir)));\n continue;\n }\n const info = await stat(fullPath);\n if (!info.isFile()) continue;\n files.push({\n path: relative(baseDir, fullPath).split(sep).join(\"/\"),\n bytes: new Uint8Array(await readFile(fullPath)),\n });\n }\n return files;\n}\n\nexport async function collectDistFiles(distDir: string): Promise<DistFile[]> {\n return walkDist(distDir, distDir);\n}\n\nexport async function uploadBundle(input: {\n origin: string;\n apiKey?: string;\n account: string;\n gateway: string;\n workspace: string;\n files: DistFile[];\n fetchImpl?: typeof fetch;\n}): Promise<BundleUploadResult> {\n const client = createUploadClient({\n origin: input.origin,\n apiKey: input.apiKey,\n fetchImpl: input.fetchImpl,\n });\n const result = await client.uploadBundle({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n files: input.files.map((file) => ({\n path: file.path,\n contentBase64: Buffer.from(file.bytes).toString(\"base64\"),\n })),\n });\n return result;\n}\n\nexport async function uploadWorkspaceDist(input: {\n origin: string;\n apiKey?: string;\n account: string;\n gateway: string;\n workspace: string;\n files: DistFile[];\n maxRequestBytes?: number;\n}): Promise<BundleUploadResult> {\n const maxRequestBytes =\n input.maxRequestBytes ?? (Number(process.env.BOS_MAX_BUNDLE_UPLOAD_BYTES) || 64 * 1024 * 1024);\n\n const merged: BundleUploadResult = { stored: 0, totalBytes: 0, integrity: {} };\n let batch: DistFile[] = [];\n let batchBytes = 0;\n const overheadPerFile = (file: DistFile): number =>\n Math.ceil(file.bytes.byteLength / 3) * 4 + file.path.length + 64;\n\n const flush = async () => {\n if (batch.length === 0) return;\n const result = await uploadBundle({\n origin: input.origin,\n apiKey: input.apiKey,\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n files: batch,\n });\n merged.stored += result.stored;\n merged.totalBytes += result.totalBytes;\n merged.unchanged = (merged.unchanged ?? 0) + (result.unchanged ?? 0);\n Object.assign(merged.integrity, result.integrity);\n merged.storage = result.storage;\n batch = [];\n batchBytes = 0;\n };\n\n for (const file of input.files) {\n const cost = overheadPerFile(file);\n if (cost > maxRequestBytes) {\n throw new Error(\n `[publish] ${input.workspace}/${file.path} (${file.bytes.byteLength} bytes) exceeds the per-request upload ceiling`,\n );\n }\n if (batchBytes + cost > maxRequestBytes) {\n await flush();\n }\n batch.push(file);\n batchBytes += cost;\n }\n await flush();\n\n return merged;\n}\n"],"mappings":";;;;;AAiBA,eAAe,SAAS,KAAa,SAAsC;CACzE,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAC1D,MAAM,QAAoB,CAAC;CAC3B,KAAK,MAAM,SAAS,QAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC,GAAG;EACxE,MAAM,WAAW,KAAK,KAAK,MAAM,IAAI;EACrC,IAAI,MAAM,YAAY,GAAG;GACvB,MAAM,KAAK,GAAI,MAAM,SAAS,UAAU,OAAO,CAAE;GACjD;EACF;EAEA,IAAI,EAAC,MADc,KAAK,QAAQ,EACvB,CAAC,OAAO,GAAG;EACpB,MAAM,KAAK;GACT,MAAM,SAAS,SAAS,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG;GACrD,OAAO,IAAI,WAAW,MAAM,SAAS,QAAQ,CAAC;EAChD,CAAC;CACH;CACA,OAAO;AACT;AAEA,eAAsB,iBAAiB,SAAsC;CAC3E,OAAO,SAAS,SAAS,OAAO;AAClC;AAEA,eAAsB,aAAa,OAQH;CAe9B,OAAO,MAdQ,mBAAmB;EAChC,QAAQ,MAAM;EACd,QAAQ,MAAM;EACd,WAAW,MAAM;CACnB,CAC0B,CAAC,CAAC,aAAa;EACvC,SAAS,MAAM;EACf,SAAS,MAAM;EACf,WAAW,MAAM;EACjB,OAAO,MAAM,MAAM,KAAK,UAAU;GAChC,MAAM,KAAK;GACX,eAAe,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC,SAAS,QAAQ;EAC1D,EAAE;CACJ,CAAC;AAEH;AAEA,eAAsB,oBAAoB,OAQV;CAC9B,MAAM,kBACJ,MAAM,oBAAoB,OAAO,QAAQ,IAAI,2BAA2B,KAAK;CAE/E,MAAM,SAA6B;EAAE,QAAQ;EAAG,YAAY;EAAG,WAAW,CAAC;CAAE;CAC7E,IAAI,QAAoB,CAAC;CACzB,IAAI,aAAa;CACjB,MAAM,mBAAmB,SACvB,KAAK,KAAK,KAAK,MAAM,aAAa,CAAC,IAAI,IAAI,KAAK,KAAK,SAAS;CAEhE,MAAM,QAAQ,YAAY;EACxB,IAAI,MAAM,WAAW,GAAG;EACxB,MAAM,SAAS,MAAM,aAAa;GAChC,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,SAAS,MAAM;GACf,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,OAAO;EACT,CAAC;EACD,OAAO,UAAU,OAAO;EACxB,OAAO,cAAc,OAAO;EAC5B,OAAO,aAAa,OAAO,aAAa,MAAM,OAAO,aAAa;EAClE,OAAO,OAAO,OAAO,WAAW,OAAO,SAAS;EAChD,OAAO,UAAU,OAAO;EACxB,QAAQ,CAAC;EACT,aAAa;CACf;CAEA,KAAK,MAAM,QAAQ,MAAM,OAAO;EAC9B,MAAM,OAAO,gBAAgB,IAAI;EACjC,IAAI,OAAO,iBACT,MAAM,IAAI,MACR,aAAa,MAAM,UAAU,GAAG,KAAK,KAAK,IAAI,KAAK,MAAM,WAAW,+CACtE;EAEF,IAAI,aAAa,OAAO,iBACtB,MAAM,MAAM;EAEd,MAAM,KAAK,IAAI;EACf,cAAc;CAChB;CACA,MAAM,MAAM;CAEZ,OAAO;AACT"}
|
package/dist/ui/api.cjs
CHANGED
|
@@ -1,80 +1,22 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
let _orpc_client = require("@orpc/client");
|
|
3
|
-
let _orpc_client_fetch = require("@orpc/client/fetch");
|
|
4
2
|
let _orpc_tanstack_query = require("@orpc/tanstack-query");
|
|
5
3
|
let _tanstack_react_router = require("@tanstack/react-router");
|
|
4
|
+
let every_plugin_client = require("every-plugin/client");
|
|
6
5
|
|
|
7
6
|
//#region src/ui/api.ts
|
|
8
|
-
function createRpcLink(config, url, headers) {
|
|
9
|
-
return new _orpc_client_fetch.RPCLink({
|
|
10
|
-
origin: config.hostUrl,
|
|
11
|
-
url,
|
|
12
|
-
interceptors: [(0, _orpc_client.onError)((error) => {
|
|
13
|
-
if (typeof window === "undefined") return;
|
|
14
|
-
if (error && typeof error === "object" && "message" in error) {
|
|
15
|
-
const message = String(error.message).toLowerCase();
|
|
16
|
-
if (message.includes("fetch") || message.includes("network") || message.includes("failed to fetch")) import("sonner").then(({ toast }) => {
|
|
17
|
-
const message = config.connectionError?.() ?? {
|
|
18
|
-
title: "Unable to connect to API",
|
|
19
|
-
description: "The API is currently unavailable. Please try again later."
|
|
20
|
-
};
|
|
21
|
-
toast.error(message.title, {
|
|
22
|
-
id: "api-connection-error",
|
|
23
|
-
description: message.description
|
|
24
|
-
});
|
|
25
|
-
}).catch(() => {});
|
|
26
|
-
}
|
|
27
|
-
})],
|
|
28
|
-
fetch(fetchUrl, options) {
|
|
29
|
-
return fetchWithRateLimitRetry(fetchUrl, {
|
|
30
|
-
...options,
|
|
31
|
-
credentials: "include",
|
|
32
|
-
headers: headers ? {
|
|
33
|
-
...Object.fromEntries(headers),
|
|
34
|
-
...options?.headers
|
|
35
|
-
} : options?.headers
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
const RATE_LIMIT_MAX_RETRIES = 3;
|
|
41
|
-
const RATE_LIMIT_RETRY_CAP_MS = 2e3;
|
|
42
|
-
/**
|
|
43
|
-
* A 429 from the host's edge limiter is transient saturation, not an
|
|
44
|
-
* application failure — retry within the server's `Retry-After` (capped) so
|
|
45
|
-
* queries and the session check self-heal instead of surfacing errors.
|
|
46
|
-
*/
|
|
47
|
-
async function fetchWithRateLimitRetry(fetchUrl, options) {
|
|
48
|
-
let response = await fetch(fetchUrl, options);
|
|
49
|
-
for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {
|
|
50
|
-
const retryAfterSeconds = Number(response.headers.get("retry-after"));
|
|
51
|
-
const delay = Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0 ? Math.min(retryAfterSeconds * 1e3, RATE_LIMIT_RETRY_CAP_MS) : 100 * attempt;
|
|
52
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
53
|
-
response = await fetch(fetchUrl, options);
|
|
54
|
-
}
|
|
55
|
-
return response;
|
|
56
|
-
}
|
|
57
|
-
function buildClient(config, url, headers) {
|
|
58
|
-
if (!config.hostUrl) throw new Error("Missing runtime host URL");
|
|
59
|
-
return (0, _orpc_client.createORPCClient)(createRpcLink(config, url, headers));
|
|
60
|
-
}
|
|
61
|
-
const browserClients = /* @__PURE__ */ new Map();
|
|
62
|
-
function memoizedClient(config, url, headers) {
|
|
63
|
-
if (typeof window !== "undefined" && !headers) {
|
|
64
|
-
const key = `${config.hostUrl}${url}`;
|
|
65
|
-
const memoized = browserClients.get(key);
|
|
66
|
-
if (memoized) return memoized;
|
|
67
|
-
const client = buildClient(config, url, headers);
|
|
68
|
-
browserClients.set(key, client);
|
|
69
|
-
return client;
|
|
70
|
-
}
|
|
71
|
-
return buildClient(config, url, headers);
|
|
72
|
-
}
|
|
73
7
|
function createApiClient(config, headers) {
|
|
74
|
-
return
|
|
8
|
+
return (0, every_plugin_client.createRpcClient)({
|
|
9
|
+
hostUrl: config.hostUrl,
|
|
10
|
+
path: config.rpcBase,
|
|
11
|
+
connectionError: config.connectionError
|
|
12
|
+
}, headers);
|
|
75
13
|
}
|
|
76
14
|
function createPluginApiClient(pluginKey, config, headers) {
|
|
77
|
-
return
|
|
15
|
+
return (0, every_plugin_client.createRpcClient)({
|
|
16
|
+
hostUrl: config.hostUrl,
|
|
17
|
+
path: `${config.rpcBase}/${pluginKey}`,
|
|
18
|
+
connectionError: config.connectionError
|
|
19
|
+
}, headers);
|
|
78
20
|
}
|
|
79
21
|
function createServiceClients(config, keys, headers) {
|
|
80
22
|
const clients = {};
|
package/dist/ui/api.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.cjs","names":["
|
|
1
|
+
{"version":3,"file":"api.cjs","names":["createRpcClient","useRouter","createTanstackQueryUtils"],"sources":["../../src/ui/api.ts"],"sourcesContent":["import type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { useRouter } from \"@tanstack/react-router\";\nimport { createRpcClient, type RpcConnectionErrorCopy } from \"every-plugin/client\";\n\nexport type { RouterContract };\n\n/**\n * Localized copy for the API connection-failure toast — the app's authored\n * override seam (ADR 0023). Called at error time, so the implementation may\n * resolve the active locale itself.\n */\nexport type ApiConnectionErrorCopy = RpcConnectionErrorCopy;\n\n/** typed client for the app's default router contract (router-context default) */\nexport type ApiClient<T extends RouterContract = RouterContract> = ContractRouterClient<T>;\n\nexport interface ClientServiceConfig {\n hostUrl: string;\n rpcBase: `/${string}`;\n connectionError?: ApiConnectionErrorCopy;\n}\n\ntype ClientRouterContext = {\n apiClient?: unknown;\n pluginClients?: Record<string, unknown>;\n};\n\nexport type { ClientRouterContext };\n\nexport function createApiClient<T extends RouterContract = RouterContract>(\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return createRpcClient<T>(\n {\n hostUrl: config.hostUrl,\n path: config.rpcBase,\n connectionError: config.connectionError,\n },\n headers,\n );\n}\n\nexport function createPluginApiClient<T extends RouterContract = RouterContract>(\n pluginKey: string,\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return createRpcClient<T>(\n {\n hostUrl: config.hostUrl,\n path: `${config.rpcBase}/${pluginKey}`,\n connectionError: config.connectionError,\n },\n headers,\n );\n}\n\nexport function createServiceClients<\n C extends Record<string, RouterContract>,\n K extends keyof C & string = keyof C & string,\n>(\n config: ClientServiceConfig,\n keys: readonly K[],\n headers?: Headers,\n): Pick<{ [P in keyof C]: ContractRouterClient<C[P]> }, K> {\n const clients: Record<string, unknown> = {};\n for (const key of keys) {\n if (key === \"api\") {\n clients[key] = createApiClient<C[typeof key]>(config, headers);\n } else {\n clients[key] = createPluginApiClient<C[typeof key]>(key, config, headers);\n }\n }\n return clients as Pick<{ [P in keyof C]: ContractRouterClient<C[P]> }, K>;\n}\n\nfunction useClientContext(): ClientRouterContext {\n return useRouter().options.context as ClientRouterContext;\n}\n\nexport function useApiClient<T extends RouterContract = RouterContract>(): ContractRouterClient<T> {\n return useClientContext().apiClient as ContractRouterClient<T>;\n}\n\nexport function useOrpc<T extends RouterContract = RouterContract>() {\n return createTanstackQueryUtils(useApiClient<T>());\n}\n\nexport function usePluginClients<C extends Record<string, unknown> = Record<string, unknown>>(): C {\n return (useClientContext().pluginClients ?? {}) as C;\n}\n"],"mappings":";;;;;;AA8BA,SAAgB,gBACd,QACA,SACyB;CACzB,WAAOA,qCACL;EACE,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,iBAAiB,OAAO;CAC1B,GACA,OACF;AACF;AAEA,SAAgB,sBACd,WACA,QACA,SACyB;CACzB,WAAOA,qCACL;EACE,SAAS,OAAO;EAChB,MAAM,GAAG,OAAO,QAAQ,GAAG;EAC3B,iBAAiB,OAAO;CAC1B,GACA,OACF;AACF;AAEA,SAAgB,qBAId,QACA,MACA,SACyD;CACzD,MAAM,UAAmC,CAAC;CAC1C,KAAK,MAAM,OAAO,MAChB,IAAI,QAAQ,OACV,QAAQ,OAAO,gBAA+B,QAAQ,OAAO;MAE7D,QAAQ,OAAO,sBAAqC,KAAK,QAAQ,OAAO;CAG5E,OAAO;AACT;AAEA,SAAS,mBAAwC;CAC/C,WAAOC,kCAAU,CAAC,CAAC,QAAQ;AAC7B;AAEA,SAAgB,eAAmF;CACjG,OAAO,iBAAiB,CAAC,CAAC;AAC5B;AAEA,SAAgB,UAAqD;CACnE,WAAOC,+CAAyB,aAAgB,CAAC;AACnD;AAEA,SAAgB,mBAAmF;CACjG,OAAQ,iBAAiB,CAAC,CAAC,iBAAiB,CAAC;AAC/C"}
|
package/dist/ui/api.d.cts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { ContractRouterClient, RouterContract } from "@orpc/contract";
|
|
2
|
+
import { RpcConnectionErrorCopy } from "every-plugin/client";
|
|
2
3
|
//#region src/ui/api.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Localized copy for the API connection-failure toast — the app's authored
|
|
5
6
|
* override seam (ADR 0023). Called at error time, so the implementation may
|
|
6
7
|
* resolve the active locale itself.
|
|
7
8
|
*/
|
|
8
|
-
type ApiConnectionErrorCopy =
|
|
9
|
-
title: string;
|
|
10
|
-
description: string;
|
|
11
|
-
};
|
|
9
|
+
type ApiConnectionErrorCopy = RpcConnectionErrorCopy;
|
|
12
10
|
/** typed client for the app's default router contract (router-context default) */
|
|
13
11
|
type ApiClient<T extends RouterContract = RouterContract> = ContractRouterClient<T>;
|
|
14
12
|
interface ClientServiceConfig {
|
package/dist/ui/api.d.mts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { ContractRouterClient, RouterContract } from "@orpc/contract";
|
|
2
|
+
import { RpcConnectionErrorCopy } from "every-plugin/client";
|
|
2
3
|
//#region src/ui/api.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Localized copy for the API connection-failure toast — the app's authored
|
|
5
6
|
* override seam (ADR 0023). Called at error time, so the implementation may
|
|
6
7
|
* resolve the active locale itself.
|
|
7
8
|
*/
|
|
8
|
-
type ApiConnectionErrorCopy =
|
|
9
|
-
title: string;
|
|
10
|
-
description: string;
|
|
11
|
-
};
|
|
9
|
+
type ApiConnectionErrorCopy = RpcConnectionErrorCopy;
|
|
12
10
|
/** typed client for the app's default router contract (router-context default) */
|
|
13
11
|
type ApiClient<T extends RouterContract = RouterContract> = ContractRouterClient<T>;
|
|
14
12
|
interface ClientServiceConfig {
|
package/dist/ui/api.mjs
CHANGED
|
@@ -1,79 +1,21 @@
|
|
|
1
|
-
import { createORPCClient, onError } from "@orpc/client";
|
|
2
|
-
import { RPCLink } from "@orpc/client/fetch";
|
|
3
1
|
import { createTanstackQueryUtils } from "@orpc/tanstack-query";
|
|
4
2
|
import { useRouter } from "@tanstack/react-router";
|
|
3
|
+
import { createRpcClient } from "every-plugin/client";
|
|
5
4
|
|
|
6
5
|
//#region src/ui/api.ts
|
|
7
|
-
function createRpcLink(config, url, headers) {
|
|
8
|
-
return new RPCLink({
|
|
9
|
-
origin: config.hostUrl,
|
|
10
|
-
url,
|
|
11
|
-
interceptors: [onError((error) => {
|
|
12
|
-
if (typeof window === "undefined") return;
|
|
13
|
-
if (error && typeof error === "object" && "message" in error) {
|
|
14
|
-
const message = String(error.message).toLowerCase();
|
|
15
|
-
if (message.includes("fetch") || message.includes("network") || message.includes("failed to fetch")) import("sonner").then(({ toast }) => {
|
|
16
|
-
const message = config.connectionError?.() ?? {
|
|
17
|
-
title: "Unable to connect to API",
|
|
18
|
-
description: "The API is currently unavailable. Please try again later."
|
|
19
|
-
};
|
|
20
|
-
toast.error(message.title, {
|
|
21
|
-
id: "api-connection-error",
|
|
22
|
-
description: message.description
|
|
23
|
-
});
|
|
24
|
-
}).catch(() => {});
|
|
25
|
-
}
|
|
26
|
-
})],
|
|
27
|
-
fetch(fetchUrl, options) {
|
|
28
|
-
return fetchWithRateLimitRetry(fetchUrl, {
|
|
29
|
-
...options,
|
|
30
|
-
credentials: "include",
|
|
31
|
-
headers: headers ? {
|
|
32
|
-
...Object.fromEntries(headers),
|
|
33
|
-
...options?.headers
|
|
34
|
-
} : options?.headers
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
const RATE_LIMIT_MAX_RETRIES = 3;
|
|
40
|
-
const RATE_LIMIT_RETRY_CAP_MS = 2e3;
|
|
41
|
-
/**
|
|
42
|
-
* A 429 from the host's edge limiter is transient saturation, not an
|
|
43
|
-
* application failure — retry within the server's `Retry-After` (capped) so
|
|
44
|
-
* queries and the session check self-heal instead of surfacing errors.
|
|
45
|
-
*/
|
|
46
|
-
async function fetchWithRateLimitRetry(fetchUrl, options) {
|
|
47
|
-
let response = await fetch(fetchUrl, options);
|
|
48
|
-
for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {
|
|
49
|
-
const retryAfterSeconds = Number(response.headers.get("retry-after"));
|
|
50
|
-
const delay = Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0 ? Math.min(retryAfterSeconds * 1e3, RATE_LIMIT_RETRY_CAP_MS) : 100 * attempt;
|
|
51
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
52
|
-
response = await fetch(fetchUrl, options);
|
|
53
|
-
}
|
|
54
|
-
return response;
|
|
55
|
-
}
|
|
56
|
-
function buildClient(config, url, headers) {
|
|
57
|
-
if (!config.hostUrl) throw new Error("Missing runtime host URL");
|
|
58
|
-
return createORPCClient(createRpcLink(config, url, headers));
|
|
59
|
-
}
|
|
60
|
-
const browserClients = /* @__PURE__ */ new Map();
|
|
61
|
-
function memoizedClient(config, url, headers) {
|
|
62
|
-
if (typeof window !== "undefined" && !headers) {
|
|
63
|
-
const key = `${config.hostUrl}${url}`;
|
|
64
|
-
const memoized = browserClients.get(key);
|
|
65
|
-
if (memoized) return memoized;
|
|
66
|
-
const client = buildClient(config, url, headers);
|
|
67
|
-
browserClients.set(key, client);
|
|
68
|
-
return client;
|
|
69
|
-
}
|
|
70
|
-
return buildClient(config, url, headers);
|
|
71
|
-
}
|
|
72
6
|
function createApiClient(config, headers) {
|
|
73
|
-
return
|
|
7
|
+
return createRpcClient({
|
|
8
|
+
hostUrl: config.hostUrl,
|
|
9
|
+
path: config.rpcBase,
|
|
10
|
+
connectionError: config.connectionError
|
|
11
|
+
}, headers);
|
|
74
12
|
}
|
|
75
13
|
function createPluginApiClient(pluginKey, config, headers) {
|
|
76
|
-
return
|
|
14
|
+
return createRpcClient({
|
|
15
|
+
hostUrl: config.hostUrl,
|
|
16
|
+
path: `${config.rpcBase}/${pluginKey}`,
|
|
17
|
+
connectionError: config.connectionError
|
|
18
|
+
}, headers);
|
|
77
19
|
}
|
|
78
20
|
function createServiceClients(config, keys, headers) {
|
|
79
21
|
const clients = {};
|
package/dist/ui/api.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.mjs","names":[],"sources":["../../src/ui/api.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"api.mjs","names":[],"sources":["../../src/ui/api.ts"],"sourcesContent":["import type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { useRouter } from \"@tanstack/react-router\";\nimport { createRpcClient, type RpcConnectionErrorCopy } from \"every-plugin/client\";\n\nexport type { RouterContract };\n\n/**\n * Localized copy for the API connection-failure toast — the app's authored\n * override seam (ADR 0023). Called at error time, so the implementation may\n * resolve the active locale itself.\n */\nexport type ApiConnectionErrorCopy = RpcConnectionErrorCopy;\n\n/** typed client for the app's default router contract (router-context default) */\nexport type ApiClient<T extends RouterContract = RouterContract> = ContractRouterClient<T>;\n\nexport interface ClientServiceConfig {\n hostUrl: string;\n rpcBase: `/${string}`;\n connectionError?: ApiConnectionErrorCopy;\n}\n\ntype ClientRouterContext = {\n apiClient?: unknown;\n pluginClients?: Record<string, unknown>;\n};\n\nexport type { ClientRouterContext };\n\nexport function createApiClient<T extends RouterContract = RouterContract>(\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return createRpcClient<T>(\n {\n hostUrl: config.hostUrl,\n path: config.rpcBase,\n connectionError: config.connectionError,\n },\n headers,\n );\n}\n\nexport function createPluginApiClient<T extends RouterContract = RouterContract>(\n pluginKey: string,\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return createRpcClient<T>(\n {\n hostUrl: config.hostUrl,\n path: `${config.rpcBase}/${pluginKey}`,\n connectionError: config.connectionError,\n },\n headers,\n );\n}\n\nexport function createServiceClients<\n C extends Record<string, RouterContract>,\n K extends keyof C & string = keyof C & string,\n>(\n config: ClientServiceConfig,\n keys: readonly K[],\n headers?: Headers,\n): Pick<{ [P in keyof C]: ContractRouterClient<C[P]> }, K> {\n const clients: Record<string, unknown> = {};\n for (const key of keys) {\n if (key === \"api\") {\n clients[key] = createApiClient<C[typeof key]>(config, headers);\n } else {\n clients[key] = createPluginApiClient<C[typeof key]>(key, config, headers);\n }\n }\n return clients as Pick<{ [P in keyof C]: ContractRouterClient<C[P]> }, K>;\n}\n\nfunction useClientContext(): ClientRouterContext {\n return useRouter().options.context as ClientRouterContext;\n}\n\nexport function useApiClient<T extends RouterContract = RouterContract>(): ContractRouterClient<T> {\n return useClientContext().apiClient as ContractRouterClient<T>;\n}\n\nexport function useOrpc<T extends RouterContract = RouterContract>() {\n return createTanstackQueryUtils(useApiClient<T>());\n}\n\nexport function usePluginClients<C extends Record<string, unknown> = Record<string, unknown>>(): C {\n return (useClientContext().pluginClients ?? {}) as C;\n}\n"],"mappings":";;;;;AA8BA,SAAgB,gBACd,QACA,SACyB;CACzB,OAAO,gBACL;EACE,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,iBAAiB,OAAO;CAC1B,GACA,OACF;AACF;AAEA,SAAgB,sBACd,WACA,QACA,SACyB;CACzB,OAAO,gBACL;EACE,SAAS,OAAO;EAChB,MAAM,GAAG,OAAO,QAAQ,GAAG;EAC3B,iBAAiB,OAAO;CAC1B,GACA,OACF;AACF;AAEA,SAAgB,qBAId,QACA,MACA,SACyD;CACzD,MAAM,UAAmC,CAAC;CAC1C,KAAK,MAAM,OAAO,MAChB,IAAI,QAAQ,OACV,QAAQ,OAAO,gBAA+B,QAAQ,OAAO;MAE7D,QAAQ,OAAO,sBAAqC,KAAK,QAAQ,OAAO;CAG5E,OAAO;AACT;AAEA,SAAS,mBAAwC;CAC/C,OAAO,UAAU,CAAC,CAAC,QAAQ;AAC7B;AAEA,SAAgB,eAAmF;CACjG,OAAO,iBAAiB,CAAC,CAAC;AAC5B;AAEA,SAAgB,UAAqD;CACnE,OAAO,yBAAyB,aAAgB,CAAC;AACnD;AAEA,SAAgB,mBAAmF;CACjG,OAAQ,iBAAiB,CAAC,CAAC,iBAAiB,CAAC;AAC/C"}
|