everything-dev 2.0.0-rc.4 → 2.0.0-rc.5
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/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/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 +1 -0
- package/dist/dev-program.cjs.map +1 -1
- package/dist/dev-program.mjs +1 -1
- 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/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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-deploy.mjs","names":[],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n /** Push `:rc` after the pinned tags — the floating prerelease-train ref. */\n pushRc: boolean;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image — and `:rc` is the floating\n * ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on\n * every prerelease deploy and freezes at the last rc-train build once the\n * workspace releases a stable version.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true, pushRc: false };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n pushRc: true,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true, pushRc: false };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n /** Whether the floating `:rc` prerelease-train ref was pushed. */\n rcPushed: boolean;\n digest?: string;\n}\n\nexport type DeployImagePlan =\n | { kind: \"prebuilt\"; image: string; digest: string }\n | { kind: \"build\"; image: string }\n | { kind: \"skip\"; reason: string };\n\nconst DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;\n\n/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */\nexport function isValidImageDigest(digest: string): boolean {\n return DIGEST_PATTERN.test(digest);\n}\n\n/**\n * Decide the image leg's source: a digest handed over from a pre-pushed image\n * (the CI image job) wins; otherwise the local docker build runs when the\n * toolchain and Dockerfile are present (ADR 0021, as amended).\n */\nexport function resolveDeployImagePlan(input: {\n imageRef: ResolvedImageRef | undefined;\n imageDigest?: string;\n hasDocker: boolean;\n hasDockerfile: boolean;\n}): DeployImagePlan {\n if (!input.imageRef) {\n return {\n kind: \"skip\",\n reason: \"set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image\",\n };\n }\n if (input.imageDigest) {\n return { kind: \"prebuilt\", image: input.imageRef.image, digest: input.imageDigest };\n }\n if (!input.hasDocker) {\n return { kind: \"skip\", reason: \"docker is not available\" };\n }\n if (!input.hasDockerfile) {\n return { kind: \"skip\", reason: \"no Dockerfile at the config root\" };\n }\n return { kind: \"build\", image: input.imageRef.image };\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, then push the floating refs — `:latest`\n * unless the version is a prerelease, and `:rc` when it is. Callers skip the\n * leg with a notice when no image is configured (child repos get\n * build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({\n shortSha,\n version,\n });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const rcRef = `${input.image}:rc`;\n const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];\n if (pushRc) buildRefs.push(rcRef);\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n const pushFloating = async (ref: string): Promise<boolean> => {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n return true;\n };\n\n let latestPushed = false;\n if (pushLatest) {\n latestPushed = await pushFloating(latestRef);\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n const rcPushed = pushRc ? await pushFloating(rcRef) : false;\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0]!, tags, latestPushed, rcPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021): pull-only deploy of the pushed image digest — a\n * thin `FROM image@digest` Dockerfile pins the digest so Railway never\n * rebuilds; `RAILWAY_DOCKERFILE_PATH` points at the generated file.\n */\nexport async function deployImageToRailway(input: {\n image: string;\n digest: string;\n service: string;\n configDir: string;\n}): Promise<void> {\n const deployDir = join(input.configDir, \".bos\", \"deploy\");\n mkdirSync(deployDir, { recursive: true });\n const dockerfilePath = join(deployDir, \"Dockerfile\");\n writeFileSync(dockerfilePath, `FROM ${input.image}@${input.digest}\\n`);\n\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (pull-only)...`);\n const result = await run(\"railway\", [\"up\", \"--service\", input.service, \"--ci\"], {\n cwd: input.configDir,\n capture: true,\n env: { RAILWAY_DOCKERFILE_PATH: \".bos/deploy/Dockerfile\" },\n });\n if (result?.stdout) {\n for (const line of result.stdout.split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n }\n if (!result || result.exitCode !== 0) {\n throw new Error(\n result?.stderr.trim()\n ? `railway up failed: ${result.stderr.trim().split(\"\\n\").slice(-3).join(\" \")}`\n : `railway up failed with exit code ${result?.exitCode ?? \"unknown\"}`,\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"image-deploy.mjs","names":[],"sources":["../src/image-deploy.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { run } from \"./utils/run\";\nimport { colors, icons } from \"./utils/theme\";\n\nexport interface ResolvedImageRef {\n image: string;\n source: \"ci\" | \"env\" | \"repository\";\n}\n\n/**\n * Image reference resolution precedence (ADR 0021): `ci.image` in\n * bos.config.json → `BOS_IMAGE` env → derived from `repository`.\n */\nexport function resolveImageRef(input: {\n ciImage?: string;\n repository?: string;\n env?: Record<string, string | undefined>;\n}): ResolvedImageRef | undefined {\n const envImage = input.env?.BOS_IMAGE;\n if (input.ciImage) return { image: input.ciImage, source: \"ci\" };\n if (envImage) return { image: envImage, source: \"env\" };\n\n const repo = input.repository\n ?.replace(/^https?:\\/\\/(www\\.)?github\\.com\\//i, \"\")\n .replace(/\\.git$/i, \"\")\n .replace(/\\/+$/, \"\")\n .toLowerCase();\n if (!repo?.includes(\"/\") || repo.split(\"/\").length !== 2) return undefined;\n return { image: `ghcr.io/${repo}`, source: \"repository\" };\n}\n\nexport async function hasDocker(): Promise<boolean> {\n try {\n const result = await run(\"docker\", [\"info\", \"--format\", \"{{.ServerVersion}}\"], {\n capture: true,\n });\n return result?.exitCode === 0;\n } catch {\n return false;\n }\n}\n\nfunction parseDigest(pushOutput: string): string | undefined {\n const match = pushOutput.match(/digest:\\s*(sha256:[a-f0-9]+)/i);\n return match?.[1];\n}\n\nconst SEMVER_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\n/**\n * The runtime framework version baked into the image is the workspace source's\n * `everything-dev` package version — the image builds from `configDir`, so that\n * file is the truth. Missing (child repos without the workspace) → undefined.\n */\nfunction readFrameworkVersion(configDir: string): string | undefined {\n try {\n const raw = readFileSync(\n join(configDir, \"packages\", \"everything-dev\", \"package.json\"),\n \"utf-8\",\n );\n const pkg = JSON.parse(raw) as { version?: unknown };\n return typeof pkg.version === \"string\" && SEMVER_PATTERN.test(pkg.version)\n ? pkg.version\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface ImageTags {\n /** Exact refs to build and push, in push order (latest excluded). */\n tags: string[];\n /** Push `:latest` after the pinned tags. */\n pushLatest: boolean;\n /** Human-readable reason when `latest` is held. */\n latestHoldReason?: string;\n /** Push `:rc` after the pinned tags — the floating prerelease-train ref. */\n pushRc: boolean;\n}\n\n/**\n * Tag scheme (ADR 0021): `sha-<short>` always; the exact `v<version>` tag; a\n * floating major tag (`v2`) on stable releases only. `latest` is held during\n * prereleases (npm's `rc` dist-tag analog) so existing deployments pinned to\n * `:latest` keep serving the last stable image — and `:rc` is the floating\n * ref for the prerelease train (npm's `rc` dist-tag mirror): it moves on\n * every prerelease deploy and freezes at the last rc-train build once the\n * workspace releases a stable version.\n */\nexport function computeImageTags(input: { shortSha: string; version?: string }): ImageTags {\n const tags = [`sha-${input.shortSha}`];\n const match = input.version?.trim().match(SEMVER_PATTERN);\n if (!match) {\n return { tags, pushLatest: true, pushRc: false };\n }\n const [, major, minor, patch, prerelease] = match;\n const exact = prerelease\n ? `v${major}.${minor}.${patch}-${prerelease}`\n : `v${major}.${minor}.${patch}`;\n if (prerelease) {\n return {\n tags: [...tags, exact],\n pushLatest: false,\n latestHoldReason: `${exact} is a prerelease — :latest still serves the last stable image`,\n pushRc: true,\n };\n }\n return { tags: [...tags, exact, `v${major}`], pushLatest: true, pushRc: false };\n}\n\nexport interface ImageDeployResult {\n image: string;\n /** Primary pinned tag (the sha tag). */\n tag: string;\n /** All tags pushed, including the primary. */\n tags: string[];\n latestPushed: boolean;\n /** Whether the floating `:rc` prerelease-train ref was pushed. */\n rcPushed: boolean;\n digest?: string;\n}\n\nexport type DeployImagePlan =\n | { kind: \"prebuilt\"; image: string; digest: string }\n | { kind: \"build\"; image: string }\n | { kind: \"skip\"; reason: string };\n\nconst DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;\n\n/** A Railway pin is `FROM image@<digest>` — anything else cannot resolve. */\nexport function isValidImageDigest(digest: string): boolean {\n return DIGEST_PATTERN.test(digest);\n}\n\n/**\n * Decide the image leg's source: a digest handed over from a pre-pushed image\n * (the CI image job) wins; otherwise the local docker build runs when the\n * toolchain and Dockerfile are present (ADR 0021, as amended).\n */\nexport function resolveDeployImagePlan(input: {\n imageRef: ResolvedImageRef | undefined;\n imageDigest?: string;\n hasDocker: boolean;\n hasDockerfile: boolean;\n}): DeployImagePlan {\n if (!input.imageRef) {\n return {\n kind: \"skip\",\n reason: \"set ci.image in bos.config.json (or BOS_IMAGE) to build and push the runtime image\",\n };\n }\n if (input.imageDigest) {\n return { kind: \"prebuilt\", image: input.imageRef.image, digest: input.imageDigest };\n }\n if (!input.hasDocker) {\n return { kind: \"skip\", reason: \"docker is not available\" };\n }\n if (!input.hasDockerfile) {\n return { kind: \"skip\", reason: \"no Dockerfile at the config root\" };\n }\n return { kind: \"build\", image: input.imageRef.image };\n}\n\n/**\n * Image leg (ADR 0021): build the runtime stage, tag `sha-<short>` + version\n * tags, push them, capture the digest, then push the floating refs — `:latest`\n * unless the version is a prerelease, and `:rc` when it is. Callers skip the\n * leg with a notice when no image is configured (child repos get\n * build+upload+publish only).\n */\nexport async function buildAndPushImage(input: {\n image: string;\n configDir: string;\n verbose?: boolean;\n}): Promise<ImageDeployResult> {\n const shaResult = await run(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"], {\n cwd: input.configDir,\n capture: true,\n });\n const shortSha = shaResult?.stdout.trim();\n if (!shaResult || shaResult.exitCode !== 0 || !shortSha) {\n const detail = shaResult?.stderr.trim();\n throw new Error(\n `Failed to resolve the current git SHA in ${input.configDir} (exit code ${shaResult?.exitCode ?? \"unknown\"}${detail ? `: ${detail}` : \"\"})`,\n );\n }\n\n const version = readFrameworkVersion(input.configDir);\n const { tags, pushLatest, latestHoldReason, pushRc } = computeImageTags({\n shortSha,\n version,\n });\n const refs = tags.map((tag) => `${input.image}:${tag}`);\n const latestRef = `${input.image}:latest`;\n const rcRef = `${input.image}:rc`;\n const buildRefs = pushLatest ? [...refs, latestRef] : [...refs];\n if (pushRc) buildRefs.push(rcRef);\n\n console.log(\n ` Building runtime image ${colors.cyan(buildRefs.join(\", \"))}${pushLatest ? \"\" : colors.yellow(\" (latest held)\")}`,\n );\n await run(\n \"docker\",\n [\"build\", \"--target\", \"runtime\", ...buildRefs.flatMap((ref) => [\"-t\", ref]), \".\"],\n { cwd: input.configDir },\n );\n\n console.log(` Pushing ${colors.cyan(refs.join(\", \"))}...`);\n let digest: string | undefined;\n for (const ref of refs) {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n }\n\n const pushFloating = async (ref: string): Promise<boolean> => {\n const push = await run(\"docker\", [\"push\", ref], {\n cwd: input.configDir,\n capture: true,\n });\n if (!push || push.exitCode !== 0) {\n throw new Error(`docker push ${ref} failed with exit code ${push?.exitCode ?? \"unknown\"}`);\n }\n digest ??= parseDigest(push.stdout);\n if (input.verbose && push.stdout.trim()) {\n console.log(colors.dim(push.stdout.trim()));\n }\n return true;\n };\n\n let latestPushed = false;\n if (pushLatest) {\n latestPushed = await pushFloating(latestRef);\n } else {\n console.log(colors.yellow(` latest held: ${latestHoldReason}`));\n }\n const rcPushed = pushRc ? await pushFloating(rcRef) : false;\n\n if (!digest) {\n console.log(colors.yellow(\" Could not capture the image digest from push output\"));\n }\n\n return { image: input.image, tag: tags[0]!, tags, latestPushed, rcPushed, digest };\n}\n\n/**\n * Railway leg (ADR 0021, amended): image-backed deploy. The service's source\n * is a Docker image ref (connected once in the dashboard — `:rc` during the\n * prerelease train), and `redeploy --from-source` pulls and deploys whatever\n * that ref points at. The image leg pushes `:rc` (and the `sha-<short>` /\n * version tags) immediately before this step, so the pulled bytes are exactly\n * what this train validated. Railway skips the build entirely for\n * Docker-image sources — nothing is uploaded, nothing is built on Railway,\n * and the digest-pin Dockerfile mechanism this replaces is gone (the CLI v5\n * dropped RAILWAY_DOCKERFILE_PATH, its snapshot respects .gitignore so\n * generated Dockerfiles never shipped, and its Metal builder rejected the\n * repo Dockerfile's cache-mount ids).\n */\nexport async function deployImageToRailway(input: {\n service: string;\n configDir: string;\n}): Promise<void> {\n console.log(` Deploying to Railway service ${colors.cyan(input.service)} (image-backed)...`);\n // The probe is authoritative: runners don't ship the CLI, and a spawn\n // failure resolves (reject:false) as exitCode 1 with empty output — the\n // 2026-10-13 deploy surfaced exactly that as a silent instant failure.\n let version: string | undefined;\n try {\n version = (await run(\"railway\", [\"--version\"], { cwd: input.configDir, capture: true }))\n ?.stdout;\n } catch {\n version = undefined;\n }\n if (!version?.trim()) {\n throw new Error(\"Railway CLI not found. Install it: npm i -g @railway/cli\");\n }\n console.log(` ${colors.dim(`railway ${version.trim()}`)}`);\n\n // Stream the CLI's output live — a silent exit (empty stdout+stderr, seen\n // on the 2026-10-13 deploy) must still be diagnosable from the workflow log.\n const collected = { stdout: \"\", stderr: \"\" };\n const result = await run(\n \"railway\",\n [\"redeploy\", \"--service\", input.service, \"--from-source\", \"--yes\"],\n {\n cwd: input.configDir,\n capture: true,\n onChunk: (stream, chunk) => {\n collected[stream] += chunk.toString(\"utf-8\");\n for (const line of chunk.toString(\"utf-8\").split(\"\\n\")) {\n if (line.trim()) console.log(` ${colors.dim(line.trim())}`);\n }\n },\n },\n );\n if (!result || result.exitCode !== 0) {\n const exitCode = result?.exitCode ?? \"unknown\";\n const tail = (stream: string, text: string) =>\n text.trim()\n ? `\\nrailway ${stream} (tail):\\n${text.trim().split(\"\\n\").slice(-20).join(\"\\n\")}`\n : \"\";\n throw new Error(\n `railway redeploy failed with exit code ${exitCode}` +\n tail(\"stdout\", collected.stdout) +\n tail(\"stderr\", collected.stderr),\n );\n }\n console.log(colors.green(` ${icons.ok} Railway deploy complete`));\n}\n"],"mappings":"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"}
|
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":";;;;;AAgBA,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,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAC3B,MAAM,qCAAqB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG,CAAC;AACjE,MAAM,SAAS,OAAe,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AAC9E,MAAM,aAAa,YAAoB,MAAO,UAAU,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAEtF,IAAM,oBAAN,MAAM,0BAA0BC,YAAK,YAAY,mBAAmB,CAAC,CAIlE;CACD,YAAY,QAAgB,QAAgB,MAAe;EACzD,MAAM;GAAE;GAAQ;GAAQ;EAAK,CAAC;CAChC;CAEA,IAAa,UAAU;EACrB,OAAO,GAAG,KAAK,SAAS,KAAK,QAAQ;CACvC;CAEA,SAAS,MAAc;EACrB,OAAO,IAAI,kBAAkB,KAAK,QAAQ,KAAK,QAAQ,GAAG,KAAK,QAAQ,KAAK,MAAM;CACpF;AACF;AAEA,SAAS,sBAAsB,QAAgB,QAAwB;CACrE,MAAM,UAAU,OAAO,MAAM,GAAG,GAAG;CACnC,IAAI,WAAW,OAAO,2CAA2C,KAAK,OAAO,GAC3E,OAAO,GAAG,QAAQ;CAEpB,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,MAAM;EAChC,IAAI,QAAQ,SAAS,2BAA2B,QAAQ,YAAY,yBAClE,OAAO,GAAG,QAAQ;CAEtB,QAAQ,CAER;CACA,OAAO;AACT;AAEA,eAAsB,aAAa,OAQH;CAC9B,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,UAAkC,EAAE,gBAAgB,mBAAmB;CAC7E,IAAI,MAAM,QAAQ,QAAQ,eAAe,MAAM;CAE/C,MAAM,OAAO,KAAK,UAAU;EAC1B,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;CAED,IAAI;CACJ,IAAI,oBAAoB;CACxB,IAAI,iBAAiB;CACrB,KAAK,IAAI,UAAU,GAAG,WAAW,oBAAoB,WAAW;EAC9D,IAAI;GACF,MAAM,WAAW,MAAM,UAAU,GAAG,MAAM,OAAO,QAAQ,OAAO,EAAE,EAAE,uBAAuB;IACzF,QAAQ;IACR,SACE,oBAAoB,IAGhB;KAAE,GAAG;KAAS,YAAY;IAAQ,IAClC;IACN;GACF,CAAC;GAED,IAAI,SAAS,IACX,OAAQ,MAAM,SAAS,KAAK;GAG9B,MAAM,SAAS,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;GACnD,YAAY,IAAI,kBACd,SAAS,QACT,+BAA+B,MAAM,UAAU,WAAW,SAAS,OAAO,GAAG,sBAAsB,SAAS,QAAQ,MAAM,GAC5H;GAGA,IAAI,CAAC,mBAAmB,IAAI,SAAS,MAAM,GAAG;GAC9C,kBAAkB;GAClB,IAAI,kBAAkB,iBAAiB;EACzC,SAAS,OAAO;GACd,qBAAqB;GACrB,YAAY,IAAI,kBACd,GACA,+BAA+B,MAAM,UAAU,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACjH;EACF;EAEA,IAAI,UAAU,oBAAoB,MAAM,MAAM,UAAU,OAAO,CAAC;CAClE;CAEA,IAAI,qBAAqB,qBAAqB,UAAU,WAAW,GACjE,YAAY,UAAU,SACpB,qEACF;CAEF,MAAM,6BAAa,IAAI,MAAM,+BAA+B,MAAM,UAAU,QAAQ;AACtF;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,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
|
+
{"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":";;;;;AAgBA,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,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAC3B,MAAM,qCAAqB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG,CAAC;AACjE,MAAM,SAAS,OAAe,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AAC9E,MAAM,aAAa,YAAoB,MAAO,UAAU,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAEtF,IAAM,oBAAN,MAAM,0BAA0B,KAAK,YAAY,mBAAmB,CAAC,CAIlE;CACD,YAAY,QAAgB,QAAgB,MAAe;EACzD,MAAM;GAAE;GAAQ;GAAQ;EAAK,CAAC;CAChC;CAEA,IAAa,UAAU;EACrB,OAAO,GAAG,KAAK,SAAS,KAAK,QAAQ;CACvC;CAEA,SAAS,MAAc;EACrB,OAAO,IAAI,kBAAkB,KAAK,QAAQ,KAAK,QAAQ,GAAG,KAAK,QAAQ,KAAK,MAAM;CACpF;AACF;AAEA,SAAS,sBAAsB,QAAgB,QAAwB;CACrE,MAAM,UAAU,OAAO,MAAM,GAAG,GAAG;CACnC,IAAI,WAAW,OAAO,2CAA2C,KAAK,OAAO,GAC3E,OAAO,GAAG,QAAQ;CAEpB,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,MAAM;EAChC,IAAI,QAAQ,SAAS,2BAA2B,QAAQ,YAAY,yBAClE,OAAO,GAAG,QAAQ;CAEtB,QAAQ,CAER;CACA,OAAO;AACT;AAEA,eAAsB,aAAa,OAQH;CAC9B,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,UAAkC,EAAE,gBAAgB,mBAAmB;CAC7E,IAAI,MAAM,QAAQ,QAAQ,eAAe,MAAM;CAE/C,MAAM,OAAO,KAAK,UAAU;EAC1B,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;CAED,IAAI;CACJ,IAAI,oBAAoB;CACxB,IAAI,iBAAiB;CACrB,KAAK,IAAI,UAAU,GAAG,WAAW,oBAAoB,WAAW;EAC9D,IAAI;GACF,MAAM,WAAW,MAAM,UAAU,GAAG,MAAM,OAAO,QAAQ,OAAO,EAAE,EAAE,uBAAuB;IACzF,QAAQ;IACR,SACE,oBAAoB,IAGhB;KAAE,GAAG;KAAS,YAAY;IAAQ,IAClC;IACN;GACF,CAAC;GAED,IAAI,SAAS,IACX,OAAQ,MAAM,SAAS,KAAK;GAG9B,MAAM,SAAS,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;GACnD,YAAY,IAAI,kBACd,SAAS,QACT,+BAA+B,MAAM,UAAU,WAAW,SAAS,OAAO,GAAG,sBAAsB,SAAS,QAAQ,MAAM,GAC5H;GAGA,IAAI,CAAC,mBAAmB,IAAI,SAAS,MAAM,GAAG;GAC9C,kBAAkB;GAClB,IAAI,kBAAkB,iBAAiB;EACzC,SAAS,OAAO;GACd,qBAAqB;GACrB,YAAY,IAAI,kBACd,GACA,+BAA+B,MAAM,UAAU,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACjH;EACF;EAEA,IAAI,UAAU,oBAAoB,MAAM,MAAM,UAAU,OAAO,CAAC;CAClE;CAEA,IAAI,qBAAqB,qBAAqB,UAAU,WAAW,GACjE,YAAY,UAAU,SACpB,qEACF;CAEF,MAAM,6BAAa,IAAI,MAAM,+BAA+B,MAAM,UAAU,QAAQ;AACtF;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,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
|
+
{"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 = {};
|