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
@@ -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
- const fetchImpl = input.fetchImpl ?? fetch;
64
- const headers = { "content-type": "application/json" };
65
- if (input.apiKey) headers["x-api-key"] = input.apiKey;
66
- const body = JSON.stringify({
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"}
@@ -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
- const fetchImpl = input.fetchImpl ?? fetch;
64
- const headers = { "content-type": "application/json" };
65
- if (input.apiKey) headers["x-api-key"] = input.apiKey;
66
- const body = JSON.stringify({
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 memoizedClient(config, config.rpcBase, headers);
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 memoizedClient(config, `${config.rpcBase}/${pluginKey}`, headers);
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 = {};
@@ -1 +1 @@
1
- {"version":3,"file":"api.cjs","names":["RPCLink","onError","createORPCClient","useRouter","createTanstackQueryUtils"],"sources":["../../src/ui/api.ts"],"sourcesContent":["import { createORPCClient, onError } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { useRouter } from \"@tanstack/react-router\";\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 = () => { title: string; description: string };\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\nfunction createRpcLink(config: ClientServiceConfig, url: `/${string}`, headers?: Headers) {\n return new RPCLink({\n origin: config.hostUrl,\n url,\n interceptors: [\n onError((error: unknown) => {\n if (typeof window === \"undefined\") {\n return;\n }\n\n if (error && typeof error === \"object\" && \"message\" in error) {\n const message = String(error.message).toLowerCase();\n if (\n message.includes(\"fetch\") ||\n message.includes(\"network\") ||\n message.includes(\"failed to fetch\")\n ) {\n void import(\"sonner\")\n .then(({ toast }) => {\n const message = config.connectionError?.() ?? {\n title: \"Unable to connect to API\",\n description: \"The API is currently unavailable. Please try again later.\",\n };\n toast.error(message.title, {\n id: \"api-connection-error\",\n description: message.description,\n });\n })\n .catch(() => {});\n }\n }\n }),\n ],\n fetch(fetchUrl: RequestInfo | URL, options?: RequestInit) {\n return fetchWithRateLimitRetry(fetchUrl, {\n ...options,\n credentials: \"include\",\n headers: headers\n ? { ...Object.fromEntries(headers), ...(options?.headers as Record<string, string>) }\n : options?.headers,\n });\n },\n });\n}\n\nconst RATE_LIMIT_MAX_RETRIES = 3;\nconst RATE_LIMIT_RETRY_CAP_MS = 2_000;\n\n/**\n * A 429 from the host's edge limiter is transient saturation, not an\n * application failure — retry within the server's `Retry-After` (capped) so\n * queries and the session check self-heal instead of surfacing errors.\n */\nasync function fetchWithRateLimitRetry(\n fetchUrl: RequestInfo | URL,\n options?: RequestInit,\n): Promise<Response> {\n let response = await fetch(fetchUrl, options);\n for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {\n const retryAfterSeconds = Number(response.headers.get(\"retry-after\"));\n const delay =\n Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0\n ? Math.min(retryAfterSeconds * 1000, RATE_LIMIT_RETRY_CAP_MS)\n : 100 * attempt;\n await new Promise((resolve) => setTimeout(resolve, delay));\n response = await fetch(fetchUrl, options);\n }\n return response;\n}\n\nfunction buildClient<T extends RouterContract>(\n config: ClientServiceConfig,\n url: `/${string}`,\n headers?: Headers,\n): ContractRouterClient<T> {\n if (!config.hostUrl) {\n throw new Error(\"Missing runtime host URL\");\n }\n return createORPCClient(createRpcLink(config, url, headers)) as ContractRouterClient<T>;\n}\n\nconst browserClients = new Map<string, unknown>();\n\nfunction memoizedClient<T extends RouterContract>(\n config: ClientServiceConfig,\n url: `/${string}`,\n headers?: Headers,\n): ContractRouterClient<T> {\n if (typeof window !== \"undefined\" && !headers) {\n const key = `${config.hostUrl}${url}`;\n const memoized = browserClients.get(key);\n if (memoized) {\n return memoized as ContractRouterClient<T>;\n }\n const client = buildClient<T>(config, url, headers);\n browserClients.set(key, client);\n return client;\n }\n\n return buildClient<T>(config, url, headers);\n}\n\nexport function createApiClient<T extends RouterContract = RouterContract>(\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return memoizedClient<T>(config, config.rpcBase, headers);\n}\n\nexport function createPluginApiClient<T extends RouterContract = RouterContract>(\n pluginKey: string,\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return memoizedClient<T>(config, `${config.rpcBase}/${pluginKey}`, headers);\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":"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"}
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 memoizedClient(config, config.rpcBase, headers);
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 memoizedClient(config, `${config.rpcBase}/${pluginKey}`, headers);
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 = {};
@@ -1 +1 @@
1
- {"version":3,"file":"api.mjs","names":[],"sources":["../../src/ui/api.ts"],"sourcesContent":["import { createORPCClient, onError } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { ContractRouterClient, RouterContract } from \"@orpc/contract\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { useRouter } from \"@tanstack/react-router\";\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 = () => { title: string; description: string };\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\nfunction createRpcLink(config: ClientServiceConfig, url: `/${string}`, headers?: Headers) {\n return new RPCLink({\n origin: config.hostUrl,\n url,\n interceptors: [\n onError((error: unknown) => {\n if (typeof window === \"undefined\") {\n return;\n }\n\n if (error && typeof error === \"object\" && \"message\" in error) {\n const message = String(error.message).toLowerCase();\n if (\n message.includes(\"fetch\") ||\n message.includes(\"network\") ||\n message.includes(\"failed to fetch\")\n ) {\n void import(\"sonner\")\n .then(({ toast }) => {\n const message = config.connectionError?.() ?? {\n title: \"Unable to connect to API\",\n description: \"The API is currently unavailable. Please try again later.\",\n };\n toast.error(message.title, {\n id: \"api-connection-error\",\n description: message.description,\n });\n })\n .catch(() => {});\n }\n }\n }),\n ],\n fetch(fetchUrl: RequestInfo | URL, options?: RequestInit) {\n return fetchWithRateLimitRetry(fetchUrl, {\n ...options,\n credentials: \"include\",\n headers: headers\n ? { ...Object.fromEntries(headers), ...(options?.headers as Record<string, string>) }\n : options?.headers,\n });\n },\n });\n}\n\nconst RATE_LIMIT_MAX_RETRIES = 3;\nconst RATE_LIMIT_RETRY_CAP_MS = 2_000;\n\n/**\n * A 429 from the host's edge limiter is transient saturation, not an\n * application failure — retry within the server's `Retry-After` (capped) so\n * queries and the session check self-heal instead of surfacing errors.\n */\nasync function fetchWithRateLimitRetry(\n fetchUrl: RequestInfo | URL,\n options?: RequestInit,\n): Promise<Response> {\n let response = await fetch(fetchUrl, options);\n for (let attempt = 1; attempt <= RATE_LIMIT_MAX_RETRIES && response.status === 429; attempt++) {\n const retryAfterSeconds = Number(response.headers.get(\"retry-after\"));\n const delay =\n Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0\n ? Math.min(retryAfterSeconds * 1000, RATE_LIMIT_RETRY_CAP_MS)\n : 100 * attempt;\n await new Promise((resolve) => setTimeout(resolve, delay));\n response = await fetch(fetchUrl, options);\n }\n return response;\n}\n\nfunction buildClient<T extends RouterContract>(\n config: ClientServiceConfig,\n url: `/${string}`,\n headers?: Headers,\n): ContractRouterClient<T> {\n if (!config.hostUrl) {\n throw new Error(\"Missing runtime host URL\");\n }\n return createORPCClient(createRpcLink(config, url, headers)) as ContractRouterClient<T>;\n}\n\nconst browserClients = new Map<string, unknown>();\n\nfunction memoizedClient<T extends RouterContract>(\n config: ClientServiceConfig,\n url: `/${string}`,\n headers?: Headers,\n): ContractRouterClient<T> {\n if (typeof window !== \"undefined\" && !headers) {\n const key = `${config.hostUrl}${url}`;\n const memoized = browserClients.get(key);\n if (memoized) {\n return memoized as ContractRouterClient<T>;\n }\n const client = buildClient<T>(config, url, headers);\n browserClients.set(key, client);\n return client;\n }\n\n return buildClient<T>(config, url, headers);\n}\n\nexport function createApiClient<T extends RouterContract = RouterContract>(\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return memoizedClient<T>(config, config.rpcBase, headers);\n}\n\nexport function createPluginApiClient<T extends RouterContract = RouterContract>(\n pluginKey: string,\n config: ClientServiceConfig,\n headers?: Headers,\n): ContractRouterClient<T> {\n return memoizedClient<T>(config, `${config.rpcBase}/${pluginKey}`, headers);\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":"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"}
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"}