From cc43db2a580b6d5b34d05e266f8668dc3c5bea99 Mon Sep 17 00:00:00 2001 From: clibequilibrium <105135724+clibequilibrium@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:09:37 -0400 Subject: [PATCH 1/8] Console drops the hosting agent Nobody deploys apps through managed hosting, and Workers for Platforms is a paid dependency we no longer want to carry, so the console stops speaking to it: the registry entry and sidebar section, the hosting API proxy and OpenAPI module, the GitHub App push-to-deploy flow, deploy tokens (cfbd_) and the Actions OIDC bearer in the guard, and the D1 tables they owned. Existing app/deploy_token/github_* tables are left in place untouched; nothing reads them any more. The e2e stack loses its :8800 hosting server; the landing page lists four live primitives instead of five. --- .prettierignore | 7 - e2e/deploy-token.api.spec.ts | 163 ---- e2e/github-connect.api.spec.ts | 213 ----- e2e/helpers.ts | 83 -- e2e/hosting-env.api.spec.ts | 270 ------ e2e/hosting-real.api.spec.ts | 135 --- e2e/hosting.api.spec.ts | 367 -------- e2e/hosting.ui.spec.ts | 146 ---- e2e/landing.ui.spec.ts | 2 +- e2e/service-keys.api.spec.ts | 3 +- eslint.config.js | 1 - package.json | 4 +- playwright.config.ts | 7 - scripts/local-dev.json | 6 +- scripts/postinstall.json | 4 - src/app.d.ts | 27 - src/hooks.server.ts | 105 +-- src/lib/agent-registry.ts | 8 +- src/lib/agents.ts | 322 -------- src/lib/branch-match.ts | 53 -- src/lib/branch-match.unit.test.ts | 49 -- src/lib/components/console-shell.svelte | 8 +- src/lib/components/github-mark.svelte | 24 - src/lib/hosting-workflow.ts | 330 -------- src/lib/hosting-workflow.unit.test.ts | 162 ---- src/lib/openapi/hosting.ts | 470 ----------- src/lib/openapi/index.ts | 11 +- src/lib/openapi/shared.ts | 2 +- src/lib/server/db/index.ts | 55 +- src/lib/server/db/schema.ts | 172 +--- src/lib/server/frameworks.ts | 314 ------- src/lib/server/frameworks.unit.test.ts | 222 ----- src/lib/server/github-connect.ts | 781 ------------------ src/lib/server/github-deploy.ts | 245 ------ src/lib/server/github-oidc.ts | 167 ---- src/lib/server/github-repo.ts | 479 ----------- src/lib/server/github.ts | 365 -------- src/lib/server/hosting.ts | 422 ---------- src/lib/server/registry.ts | 9 - src/lib/server/service-keys.ts | 6 +- src/params/hostingapp.ts | 9 - .../(app)/dashboard/(account)/+page.svelte | 2 +- .../dashboard/[projectId]/+layout.svelte | 3 +- .../(app)/dashboard/[projectId]/+page.svelte | 2 +- .../[projectId]/hosting/+page.server.ts | 70 -- .../[projectId]/hosting/+page.svelte | 771 ----------------- .../[[tab=hostingapp]]/+page.server.ts | 92 --- .../[appName]/[[tab=hostingapp]]/+page.svelte | 706 ---------------- .../hosting/connect-github-dialog.svelte | 497 ----------- .../[projectId]/hosting/deploys-table.svelte | 125 --- .../[projectId]/hosting/vars-editor.svelte | 256 ------ .../[projectId]/settings/+page.svelte | 4 +- src/routes/(marketing)/+page.svelte | 7 +- src/routes/+layout.svelte | 4 +- src/routes/api/github/callback/+server.ts | 91 -- src/routes/api/github/webhook/+server.ts | 59 -- .../hosting/apps/[appName]/+server.ts | 64 -- .../apps/[appName]/analytics/+server.ts | 24 - .../apps/[appName]/build-env/+server.ts | 50 -- .../[appName]/build-secrets/[name]/+server.ts | 54 -- .../apps/[appName]/build-vars/+server.ts | 29 - .../hosting/apps/[appName]/deploys/+server.ts | 68 -- .../hosting/apps/[appName]/secrets/+server.ts | 44 - .../apps/[appName]/secrets/[name]/+server.ts | 25 - .../hosting/apps/[appName]/vars/+server.ts | 47 -- .../[projectId]/hosting/claims/+server.ts | 63 -- .../[projectId]/hosting/deploys/+server.ts | 14 - .../[projectId]/hosting/github/+server.ts | 26 - .../hosting/github/connections/+server.ts | 36 - .../github/connections/[appName]/+server.ts | 40 - .../hosting/github/install/+server.ts | 50 -- .../hosting/github/repos/+server.ts | 64 -- .../[projectId]/hosting/overview/+server.ts | 12 - .../[projectId]/hosting/tokens/+server.ts | 26 - .../hosting/tokens/[tokenId]/+server.ts | 13 - src/worker-configuration.d.ts | 10 +- wrangler.e2e.jsonc | 1 - wrangler.jsonc | 7 +- 78 files changed, 38 insertions(+), 9646 deletions(-) delete mode 100644 e2e/deploy-token.api.spec.ts delete mode 100644 e2e/github-connect.api.spec.ts delete mode 100644 e2e/hosting-env.api.spec.ts delete mode 100644 e2e/hosting-real.api.spec.ts delete mode 100644 e2e/hosting.api.spec.ts delete mode 100644 e2e/hosting.ui.spec.ts delete mode 100644 src/lib/branch-match.ts delete mode 100644 src/lib/branch-match.unit.test.ts delete mode 100644 src/lib/components/github-mark.svelte delete mode 100644 src/lib/hosting-workflow.ts delete mode 100644 src/lib/hosting-workflow.unit.test.ts delete mode 100644 src/lib/openapi/hosting.ts delete mode 100644 src/lib/server/frameworks.ts delete mode 100644 src/lib/server/frameworks.unit.test.ts delete mode 100644 src/lib/server/github-connect.ts delete mode 100644 src/lib/server/github-deploy.ts delete mode 100644 src/lib/server/github-oidc.ts delete mode 100644 src/lib/server/github-repo.ts delete mode 100644 src/lib/server/github.ts delete mode 100644 src/lib/server/hosting.ts delete mode 100644 src/params/hostingapp.ts delete mode 100644 src/routes/(app)/dashboard/[projectId]/hosting/+page.server.ts delete mode 100644 src/routes/(app)/dashboard/[projectId]/hosting/+page.svelte delete mode 100644 src/routes/(app)/dashboard/[projectId]/hosting/apps/[appName]/[[tab=hostingapp]]/+page.server.ts delete mode 100644 src/routes/(app)/dashboard/[projectId]/hosting/apps/[appName]/[[tab=hostingapp]]/+page.svelte delete mode 100644 src/routes/(app)/dashboard/[projectId]/hosting/connect-github-dialog.svelte delete mode 100644 src/routes/(app)/dashboard/[projectId]/hosting/deploys-table.svelte delete mode 100644 src/routes/(app)/dashboard/[projectId]/hosting/vars-editor.svelte delete mode 100644 src/routes/api/github/callback/+server.ts delete mode 100644 src/routes/api/github/webhook/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/analytics/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-env/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-secrets/[name]/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-vars/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/deploys/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/secrets/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/secrets/[name]/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/apps/[appName]/vars/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/claims/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/deploys/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/github/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/github/connections/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/github/connections/[appName]/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/github/install/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/github/repos/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/overview/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/tokens/+server.ts delete mode 100644 src/routes/api/projects/[projectId]/hosting/tokens/[tokenId]/+server.ts diff --git a/.prettierignore b/.prettierignore index 402facbc..eab7b1d8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,29 +2,24 @@ src/lib/components/ui/ agents/auth/drizzle/meta/ agents/db/drizzle/meta/ -agents/hosting/drizzle/meta/ agents/storage/drizzle/meta/ # Regenerated by drizzle-kit; formatting it here is undone on the next generate. agents/auth/drizzle/migrations.js agents/db/drizzle/migrations.js -agents/hosting/drizzle/migrations.js agents/storage/drizzle/migrations.js **/worker-configuration.d.ts AGENTS.md agents/auth/AGENTS.md agents/db/AGENTS.md -agents/hosting/AGENTS.md agents/storage/AGENTS.md **/package-lock.json agents/auth/.prettierrc agents/db/.prettierrc -agents/hosting/.prettierrc agents/storage/.prettierrc # Publish build output for the published packages. agents/auth/dist/ agents/db/dist/ -agents/hosting/dist/ agents/storage/dist/ cli/dist/ @@ -36,8 +31,6 @@ agents/auth/LICENSE agents/auth/NOTICE agents/db/LICENSE agents/db/NOTICE -agents/hosting/LICENSE -agents/hosting/NOTICE agents/storage/LICENSE agents/storage/NOTICE cli/LICENSE diff --git a/e2e/deploy-token.api.spec.ts b/e2e/deploy-token.api.spec.ts deleted file mode 100644 index d01cf3d4..00000000 --- a/e2e/deploy-token.api.spec.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { request as playwrightRequest, expect, test } from '@playwright/test'; -import { - hostingAppPath, - hostingDeployPath, - hostingOverviewPath, - hostingTokenPath, - hostingTokensPath, - projectBranchesPath, - registryProjectPath, - SEED_PROJECT -} from './helpers'; - -/** - * Deploy tokens (Phase B): CI's durable - * credential. The contract this file pins: minted on roots only, the secret - * appears exactly once and is stored hashed, the `cfbd_` bearer is accepted - * SOLELY on the deploy and branch-create endpoints for the token's family - - * everywhere else it is a plain 401 - and revocation ends it immediately. - */ - -const runId = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.slice(0, 12); -const rootId = `e2e-tok-${runId}`; -const appName = `tok-${runId}`; - -const deployBody = { - multipart: { - meta: JSON.stringify({}), - 'asset:/index.html': { - name: 'index.html', - mimeType: 'text/html', - buffer: Buffer.from('

token deploy

') - } - } -}; - -/** A cookie-less context whose only credential is the deploy token. */ -function tokenContext(baseURL: string | undefined, token: string) { - return playwrightRequest.newContext({ - baseURL, - extraHTTPHeaders: { origin: baseURL ?? '', authorization: `Bearer ${token}` }, - storageState: { cookies: [], origins: [] } - }); -} - -test.describe('deploy tokens', () => { - let secret = ''; - let tokenId = ''; - - test.beforeAll(async ({ request }) => { - const created = await request.post('/api/registry/projects', { - data: { id: rootId, name: `Token e2e ${rootId}` } - }); - expect(created.status(), await created.text()).toBe(201); - }); - - test.afterAll(async ({ request }) => { - await request.delete(registryProjectPath(rootId)); - }); - - test('minting returns the secret exactly once', async ({ request }) => { - const minted = await request.post(hostingTokensPath(rootId), { data: { name: 'ci' } }); - expect(minted.status(), await minted.text()).toBe(201); - const body = (await minted.json()) as { id: string; token: string }; - expect(body.token).toMatch(/^cfbd_[0-9a-f]{64}$/); - secret = body.token; - tokenId = body.id; - - // The list carries metadata only - the secret is unrecoverable. - const listed = await request.get(hostingTokensPath(rootId)); - expect(listed.status(), await listed.text()).toBe(200); - const { tokens } = (await listed.json()) as { tokens: Record[] }; - expect(tokens.some((token) => token.id === tokenId)).toBe(true); - for (const token of tokens) { - expect(token.token).toBeUndefined(); - expect(token.tokenHash).toBeUndefined(); - } - }); - - test('branch rows refuse minting - tokens live on the root', async ({ request }) => { - const branch = await request.post(projectBranchesPath(rootId), { data: { branch: 'ci' } }); - expect(branch.status(), await branch.text()).toBe(201); - const minted = await request.post(hostingTokensPath(`${rootId}--ci`), { - data: { name: 'nope' } - }); - expect(minted.status(), await minted.text()).toBe(400); - }); - - test('a deploy token deploys - and does nothing else', async ({ baseURL }) => { - const bearer = await tokenContext(baseURL, secret); - try { - const deploy = await bearer.post(hostingDeployPath(rootId, appName), deployBody); - expect(deploy.status(), await deploy.text()).toBe(201); - - // Not a session: every other surface answers 401. - const overview = await bearer.get(hostingOverviewPath(rootId)); - expect(overview.status(), 'a deploy token must not read the overview').toBe(401); - const registry = await bearer.get('/api/registry/projects'); - expect(registry.status(), 'a deploy token must not list projects').toBe(401); - const tokens = await bearer.get(hostingTokensPath(rootId)); - expect(tokens.status(), 'a deploy token must not read tokens').toBe(401); - - // Not transferable: another project's deploys refuse it. - const foreign = await bearer.post(hostingDeployPath(SEED_PROJECT, appName), deployBody); - expect(foreign.status(), 'a deploy token is scoped to its family').toBe(401); - - // Not destructive: a credential that ships code must never erase an - // app - deletion is a session-only surface. - const erase = await bearer.delete(hostingAppPath(rootId, appName)); - expect(erase.status(), 'a deploy token must not delete apps').toBe(401); - } finally { - await bearer.dispose(); - } - }); - - test('a deploy token creates branch rows for new git branches', async ({ baseURL }) => { - const bearer = await tokenContext(baseURL, secret); - try { - const created = await bearer.post(projectBranchesPath(rootId), { - data: { branch: 'preview-x' } - }); - expect(created.status(), await created.text()).toBe(201); - const again = await bearer.post(projectBranchesPath(rootId), { - data: { branch: 'preview-x' } - }); - expect(again.status(), 'the second create is the idempotent 409').toBe(409); - - // ... and can then deploy to the branch it minted. - const deploy = await bearer.post( - hostingDeployPath(`${rootId}--preview-x`, appName), - deployBody - ); - expect(deploy.status(), await deploy.text()).toBe(201); - expect(((await deploy.json()) as { subdomain: string }).subdomain).toBe( - `${appName}-preview-x` - ); - } finally { - await bearer.dispose(); - } - }); - - test('revocation ends the token immediately', async ({ request, baseURL }) => { - const revoked = await request.delete(hostingTokenPath(rootId, tokenId)); - expect(revoked.status(), await revoked.text()).toBe(200); - - const bearer = await tokenContext(baseURL, secret); - try { - const deploy = await bearer.post(hostingDeployPath(rootId, appName), deployBody); - expect(deploy.status(), 'a revoked token must not deploy').toBe(401); - } finally { - await bearer.dispose(); - } - }); - - test('a fabricated token is a plain 401', async ({ baseURL }) => { - const bearer = await tokenContext(baseURL, `cfbd_${'0'.repeat(64)}`); - try { - const deploy = await bearer.post(hostingDeployPath(rootId, appName), deployBody); - expect(deploy.status()).toBe(401); - } finally { - await bearer.dispose(); - } - }); -}); diff --git a/e2e/github-connect.api.spec.ts b/e2e/github-connect.api.spec.ts deleted file mode 100644 index 63c77fa6..00000000 --- a/e2e/github-connect.api.spec.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { request as playwrightRequest, expect, test } from '@playwright/test'; -import { - githubConnectionPath, - githubConnectionsPath, - githubInstallPath, - githubReposPath, - githubStatePath, - GITHUB_WEBHOOK_PATH, - hostingDeployPath, - projectBranchesPath, - registryProjectPath, - SEED_PROJECT -} from './helpers'; - -/** - * GitHub push-to-deploy (Phase B). - * - * This stack configures NO GitHub App, which is the self-hosted default and - * the important half of the contract to pin: adding push-to-deploy must not - * open a single new surface on an install that never asked for it. The - * connect flow answers 503 honestly, the webhook does not exist, and none of - * it is reachable without an operator session. - * - * The parts that need a real App (installations, repository reads, workflow - * writes) need GitHub itself, so they are not e2e territory. The pure logic - * underneath is unit tested in agents/hosting (`tar.unit.test.ts`). - */ - -const runId = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.slice(0, 12); -const rootId = `e2e-gh-${runId}`; - -/** A cookie-less context - the guard must not see a session. */ -function anonymous(baseURL: string | undefined, headers: Record = {}) { - return playwrightRequest.newContext({ - baseURL, - extraHTTPHeaders: { origin: baseURL ?? '', ...headers }, - storageState: { cookies: [], origins: [] } - }); -} - -test.describe('github connect', () => { - test.beforeAll(async ({ request }) => { - const created = await request.post('/api/registry/projects', { - data: { id: rootId, name: `GitHub e2e ${rootId}` } - }); - expect(created.status(), await created.text()).toBe(201); - }); - - test.afterAll(async ({ request }) => { - await request.delete(registryProjectPath(rootId)); - }); - - test('an unconfigured console reports no App rather than half a flow', async ({ request }) => { - const state = await request.get(githubStatePath(rootId)); - expect(state.status(), await state.text()).toBe(200); - const body = (await state.json()) as { - configured: boolean; - installations: unknown[]; - connections: unknown[]; - }; - // False is what makes the Hosting page offer the manual token flow. - expect(body.configured).toBe(false); - expect(body.installations).toEqual([]); - expect(body.connections).toEqual([]); - }); - - test('connecting without an App is an honest 503, never a silent no-op', async ({ request }) => { - const install = await request.get(githubInstallPath(rootId)); - expect(install.status(), await install.text()).toBe(503); - - const repos = await request.get(githubReposPath(rootId)); - expect(repos.status(), await repos.text()).toBe(503); - - const connect = await request.post(githubConnectionsPath(rootId), { - data: { installationId: 1, repoFullName: 'acme/site', appName: 'site', mode: 'build' } - }); - expect(connect.status(), await connect.text()).toBe(503); - }); - - test('a connection that does not exist is a 404, not a 500', async ({ request }) => { - const removed = await request.delete(githubConnectionPath(rootId, 'never-connected')); - expect(removed.status(), await removed.text()).toBe(404); - }); - - test('build settings validate before the connection is even looked up', async ({ request }) => { - // Charset-limited because these values embed into workflow YAML. - for (const data of [ - { ignoredBranches: ["quo'te"] }, - { ignoredBranches: ['two words'] }, - { productionBranch: 'no spaces here' }, - { buildCommand: 'line one\nline two' }, - { unknownField: true } - ]) { - const patched = await request.patch(githubConnectionPath(rootId, 'never-connected'), { - data - }); - expect(patched.status(), JSON.stringify(data)).toBe(400); - } - - // A valid body against a missing connection is the honest 404. - const missing = await request.patch(githubConnectionPath(rootId, 'never-connected'), { - data: { productionBranch: 'release' } - }); - expect(missing.status(), await missing.text()).toBe(404); - }); - - test('every github surface requires an operator session', async ({ baseURL }) => { - const guest = await anonymous(baseURL); - try { - // The callback is deliberately NOT in this list: it is a cross-site - // return from github.com, authenticated by the signed state instead - // (covered by its own test below). - for (const path of [ - githubStatePath(rootId), - githubInstallPath(rootId), - githubReposPath(rootId), - githubConnectionsPath(rootId) - ]) { - const response = await guest.get(path); - expect(response.status(), `${path} must require an operator`).toBe(401); - } - const connect = await guest.post(githubConnectionsPath(rootId), { - data: { installationId: 1, repoFullName: 'acme/site', appName: 'site', mode: 'build' } - }); - expect(connect.status(), 'connecting must require an operator').toBe(401); - const patched = await guest.patch(githubConnectionPath(rootId, 'site'), { - data: { productionBranch: 'release' } - }); - expect(patched.status(), 'editing build settings must require an operator').toBe(401); - const removed = await guest.delete(githubConnectionPath(rootId, 'site')); - expect(removed.status(), 'disconnecting must require an operator').toBe(401); - } finally { - await guest.dispose(); - } - }); - - test('demo projects cannot start an install', async ({ request }) => { - // No demo hosting anywhere - the install entry point refuses too, so the - // upsell is the only thing a demo visitor can reach. - const install = await request.get(githubInstallPath('demo-abcdef123456')); - expect(install.status(), await install.text()).toBe(403); - }); - - test('the webhook does not exist on a console with no App', async ({ baseURL }) => { - const guest = await anonymous(baseURL, { 'x-github-event': 'ping' }); - try { - const response = await guest.post(GITHUB_WEBHOOK_PATH, { data: { zen: 'hi' } }); - // 404, not 401: there is nothing here to authenticate against. What - // matters is that it never 500s and never acts on an unsigned body. - expect(response.status(), await response.text()).toBe(404); - } finally { - await guest.dispose(); - } - }); - - test('the install callback refuses a state it did not sign', async ({ baseURL }) => { - // The callback is session-less by necessity - it is a cross-site return - // from github.com - so the SIGNED STATE is its credential. A forged or - // absent one must record nothing and bounce, never act on the - // installation_id in the query. - const guest = await anonymous(baseURL); - try { - for (const query of [ - 'installation_id=1&state=forged', - 'installation_id=1&state=eyJwcm9qZWN0SWQiOiJ4In0.bm90LWEtc2ln', - 'installation_id=1' - ]) { - const callback = await guest.get(`/api/github/callback?${query}`, { maxRedirects: 0 }); - // Always a bounce, never a 200 and never a 401 - the operator is - // mid-install and must land somewhere useful. - expect(callback.status(), `${query} must bounce`).toBe(303); - // The invariant that matters: nothing was recorded, so the redirect - // never carries the installation onward into the connect flow. - expect(callback.headers()['location'], `${query} must not be honoured`).not.toContain( - 'installation=' - ); - } - } finally { - await guest.dispose(); - } - }); - - test('a JWT-shaped bearer on the deploy surface is a plain 401', async ({ baseURL }) => { - // GitHub's OIDC token is accepted on exactly the deploy-token surfaces, - // and a forged one must never fall through to session resolution - the - // same all-or-nothing contract a `cfbd_` bearer has. - const forged = - 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImZha2UifQ.eyJyZXBvc2l0b3J5IjoiYWNtZS9zaXRlIn0.c2ln'; - const bearer = await anonymous(baseURL, { authorization: `Bearer ${forged}` }); - try { - const deploy = await bearer.post(hostingDeployPath(rootId, 'forged'), { - multipart: { - meta: JSON.stringify({}), - 'asset:/index.html': { - name: 'index.html', - mimeType: 'text/html', - buffer: Buffer.from('

nope

') - } - } - }); - expect(deploy.status(), 'a forged identity token must not deploy').toBe(401); - - const branch = await bearer.post(projectBranchesPath(rootId), { data: { branch: 'forged' } }); - expect(branch.status(), 'a forged identity token must not create branches').toBe(401); - - // And it is not a session anywhere else either. - const state = await bearer.get(githubStatePath(SEED_PROJECT)); - expect(state.status(), 'a forged identity token must not read console state').toBe(401); - } finally { - await bearer.dispose(); - } - }); -}); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index b504eb2a..bc28ef72 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -225,89 +225,6 @@ export function dbViewSqlPath(projectId: string, view: string): string { return `/api/projects/${projectId}/db/views/${view}/sql`; } -// --- Hosting (Phase B) --- - -export function hostingOverviewPath(projectId: string): string { - return `/api/projects/${projectId}/hosting/overview`; -} - -export function hostingClaimsPath(projectId: string): string { - return `/api/projects/${projectId}/hosting/claims`; -} - -export function hostingDeploysPath(projectId: string): string { - return `/api/projects/${projectId}/hosting/deploys`; -} - -export function hostingDeployPath(projectId: string, app: string): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}/deploys`; -} - -export function hostingAppPath(projectId: string, app: string): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}`; -} - -export function hostingVarsPath(projectId: string, app: string): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}/vars`; -} - -export function hostingSecretsPath(projectId: string, app: string): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}/secrets`; -} - -export function hostingSecretPath(projectId: string, app: string, name: string): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}/secrets/${encodeURIComponent(name)}`; -} - -export function hostingBuildEnvPath(projectId: string, app: string): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}/build-env`; -} - -export function hostingBuildVarsPath(projectId: string, app: string): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}/build-vars`; -} - -export function hostingBuildSecretPath(projectId: string, app: string, name: string): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}/build-secrets/${encodeURIComponent(name)}`; -} - -export function hostingAppAnalyticsPath(projectId: string, app: string, days = 7): string { - return `/api/projects/${projectId}/hosting/apps/${encodeURIComponent(app)}/analytics?days=${days}`; -} - -export function hostingTokensPath(projectId: string): string { - return `/api/projects/${projectId}/hosting/tokens`; -} - -export function hostingTokenPath(projectId: string, tokenId: string): string { - return `/api/projects/${projectId}/hosting/tokens/${encodeURIComponent(tokenId)}`; -} - -// --- GitHub push-to-deploy (Phase B) --- - -export function githubStatePath(projectId: string): string { - return `/api/projects/${projectId}/hosting/github`; -} - -export function githubInstallPath(projectId: string): string { - return `/api/projects/${projectId}/hosting/github/install`; -} - -export function githubReposPath(projectId: string, installation = 1): string { - return `/api/projects/${projectId}/hosting/github/repos?installation=${installation}`; -} - -export function githubConnectionsPath(projectId: string): string { - return `/api/projects/${projectId}/hosting/github/connections`; -} - -export function githubConnectionPath(projectId: string, app: string): string { - return `/api/projects/${projectId}/hosting/github/connections/${encodeURIComponent(app)}`; -} - -/** The webhook is public by exception - its HMAC signature is the credential. */ -export const GITHUB_WEBHOOK_PATH = '/api/github/webhook'; - // --- Storage (S1) --- /** Project the storage specs self-seed. Buckets use FIXED names (creates are diff --git a/e2e/hosting-env.api.spec.ts b/e2e/hosting-env.api.spec.ts deleted file mode 100644 index 1bcc730b..00000000 --- a/e2e/hosting-env.api.spec.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { - hostingBuildEnvPath, - hostingBuildSecretPath, - hostingBuildVarsPath, - hostingDeployPath, - hostingSecretPath, - hostingSecretsPath, - hostingTokensPath, - hostingVarsPath, - registryProjectPath -} from './helpers'; - -/** - * Stored app environments (runtime half): vars round-trip as a - * replace-the-set store applied at deploy time, and secrets are write-through - * names the console can list and delete. Everything runs against the stub - - * which stores names and skips the Cloudflare calls - so the whole contract - * is exercised on every run; the live settings-PATCH/DELETE shapes are pinned - * by unit tests and the opt-in hosting-real spec. - */ - -const runId = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.slice(0, 12); -const projectId = `e2e-env-${runId}`; -const appName = `env-${runId}`; - -const INDEX_HTML = { - name: 'index.html', - mimeType: 'text/html', - buffer: Buffer.from('

env spec

') -}; - -test.describe('hosting app environment (stubbed)', () => { - test.beforeAll(async ({ request }) => { - const created = await request.post('/api/registry/projects', { - data: { id: projectId, name: `Hosting env e2e ${runId}` } - }); - expect(created.status(), await created.text()).toBe(201); - }); - - test.afterAll(async ({ request }) => { - await request.delete(registryProjectPath(projectId)); - }); - - test('vars round-trip, and the set is replaced - not merged', async ({ request }) => { - // Vars need no apps row: a claim-only app can be configured before its - // first deploy. - const put = await request.put(hostingVarsPath(projectId, appName), { - data: { vars: { ALPHA: 'one', BETA: 'two' } } - }); - expect(put.status(), await put.text()).toBe(200); - const putBody = (await put.json()) as { - vars: { name: string; value: string }[]; - patched: boolean; - }; - expect(putBody.vars.map((entry) => entry.name)).toEqual(['ALPHA', 'BETA']); - // Stub mode has no live script to patch. - expect(putBody.patched).toBe(false); - - const replaced = await request.put(hostingVarsPath(projectId, appName), { - data: { vars: { BETA: 'changed', GAMMA: 'three' } } - }); - expect(replaced.status(), await replaced.text()).toBe(200); - - const list = await request.get(hostingVarsPath(projectId, appName)); - expect(list.status(), await list.text()).toBe(200); - const listBody = (await list.json()) as { vars: { name: string; value: string }[] }; - // ALPHA is gone (absent = deleted), BETA updated, GAMMA added. - expect(listBody.vars.map((entry) => [entry.name, entry.value])).toEqual([ - ['BETA', 'changed'], - ['GAMMA', 'three'] - ]); - }); - - test('invalid names and multi-line values are refused', async ({ request }) => { - for (const vars of [ - { 'lower-case': 'x' }, - { '9STARTS_WITH_DIGIT': 'x' }, - { OK: 'line one\nline two' } - ]) { - const put = await request.put(hostingVarsPath(projectId, appName), { data: { vars } }); - expect(put.status(), JSON.stringify(vars)).toBe(400); - } - }); - - test('secrets require a deployed app', async ({ request }) => { - const put = await request.post(hostingSecretsPath(projectId, appName), { - data: { name: 'API_KEY', value: 'hunter2' } - }); - expect(put.status(), await put.text()).toBe(404); - }); - - test('secrets store names in stub mode, list them, and delete idempotently', async ({ - request - }) => { - const deployed = await request.post(hostingDeployPath(projectId, appName), { - multipart: { meta: JSON.stringify({}), 'asset:/index.html': INDEX_HTML } - }); - expect(deployed.status(), await deployed.text()).toBe(201); - - // This 200 pins the 501 -> ok change: stub mode records the name so the - // list/delete contract is exercisable locally. - const put = await request.post(hostingSecretsPath(projectId, appName), { - data: { name: 'API_KEY', value: 'hunter2' } - }); - expect(put.status(), await put.text()).toBe(200); - - const list = await request.get(hostingSecretsPath(projectId, appName)); - expect(list.status(), await list.text()).toBe(200); - const listBody = (await list.json()) as { secrets: { name: string }[] }; - expect(listBody.secrets.map((secret) => secret.name)).toEqual(['API_KEY']); - // Names only - the value must never come back out. - expect(JSON.stringify(listBody)).not.toContain('hunter2'); - - const deleted = await request.delete(hostingSecretPath(projectId, appName, 'API_KEY')); - expect(deleted.status(), await deleted.text()).toBe(200); - - const after = await request.get(hostingSecretsPath(projectId, appName)); - expect(((await after.json()) as { secrets: unknown[] }).secrets).toEqual([]); - - // Idempotent: deleting what is already gone is a 200, not an error. - const again = await request.delete(hostingSecretPath(projectId, appName, 'API_KEY')); - expect(again.status(), await again.text()).toBe(200); - }); - - test('deploys apply stored vars over CLI meta.vars', async ({ request }) => { - const put = await request.put(hostingVarsPath(projectId, appName), { - data: { vars: { SHARED: 'console-wins' } } - }); - expect(put.status(), await put.text()).toBe(200); - - // The stub records the deploy without uploading, so the merge itself is - // pinned by unit tests; here the contract is that a deploy with - // conflicting meta.vars still succeeds and the stored value survives. - const deployed = await request.post(hostingDeployPath(projectId, appName), { - multipart: { - meta: JSON.stringify({ vars: { SHARED: 'cli-loses', CLI_ONLY: 'kept' } }), - 'asset:/index.html': INDEX_HTML - } - }); - expect(deployed.status(), await deployed.text()).toBe(201); - - const list = await request.get(hostingVarsPath(projectId, appName)); - const listBody = (await list.json()) as { vars: { name: string; value: string }[] }; - expect(listBody.vars).toEqual([ - expect.objectContaining({ name: 'SHARED', value: 'console-wins' }) - ]); - }); - - test('build env stores vars, keeps secret values write-only', async ({ request }) => { - const putVars = await request.put(hostingBuildVarsPath(projectId, appName), { - data: { vars: { VITE_API_URL: 'https://api.example' } } - }); - expect(putVars.status(), await putVars.text()).toBe(200); - - // The env.test stack pins HOSTING_MASTER_KEY, so encryption is REAL here - // - stub mode stubs the Cloudflare API, never the crypto. - const putSecret = await request.put(hostingBuildSecretPath(projectId, appName, 'NPM_TOKEN'), { - data: { value: 'npm_supersecret' } - }); - expect(putSecret.status(), await putSecret.text()).toBe(200); - - const env = await request.get(hostingBuildEnvPath(projectId, appName)); - expect(env.status(), await env.text()).toBe(200); - const body = (await env.json()) as { - vars: { name: string; value: string }[]; - secrets: { name: string }[]; - encryptionConfigured: boolean; - }; - expect(body.vars).toEqual([ - expect.objectContaining({ name: 'VITE_API_URL', value: 'https://api.example' }) - ]); - expect(body.secrets.map((secret) => secret.name)).toEqual(['NPM_TOKEN']); - expect(body.encryptionConfigured).toBe(true); - // The operator surface never returns a decrypted value. - expect(JSON.stringify(body)).not.toContain('npm_supersecret'); - - const deleted = await request.delete(hostingBuildSecretPath(projectId, appName, 'NPM_TOKEN')); - expect(deleted.status(), await deleted.text()).toBe(200); - const after = await request.get(hostingBuildEnvPath(projectId, appName)); - expect(((await after.json()) as { secrets: unknown[] }).secrets).toEqual([]); - }); - - test('build-env refuses every wrong bearer', async ({ request, playwright, baseURL }) => { - // A deploy token can ship code, but it is a long-lived credential that - // lives in repository settings - it must never read build secrets. - const minted = await request.post(hostingTokensPath(projectId), { - data: { name: 'env-spec' } - }); - expect(minted.status(), await minted.text()).toBe(201); - const token = ((await minted.json()) as { token: string }).token; - const asDeployToken = await playwright.request.newContext({ - baseURL, - extraHTTPHeaders: { origin: baseURL ?? '', authorization: `Bearer ${token}` }, - storageState: { cookies: [], origins: [] } - }); - try { - const denied = await asDeployToken.get(hostingBuildEnvPath(projectId, appName)); - expect(denied.status(), await denied.text()).toBe(401); - } finally { - await asDeployToken.dispose(); - } - - // An OIDC-shaped bearer that verifies to nothing is a plain 401, never a - // fall-through to session resolution. - const asGarbageOidc = await playwright.request.newContext({ - baseURL, - extraHTTPHeaders: { origin: baseURL ?? '', authorization: 'Bearer x.y.z' }, - storageState: { cookies: [], origins: [] } - }); - try { - const denied = await asGarbageOidc.get(hostingBuildEnvPath(projectId, appName)); - expect(denied.status(), await denied.text()).toBe(401); - } finally { - await asGarbageOidc.dispose(); - } - }); - - test('deleting the app erases its environment', async ({ request }) => { - // Re-seed a build var so the erase has something to prove on. - const putVars = await request.put(hostingBuildVarsPath(projectId, appName), { - data: { vars: { VITE_API_URL: 'https://api.example' } } - }); - expect(putVars.status(), await putVars.text()).toBe(200); - - const deleted = await request.delete(`/api/projects/${projectId}/hosting/apps/${appName}`); - expect(deleted.status(), await deleted.text()).toBe(200); - - const vars = await request.get(hostingVarsPath(projectId, appName)); - expect(((await vars.json()) as { vars: unknown[] }).vars).toEqual([]); - const secrets = await request.get(hostingSecretsPath(projectId, appName)); - expect(((await secrets.json()) as { secrets: unknown[] }).secrets).toEqual([]); - const buildEnv = await request.get(hostingBuildEnvPath(projectId, appName)); - expect(((await buildEnv.json()) as { vars: unknown[] }).vars).toEqual([]); - }); -}); - -test.describe('hosting env guard', () => { - // LOAD-BEARING: without this override the context inherits the operator - // session and proves nothing. - test.use({ storageState: { cookies: [], origins: [] } }); - - test('every environment surface requires an operator session', async ({ request }) => { - const closed = [ - ['GET', hostingVarsPath(projectId, appName)], - ['PUT', hostingVarsPath(projectId, appName)], - ['GET', hostingSecretsPath(projectId, appName)], - ['POST', hostingSecretsPath(projectId, appName)], - ['DELETE', hostingSecretPath(projectId, appName, 'API_KEY')], - ['GET', hostingBuildEnvPath(projectId, appName)], - ['PUT', hostingBuildVarsPath(projectId, appName)], - ['PUT', hostingBuildSecretPath(projectId, appName, 'NPM_TOKEN')], - ['DELETE', hostingBuildSecretPath(projectId, appName, 'NPM_TOKEN')] - ] as const; - for (const [method, path] of closed) { - const response = await request.fetch(path, { - method, - data: method === 'GET' || method === 'DELETE' ? undefined : {} - }); - expect(response.status(), `${method} ${path} must be closed`).toBe(401); - } - }); - - test('the direct agent hop is closed too', async ({ request }) => { - // The console guard has to cover the proxy AND the passthrough, or it - // covers neither. - const direct = await request.get(`/agents/hosting-agent/${projectId}/apps/${appName}/vars`); - expect(direct.status(), await direct.text()).toBe(401); - }); -}); diff --git a/e2e/hosting-real.api.spec.ts b/e2e/hosting-real.api.spec.ts deleted file mode 100644 index 9d5b3c9d..00000000 --- a/e2e/hosting-real.api.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { - hostingDeployPath, - hostingOverviewPath, - hostingSecretPath, - hostingSecretsPath, - hostingVarsPath, - registryProjectPath -} from './helpers'; - -/** - * Real Workers for Platforms upload - opt-in, the RUN_AI_E2E precedent. - * - * Point BASE_URL at a stack whose hosting agent has a real dispatch - * namespace (the -preview namespace on cloudflarebase.com's account) and set - * RUN_HOSTING_E2E=1: this deploys a tiny asset app for real and expects a - * `live` deploy, which exercises the asset upload session, the salted - * manifest hashing, and the script PUT against the actual Cloudflare API. - * Serving is NOT asserted here - preview has no cfbase.dev route, so the - * wildcard only exists in production. - * - * This spec is ALSO the live verification of two API shapes nothing else - * proves (the stub skips both): - * - the settings PATCH that replaces a script's plain_text set works on an - * assets-only script (`PUT vars` answering `patched: true`), and - * - the per-script secrets DELETE endpoint - * (`.../scripts/{script}/secrets/{name}`) exists and answers. - * Do not trust the console's live-edit or secret-delete buttons on a real - * install until this has passed against the target account. - */ - -const runId = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.slice(0, 12); -const rootId = `e2e-real-${runId}`; -const appName = `real-${runId}`; - -test.describe('real dispatch-namespace upload', () => { - test.describe.configure({ mode: 'serial' }); - test.skip( - !process.env.RUN_HOSTING_E2E, - 'set RUN_HOSTING_E2E=1 (against a WfP-configured stack) to test the real upload' - ); - - test.beforeAll(async ({ request }) => { - const created = await request.post('/api/registry/projects', { - data: { id: rootId, name: `Real hosting e2e ${rootId}` } - }); - expect(created.status(), await created.text()).toBe(201); - }); - - test.afterAll(async ({ request }) => { - // Deleting the project also erases the uploaded script by tag. - await request.delete(registryProjectPath(rootId)); - }); - - test('a deploy lands live in the namespace', async ({ request }) => { - const before = await request.get(hostingOverviewPath(rootId)); - expect(before.status(), await before.text()).toBe(200); - const overview = (await before.json()) as { stub: boolean; configured: boolean }; - expect( - overview.stub, - 'the target stack is in HOSTING_STUB mode - this spec needs real credentials' - ).toBe(false); - expect(overview.configured, 'the hosting agent is missing its Cloudflare API config').toBe( - true - ); - - const deploy = await request.post(hostingDeployPath(rootId, appName), { - multipart: { - meta: JSON.stringify({}), - 'asset:/index.html': { - name: 'index.html', - mimeType: 'text/html', - buffer: Buffer.from(`

cloudflarebase real e2e ${runId}

`) - } - } - }); - expect(deploy.status(), await deploy.text()).toBe(201); - const body = (await deploy.json()) as { deploy: { status: string }; subdomain: string }; - expect(body.deploy.status).toBe('live'); - expect(body.subdomain).toBe(appName); - }); - - test('a var edit patches the live assets-only script', async ({ request }) => { - // RISK GATE: proves the settings PATCH (bindings replacement with - // keep_bindings [secret_text, assets]) is accepted for a script that - // has no main module. - const put = await request.put(hostingVarsPath(rootId, appName), { - data: { vars: { REAL_E2E: runId } } - }); - expect(put.status(), await put.text()).toBe(200); - const body = (await put.json()) as { patched: boolean; warning?: string }; - expect(body.warning ?? '').toBe(''); - expect(body.patched, 'the live settings PATCH must succeed on an assets-only script').toBe( - true - ); - }); - - test('secrets set, list, survive a redeploy, and DELETE for real', async ({ request }) => { - const set = await request.post(hostingSecretsPath(rootId, appName), { - data: { name: 'REAL_SECRET', value: `real-${runId}` } - }); - expect(set.status(), await set.text()).toBe(200); - - const listed = await request.get(hostingSecretsPath(rootId, appName)); - expect( - ((await listed.json()) as { secrets: { name: string }[] }).secrets.map((s) => s.name) - ).toContain('REAL_SECRET'); - - // keep_bindings holds the secret across a redeploy. - const redeploy = await request.post(hostingDeployPath(rootId, appName), { - multipart: { - meta: JSON.stringify({}), - 'asset:/index.html': { - name: 'index.html', - mimeType: 'text/html', - buffer: Buffer.from(`

redeploy ${runId}

`) - } - } - }); - expect(redeploy.status(), await redeploy.text()).toBe(201); - const after = await request.get(hostingSecretsPath(rootId, appName)); - expect( - ((await after.json()) as { secrets: { name: string }[] }).secrets.map((s) => s.name) - ).toContain('REAL_SECRET'); - - // RISK GATE: THE live verification of the WfP per-script secrets DELETE - // endpoint - the console's delete button is not "done" until this passes. - const deleted = await request.delete(hostingSecretPath(rootId, appName, 'REAL_SECRET')); - expect(deleted.status(), await deleted.text()).toBe(200); - const gone = await request.get(hostingSecretsPath(rootId, appName)); - expect( - ((await gone.json()) as { secrets: { name: string }[] }).secrets.map((s) => s.name) - ).not.toContain('REAL_SECRET'); - }); -}); diff --git a/e2e/hosting.api.spec.ts b/e2e/hosting.api.spec.ts deleted file mode 100644 index 425e086a..00000000 --- a/e2e/hosting.api.spec.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { - hostingAppAnalyticsPath, - hostingAppPath, - hostingClaimsPath, - hostingDeployPath, - hostingDeploysPath, - hostingOverviewPath, - hostingTokensPath, - projectBranchesPath, - registryProjectPath -} from './helpers'; - -/** - * The stubbed CLI -> console -> agent hosting contract (Phase B): subdomain - * claims resolve in the control - * plane with auto-numbering, deploys are multipart uploads the console - * forwards after pushing the claim, and HOSTING_STUB records everything - * without a dispatch namespace. Real upload coverage is the opt-in - * hosting-real spec; this file is what pins the shape for every run. - */ - -// Fresh ids per run: claims and registry rows are permanent on locally -// reused stacks. Short enough that `--stg` fits the 48-char ceiling. -const runId = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.slice(0, 12); -const rootId = `e2e-host-${runId}`; -const neighborId = `e2e-hostb-${runId}`; -const appName = `app-${runId}`; - -const INDEX_HTML = { - name: 'index.html', - mimeType: 'text/html', - buffer: Buffer.from('

hello from e2e

') -}; - -function deployBody(extra: Record = {}) { - return { - multipart: { - meta: JSON.stringify(extra), - 'asset:/index.html': INDEX_HTML - } - }; -} - -test.describe('hosting deploys (stubbed)', () => { - test.beforeAll(async ({ request }) => { - for (const id of [rootId, neighborId]) { - const created = await request.post('/api/registry/projects', { - data: { id, name: `Hosting e2e ${id}` } - }); - expect(created.status(), await created.text()).toBe(201); - } - }); - - test.afterAll(async ({ request }) => { - // Deleting the roots also deletes branches (child-first) and releases - // the subdomain claims - keeps reused stacks under the project cap. - for (const id of [rootId, neighborId]) { - await request.delete(registryProjectPath(id)); - } - }); - - test('a dry claim previews without claiming', async ({ request }) => { - const preview = await request.post(hostingClaimsPath(rootId), { - data: { app: appName, dry: true } - }); - expect(preview.status(), await preview.text()).toBe(200); - const body = (await preview.json()) as { subdomain: string; created: boolean }; - expect(body.subdomain).toBe(appName); - expect(body.created).toBe(false); - }); - - test('the first deploy claims the subdomain and records a stub deploy', async ({ request }) => { - const deploy = await request.post(hostingDeployPath(rootId, appName), deployBody()); - expect(deploy.status(), await deploy.text()).toBe(201); - const body = (await deploy.json()) as { - subdomain: string; - url: string | null; - deploy: { status: string; assetCount: number; hasWorker: boolean }; - }; - expect(body.subdomain).toBe(appName); - expect(body.url).toBe(`https://${appName}.cfbase.dev`); - expect(body.deploy.status).toBe('stub'); - expect(body.deploy.assetCount).toBe(1); - expect(body.deploy.hasWorker).toBe(false); - }); - - test('the overview lists the app and its deploy', async ({ request }) => { - const overview = await request.get(hostingOverviewPath(rootId)); - expect(overview.status(), await overview.text()).toBe(200); - const body = (await overview.json()) as { - apps: { name: string; subdomain: string }[]; - recentDeploys: unknown[]; - totalDeploys: number; - stub: boolean; - configured: boolean; - }; - expect(body.apps.map((app) => app.name)).toContain(appName); - expect(body.totalDeploys).toBeGreaterThanOrEqual(1); - expect(body.stub).toBe(true); - expect(body.configured).toBe(true); - }); - - test('deploy history pages over keyset cursors', async ({ request }) => { - // A second deploy so there are two pages at limit=1. - const again = await request.post(hostingDeployPath(rootId, appName), deployBody()); - expect(again.status(), await again.text()).toBe(201); - - const first = await request.get(`${hostingDeploysPath(rootId)}?limit=1`); - expect(first.status(), await first.text()).toBe(200); - const page1 = (await first.json()) as { - deploys: { id: string }[]; - total: number; - cursor: string | null; - }; - expect(page1.deploys).toHaveLength(1); - expect(page1.total).toBeGreaterThanOrEqual(2); - expect(page1.cursor).not.toBeNull(); - - const second = await request.get( - `${hostingDeploysPath(rootId)}?limit=1&cursor=${encodeURIComponent(page1.cursor!)}` - ); - expect(second.status(), await second.text()).toBe(200); - const page2 = (await second.json()) as { deploys: { id: string }[] }; - expect(page2.deploys).toHaveLength(1); - // Keyset, not offset: the pages never overlap. - expect(page2.deploys[0].id).not.toBe(page1.deploys[0].id); - }); - - test('a taken subdomain auto-numbers instead of failing', async ({ request }) => { - const claim = await request.post(hostingClaimsPath(neighborId), { - data: { app: appName } - }); - expect(claim.status(), await claim.text()).toBe(201); - const body = (await claim.json()) as { subdomain: string; created: boolean }; - expect(body.subdomain).toBe(`${appName}-2`); - - // The persisted row is reused verbatim afterwards - never re-derived. - const again = await request.post(hostingClaimsPath(neighborId), { - data: { app: appName } - }); - expect(again.status(), await again.text()).toBe(200); - expect(((await again.json()) as { subdomain: string }).subdomain).toBe(`${appName}-2`); - }); - - test('branch deploys serve at -', async ({ request }) => { - const branch = await request.post(projectBranchesPath(rootId), { - data: { branch: 'stg' } - }); - expect(branch.status(), await branch.text()).toBe(201); - - const deploy = await request.post(hostingDeployPath(`${rootId}--stg`, appName), deployBody()); - expect(deploy.status(), await deploy.text()).toBe(201); - const body = (await deploy.json()) as { subdomain: string }; - // Single dash - `main` never appears in a URL, and dispatch never - // parses this back apart; the claims table resolved it here. - expect(body.subdomain).toBe(`${appName}-stg`); - }); - - test('served requests land in the analytics tab (local mode)', async ({ - request, - playwright - }) => { - // The serve path is only dialable on the local stack. - test.skip(!!process.env.BASE_URL, 'direct agent access needs the local stack'); - - // One request through the wildcard host mints one data point; the test - // env has no read credentials, so it lands in the LOCAL_ANALYTICS D1 and - // the analytics endpoint reads it back as `status: 'local'`. - const agent = await playwright.request.newContext({ baseURL: 'http://localhost:8800' }); - try { - const served = await agent.get('/', { - headers: { 'x-cfbase-host': `${appName}.cfbase.dev` } - }); - expect(served.status(), await served.text()).toBe(200); - } finally { - await agent.dispose(); - } - - // The write rides waitUntil and the read has a 5s cache - poll past both. - await expect - .poll( - async () => { - const analytics = await request.get(hostingAppAnalyticsPath(rootId, appName)); - if (analytics.status() !== 200) return { status: analytics.status() }; - const body = (await analytics.json()) as { - totals: { requests: number }; - engine: { status: string }; - }; - return { status: 200, engine: body.engine.status, requests: body.totals.requests }; - }, - { timeout: 15_000 } - ) - .toMatchObject({ status: 200, engine: 'local', requests: expect.any(Number) }); - - const analytics = await request.get(hostingAppAnalyticsPath(rootId, appName)); - const body = (await analytics.json()) as { - appName: string; - subdomain: string; - totals: { requests: number }; - byDay: { day: string; requests: number }[]; - }; - expect(body.appName).toBe(appName); - expect(body.subdomain).toBe(appName); - expect(body.totals.requests).toBeGreaterThanOrEqual(1); - expect(body.byDay.length).toBeGreaterThanOrEqual(1); - }); - - test('demo projects get the 403 upsell, never a deploy', async ({ request }) => { - const deploy = await request.post( - hostingDeployPath('demo-abcdef123456', appName), - deployBody() - ); - expect(deploy.status(), await deploy.text()).toBe(403); - }); - - test('deleting an app erases it and frees the subdomain', async ({ request }) => { - // Self-contained app so the shared appName claims stay untouched for - // the release test below. - const delApp = `del-${runId}`; - const deployed = await request.post(hostingDeployPath(rootId, delApp), deployBody()); - expect(deployed.status(), await deployed.text()).toBe(201); - - const before = await request.get(hostingOverviewPath(rootId)); - expect( - ((await before.json()) as { apps: { name: string }[] }).apps.map((a) => a.name) - ).toContain(delApp); - - const deleted = await request.delete(hostingAppPath(rootId, delApp)); - expect(deleted.status(), await deleted.text()).toBe(200); - const body = (await deleted.json()) as { deleted: boolean; subdomain: string }; - expect(body.deleted).toBe(true); - expect(body.subdomain).toBe(delApp); - - // Gone from the agent - deploy history included. - const after = await request.get(hostingOverviewPath(rootId)); - expect( - ((await after.json()) as { apps: { name: string }[] }).apps.map((a) => a.name) - ).not.toContain(delApp); - - // The subdomain is free again: a neighbor's dry claim gets the base name. - const freed = await request.post(hostingClaimsPath(neighborId), { - data: { app: delApp, dry: true } - }); - expect(((await freed.json()) as { subdomain: string }).subdomain).toBe(delApp); - - // Deleting it again is a 404, not a 500 - the claim row is the gate. - const again = await request.delete(hostingAppPath(rootId, delApp)); - expect(again.status(), await again.text()).toBe(404); - }); - - test('a claim that never deployed can still be deleted', async ({ request }) => { - // Connecting a repository claims immediately; the agent only learns at - // first deploy. Deleting must work on that claim-only state - the exact - // shape that used to be stuck forever on the Hosting page. - const ghost = `ghost-${runId}`; - const claimed = await request.post(hostingClaimsPath(neighborId), { data: { app: ghost } }); - expect(claimed.status(), await claimed.text()).toBe(201); - - const deleted = await request.delete(hostingAppPath(neighborId, ghost)); - expect(deleted.status(), await deleted.text()).toBe(200); - - const freed = await request.post(hostingClaimsPath(neighborId), { - data: { app: ghost, dry: true } - }); - expect(((await freed.json()) as { subdomain: string }).subdomain).toBe(ghost); - }); - - test('the 11th app claim is refused - the cap is the squatting guard', async ({ request }) => { - const capProbe = `e2e-hostcap-${runId}`; - const created = await request.post('/api/registry/projects', { - data: { id: capProbe, name: 'cap probe' } - }); - expect(created.status(), await created.text()).toBe(201); - try { - for (let n = 1; n <= 10; n += 1) { - const claim = await request.post(hostingClaimsPath(capProbe), { - data: { app: `cap-${runId}-${n}` } - }); - expect(claim.status(), await claim.text()).toBe(201); - } - const eleventh = await request.post(hostingClaimsPath(capProbe), { - data: { app: `cap-${runId}-11` } - }); - expect(eleventh.status(), await eleventh.text()).toBe(409); - expect(((await eleventh.json()) as { error: string }).error).toContain('10'); - } finally { - // Cascades the ten claims with the row. - await request.delete(registryProjectPath(capProbe)); - } - }); - - test('deleting the project releases its claims', async ({ request }) => { - const releaseProbe = `e2e-hostc-${runId}`; - const created = await request.post('/api/registry/projects', { - data: { id: releaseProbe, name: 'release probe' } - }); - expect(created.status(), await created.text()).toBe(201); - try { - // Before the delete the base name belongs to rootId. - const taken = await request.post(hostingClaimsPath(releaseProbe), { - data: { app: appName, dry: true } - }); - expect(((await taken.json()) as { subdomain: string }).subdomain).not.toBe(appName); - - const deleted = await request.delete(registryProjectPath(rootId)); - expect([200, 207], await deleted.text()).toContain(deleted.status()); - - const freed = await request.post(hostingClaimsPath(releaseProbe), { - data: { app: appName, dry: true } - }); - expect(((await freed.json()) as { subdomain: string }).subdomain).toBe(appName); - } finally { - await request.delete(registryProjectPath(releaseProbe)); - } - }); -}); - -test.describe('stub serving', () => { - // The serve path is only dialable on the local stack: the agent worker has - // no public route on a deployed target. - test.skip(!!process.env.BASE_URL, 'direct agent access needs the local stack'); - - test('the wildcard host serves the stub page', async ({ playwright }) => { - const agent = await playwright.request.newContext({ baseURL: 'http://localhost:8800' }); - try { - const served = await agent.get('/', { - headers: { 'x-cfbase-host': 'anything-at-all.cfbase.dev' } - }); - expect(served.status(), await served.text()).toBe(200); - expect(await served.text()).toContain('data-cfbase-stub="anything-at-all"'); - } finally { - await agent.dispose(); - } - }); -}); - -test.describe('hosting guard', () => { - // LOAD-BEARING: without this override the context inherits the operator - // session and proves nothing. - test.use({ storageState: { cookies: [], origins: [] } }); - - test('every hosting surface requires an operator session', async ({ request }) => { - const closed = [ - ['GET', hostingOverviewPath(rootId)], - ['GET', hostingDeploysPath(rootId)], - ['POST', hostingClaimsPath(rootId)], - ['GET', hostingTokensPath(rootId)], - ['POST', hostingTokensPath(rootId)], - ['POST', hostingDeployPath(rootId, appName)], - ['GET', hostingAppAnalyticsPath(rootId, appName)], - // App deletion is session-only by design: a deploy token that can - // ship code must never be able to erase an app, and the guard's - // token surface admits POST deploys/branches alone. - ['DELETE', hostingAppPath(rootId, appName)] - ] as const; - for (const [method, path] of closed) { - const response = - method === 'GET' - ? await request.get(path) - : method === 'DELETE' - ? await request.delete(path) - : await request.post(path, { data: {} }); - expect(response.status(), `${method} ${path} must be closed`).toBe(401); - } - }); -}); diff --git a/e2e/hosting.ui.spec.ts b/e2e/hosting.ui.spec.ts deleted file mode 100644 index 424cf5c1..00000000 --- a/e2e/hosting.ui.spec.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { expect, test, type Page } from '@playwright/test'; -import { CONSOLE_STORAGE_STATE, hostingDeployPath, registryProjectPath } from './helpers'; - -/** - * Hosting pages (frontend): the hub's app list, the per-app page's URL tabs, - * and the variables-and-secrets editor - the first UI coverage hosting has. - * Uses a per-run project so reused local stacks never collide on permanent - * subdomain claims, and serial mode because the tests walk one app's life. - */ - -const runId = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.slice(0, 12); -const projectId = `e2e-hostui-${runId}`; -const appName = `ui-${runId}`; - -async function gotoHosting(page: Page) { - await page.goto(`/dashboard/${projectId}/hosting`); - await expect(page.getByTestId('hosting-page')).toHaveAttribute('data-hydrated', 'true'); -} - -async function gotoApp(page: Page, tab = '') { - await page.goto(`/dashboard/${projectId}/hosting/apps/${appName}${tab ? `/${tab}` : ''}`); - await expect(page.getByTestId('hosting-app-page')).toHaveAttribute('data-hydrated', 'true'); -} - -test.describe('hosting pages (frontend)', () => { - test.describe.configure({ mode: 'serial' }); - - test.beforeAll(async ({ playwright, baseURL }) => { - // The chromium project's request fixture sends no Origin header, and the - // multipart deploy would trip the CSRF check without one. - const api = await playwright.request.newContext({ - baseURL, - extraHTTPHeaders: { origin: baseURL ?? '' }, - storageState: CONSOLE_STORAGE_STATE - }); - try { - const created = await api.post('/api/registry/projects', { - data: { id: projectId, name: `Hosting UI e2e ${runId}` } - }); - expect(created.status(), await created.text()).toBe(201); - // A deployed app: the detail page's secrets editor needs one (secrets - // attach to the deployed script). - const deployed = await api.post(hostingDeployPath(projectId, appName), { - multipart: { - meta: JSON.stringify({}), - 'asset:/index.html': { - name: 'index.html', - mimeType: 'text/html', - buffer: Buffer.from('

ui spec

') - } - } - }); - expect(deployed.status(), await deployed.text()).toBe(201); - } finally { - await api.dispose(); - } - }); - - test.afterAll(async ({ request }) => { - await request.delete(registryProjectPath(projectId)); - }); - - test('the hub lists the app and opens its page', async ({ page }) => { - await gotoHosting(page); - await page.getByTestId(`open-app-${appName}`).click(); - await expect(page.getByTestId('hosting-app-page')).toHaveAttribute('data-hydrated', 'true'); - await expect(page).toHaveURL(new RegExp(`/hosting/apps/${appName}$`)); - await expect(page.getByTestId('app-stat-deploys').getByTestId('stat-value')).toHaveText('1'); - }); - - test('tabs are URL-addressed sibling routes', async ({ page }) => { - await gotoApp(page); - await page.getByTestId('tab-app-deployments').click(); - await expect(page).toHaveURL(new RegExp(`/hosting/apps/${appName}/deployments$`)); - await expect(page.getByTestId('hosting-deploys-range')).toHaveText('1–1 of 1'); - - await page.getByTestId('tab-app-analytics').click(); - await expect(page).toHaveURL(new RegExp(`/hosting/apps/${appName}/analytics$`)); - // The test stack reads the local D1 stand-in: the chart renders (zeroed - // is fine), never the write-only upsell. - await expect(page.getByTestId('hosting-analytics-chart')).toBeVisible(); - - await page.getByTestId('tab-app-settings').click(); - await expect(page).toHaveURL(new RegExp(`/hosting/apps/${appName}/settings$`)); - await expect(page.getByTestId('hosting-vars-card')).toBeVisible(); - }); - - test('runtime vars round-trip through the editor', async ({ page }) => { - await gotoApp(page, 'settings'); - - await page.getByTestId('hosting-vars-add').click(); - await page.getByTestId('hosting-vars-name-0').fill('API_BASE'); - await page.getByTestId('hosting-vars-value-0').fill('https://api.example'); - await page.getByTestId('hosting-vars-save').click(); - await expect(page.getByTestId('hosting-vars-feedback')).toHaveText('Saved.'); - - // Server truth, not optimistic state: a reload renders the stored row. - await gotoApp(page, 'settings'); - await expect(page.getByTestId('hosting-vars-name-0')).toHaveValue('API_BASE'); - await expect(page.getByTestId('hosting-vars-value-0')).toHaveValue('https://api.example'); - }); - - test('secrets are write-only rows', async ({ page }) => { - await gotoApp(page, 'settings'); - - // Row 0 is the stored var; add row 1 and flip it to Secret. - await page.getByTestId('hosting-vars-add').click(); - await page.getByTestId('hosting-vars-type-1').click(); - await page.getByRole('option', { name: 'Secret' }).click(); - await page.getByTestId('hosting-vars-name-1').fill('API_KEY'); - await page.getByTestId('hosting-vars-value-1').fill('hunter2'); - await page.getByTestId('hosting-vars-save').click(); - await expect(page.getByTestId('hosting-vars-feedback')).toHaveText('Saved.'); - - // Reloaded, the secret is a write-only row: named, but the value is - // gone for good - only the placeholder admits it exists. - await gotoApp(page, 'settings'); - await expect(page.getByTestId('hosting-vars-name-1')).toHaveValue('API_KEY'); - await expect(page.getByTestId('hosting-vars-value-1')).toHaveValue(''); - await expect(page.getByTestId('hosting-vars-value-1')).toHaveAttribute( - 'placeholder', - /Value encrypted/ - ); - - // Removing the row deletes the secret on Save. - await page.getByTestId('hosting-vars-remove-1').click(); - await page.getByTestId('hosting-vars-save').click(); - await expect(page.getByTestId('hosting-vars-feedback')).toHaveText('Saved.'); - await gotoApp(page, 'settings'); - await expect(page.getByTestId('hosting-vars-name-1')).toHaveCount(0); - }); - - test('the danger zone deletes the app with a typed confirm', async ({ page }) => { - await gotoApp(page, 'settings'); - await page.getByTestId(`delete-app-${appName}`).click(); - - const submit = page.getByTestId('confirm-delete-app'); - await expect(submit).toBeDisabled(); - await page.getByTestId('delete-app-confirm').fill(appName); - await submit.click(); - - // Deleting navigates back to the hub, where the app is gone. - await expect(page.getByTestId('hosting-page')).toHaveAttribute('data-hydrated', 'true'); - await expect(page.getByTestId(`open-app-${appName}`)).toHaveCount(0); - }); -}); diff --git a/e2e/landing.ui.spec.ts b/e2e/landing.ui.spec.ts index efddf5cc..c39d4798 100644 --- a/e2e/landing.ui.spec.ts +++ b/e2e/landing.ui.spec.ts @@ -19,7 +19,7 @@ test.describe('landing page (frontend)', () => { // card is a signup someone regrets. Every primitive listed is live. const primitives = page.locator('#roadmap'); await expect(primitives.getByText('Planned')).toHaveCount(0); - await expect(primitives.getByText('Live')).toHaveCount(5); + await expect(primitives.getByText('Live')).toHaveCount(4); await expect(primitives).toContainText('Storage'); // The hero visual opens on the db agent and the tabs are real controls. diff --git a/e2e/service-keys.api.spec.ts b/e2e/service-keys.api.spec.ts index 3fdf2971..68a36508 100644 --- a/e2e/service-keys.api.spec.ts +++ b/e2e/service-keys.api.spec.ts @@ -23,7 +23,7 @@ import { * 2. it must not reach another project - a sibling branch included, because * for data the branch IS the isolation boundary; * 3. it must not escalate: no registry (so it cannot delete its own project), - * no console, no CLI, no hosting, and no minting or revoking keys; + * no console, no CLI, and no minting or revoking keys; * 4. revocation must bite immediately. * * The suite's stored operator session is what mints keys, so the key-using @@ -570,7 +570,6 @@ test.describe('service keys', () => { ['GET', `/api/registry/projects`], ['DELETE', `/api/registry/projects/${KEY_PROJECT}`], ['POST', `/api/projects/${KEY_PROJECT}/branches`], - ['GET', `/api/projects/${KEY_PROJECT}/hosting/apps`], ['POST', `/api/cli/token`] ]; for (const [method, path] of escalations) { diff --git a/eslint.config.js b/eslint.config.js index 3467ddac..9f77d4ec 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -37,7 +37,6 @@ export default defineConfig( '**/worker-configuration.d.ts', 'agents/auth/dist/', 'agents/db/dist/', - 'agents/hosting/dist/', 'agents/storage/dist/', 'cli/dist/' ] diff --git a/package.json b/package.json index 7700de44..27cdbed1 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,11 @@ "lint": "prettier --check --cache . && eslint --cache --cache-location node_modules/.cache/eslint/ .", "format": "prettier --write --cache .", "deploy": "npm run build && wrangler deploy", - "deploy:all": "npm --prefix agents/auth run deploy && npm --prefix agents/db run deploy && npm --prefix agents/storage run deploy && npm --prefix agents/hosting run deploy && npm run deploy", + "deploy:all": "npm --prefix agents/auth run deploy && npm --prefix agents/db run deploy && npm --prefix agents/storage run deploy && npm run deploy", "cf-typegen": "wrangler types ./src/worker-configuration.d.ts", "postinstall": "cross-env POSTINSTALL=true node scripts/dev.js", "test": "playwright test", - "test:unit": "node --import tsx --test src/lib/server/frameworks.unit.test.ts src/lib/server/crawler.unit.test.ts src/lib/hosting-workflow.unit.test.ts src/lib/branch-match.unit.test.ts src/lib/stale-build.unit.test.ts", + "test:unit": "node --import tsx --test src/lib/server/crawler.unit.test.ts src/lib/stale-build.unit.test.ts", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", "deploy:production": "npm run build && wrangler deploy --env production" diff --git a/playwright.config.ts b/playwright.config.ts index 423d39dd..acc325d1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -89,13 +89,6 @@ export default defineConfig({ reuseExistingServer: !process.env.CI, timeout: 120_000 }, - { - command: - 'node scripts/kill-port.mjs 8800 && node scripts/clean-dir.mjs .wrangler/test-state/hosting-agent && npm run dev:test --prefix agents/hosting', - url: 'http://localhost:8800/health', - reuseExistingServer: !process.env.CI, - timeout: 120_000 - }, { command: 'node scripts/kill-port.mjs 8801 && node scripts/clean-dir.mjs .wrangler/test-state/storage-agent && npm run dev:test --prefix agents/storage', diff --git a/scripts/local-dev.json b/scripts/local-dev.json index 551d49a9..4a55de1e 100644 --- a/scripts/local-dev.json +++ b/scripts/local-dev.json @@ -11,17 +11,13 @@ "name": "db-agent", "command": "npm run dev --prefix ./agents/db" }, - { - "name": "hosting-agent", - "command": "npm run dev --prefix ./agents/hosting" - }, { "name": "storage-agent", "command": "npm run dev --prefix ./agents/storage" }, { "name": "web", - "command": "wait-on tcp:localhost:8788 tcp:localhost:8789 tcp:localhost:8790 tcp:localhost:8791 && vite dev --port 5173 --open --host" + "command": "wait-on tcp:localhost:8788 tcp:localhost:8789 tcp:localhost:8791 && vite dev --port 5173 --open --host" } ] } diff --git a/scripts/postinstall.json b/scripts/postinstall.json index 52d33c70..82816a90 100644 --- a/scripts/postinstall.json +++ b/scripts/postinstall.json @@ -11,10 +11,6 @@ "name": "db-agent", "command": "cd ./agents/db && npm install" }, - { - "name": "hosting-agent", - "command": "cd ./agents/hosting && npm install" - }, { "name": "storage-agent", "command": "cd ./agents/storage && npm install" diff --git a/src/app.d.ts b/src/app.d.ts index 89ed2ac8..58767851 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -17,18 +17,6 @@ declare global { consoleIdentity: import('$lib/console').ConsoleIdentity | null; /** Whether this deployment runs as a public demo (DEMO_MODE=true). */ demoMode: boolean; - /** - * Deploy-token grant when the request authenticated with a `cfbd_` - * bearer (Phase B). Only ever set on - * the deploy and branch-create endpoints; never a session. - */ - deployToken: import('$lib/server/hosting').DeployTokenGrant | null; - /** - * Grant when the request authenticated with a GitHub Actions OIDC - * token from a `build`-mode connection - the credential-less CI path. - * Set on exactly the same surfaces as `deployToken`, never a session. - */ - githubDeploy: import('$lib/server/github-connect').GithubDeployGrant | null; /** * Grant when the request authenticated with a `cfbs_` service key *. Set only on its own project's DATA @@ -44,8 +32,6 @@ declare global { AUTH_AGENT: Fetcher; /** Service binding to the db-agent worker (fetch-only interface). */ DB_AGENT: Fetcher; - /** Service binding to the hosting-agent worker (fetch-only interface). */ - HOSTING_AGENT: Fetcher; /** * Optional per-tenant ceiling overrides (registry.ts defaults both * to 5). Not in any deployed config's vars, so they are typed here @@ -54,19 +40,6 @@ declare global { */ MAX_PROJECTS_PER_ORG?: string; MAX_BRANCHES_PER_ROOT?: string; - /** - * GitHub App credentials for push-to-deploy (server/github.ts). - * Optional secrets - typed here rather than in the generated - * worker-configuration.d.ts because no deployed config declares - * them. All four together or the App reads as unconfigured, which - * is the self-hosted default: the Hosting page then offers the - * manual deploy-token flow and nothing GitHub-side is reachable. - * The private key may be either PEM encoding GitHub hands out. - */ - GITHUB_APP_ID?: string; - GITHUB_APP_SLUG?: string; - GITHUB_APP_PRIVATE_KEY?: string; - GITHUB_APP_WEBHOOK_SECRET?: string; }; cf: CfProperties; ctx: ExecutionContext; diff --git a/src/hooks.server.ts b/src/hooks.server.ts index abbe0f8f..bd775d02 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -10,13 +10,6 @@ import { projectIdSchema } from '$lib/schemas/auth'; import { agentFetcher, agentUrl, serverError } from '$lib/server/agents'; import { isDemoMode, isDemoProjectId, resolveConsoleIdentity } from '$lib/server/console'; import { guardConsoleClaim } from '$lib/server/console-setup'; -import { verifyGithubDeployGrant } from '$lib/server/github-connect'; -import { - deployTokenCoversProject, - isBuildEnvSurface, - isDeployTokenSurface, - verifyDeployToken -} from '$lib/server/hosting'; import { isServiceKeySurface, verifyServiceKey } from '$lib/server/service-keys'; import { getProjectOwnership, projectExists, type ProjectOwnership } from '$lib/server/registry'; import { handleErrorWithSentry, initCloudflareSentryHandle, sentryHandle } from '@sentry/sveltekit'; @@ -254,30 +247,10 @@ function classifyAccess(pathname: string): Access { // Everything under /api is operator surface unless published below, so a // route added later is private until someone deliberately opens it. if (segments[0] === 'api') { - // The two GitHub routes the guard cannot gate, both authenticated by an - // HMAC we control rather than by a session: - // - // - `webhook`: GitHub carries no session and never will. Its - // X-Hub-Signature-256 is the credential, checked over the raw body. - // - `callback`: the return leg of an App install. It arrives as a - // cross-site top-level navigation from github.com, where a session - // cookie is not reliably present - requiring one stranded operators - // mid-install. The signed install state IS the credential here: it is - // minted only for a signed-in operator on a specific project, expires - // in minutes, and cannot be forged without the webhook secret. The - // route verifies it before writing anything, and still cross-checks a - // session when the browser does send one. - if ( - segments[1] === 'github' && - segments.length === 3 && - (segments[2] === 'webhook' || segments[2] === 'callback') - ) { - return { scope: 'open' }; - } - // The first-run setup unlock. Public for the same reason as the two - // above: an unclaimed console has no session to authenticate against, - // and CONSOLE_SETUP_TOKEN - writable only with Cloudflare account - // credentials - is the credential the route checks. + // The first-run setup unlock. Public because an unclaimed console has + // no session to authenticate against, and CONSOLE_SETUP_TOKEN - writable + // only with Cloudflare account credentials - is the credential the + // route checks. if (segments[1] === 'console' && segments[2] === 'setup' && segments.length === 3) { return { scope: 'open' }; } @@ -398,8 +371,6 @@ const consoleGuardHandle: Handle = async ({ event, resolve }) => { event.locals.demoMode = isDemoMode(event.platform); event.locals.consoleUser = null; event.locals.consoleIdentity = null; - event.locals.deployToken = null; - event.locals.githubDeploy = null; event.locals.serviceKey = null; const access = classifyAccess(event.url.pathname); @@ -434,33 +405,6 @@ const consoleGuardHandle: Handle = async ({ event, resolve }) => { return noSuchProject(access.kind); } - // Deploy tokens (Phase B): a `cfbd_` bearer - // is CI's durable credential, accepted SOLELY on the deploy and - // branch-create endpoints for the token's root project and its branches. - // Any other use of one - wrong surface, wrong project, revoked - is a - // plain 401 here, never a fall-through to session resolution: a deploy - // token must never behave like a session. - const bearer = event.request.headers - .get('authorization') - ?.match(/^Bearer\s+(cfbd_[0-9a-f]{64})$/i)?.[1]; - if (bearer) { - if ( - access.projectId && - isDeployTokenSurface(event.url.pathname, event.request.method) && - !isDemoProjectId(access.projectId) - ) { - const grant = await verifyDeployToken(event.platform, bearer.toLowerCase()); - if ( - grant && - (await deployTokenCoversProject(event.platform, grant.projectId, access.projectId)) - ) { - event.locals.deployToken = grant; - return resolve(event); - } - } - return Response.json({ error: 'invalid deploy token' }, { status: 401 }); - } - // Service keys (SK1): a `cfbs_` bearer is the // credential a SERVER holds for the cases with no user to relay - crons, // queue consumers, webhook handlers, seed scripts. It reaches the DATA @@ -476,8 +420,8 @@ const consoleGuardHandle: Handle = async ({ event, resolve }) => { // A REQUEST CARRYING AN `Origin` IS REFUSED, whatever the key. Server // fetches send no Origin and browsers always do, so a key pasted into // frontend code fails immediately at the developer's desk instead of - // shipping inside a JS bundle on a CDN. Same all-or-nothing contract as a - // deploy token: never a fall-through to session resolution. + // shipping inside a JS bundle on a CDN. All or nothing: a key bearer never + // falls through to session resolution. const serviceBearer = event.request.headers .get('authorization') ?.match(/^Bearer\s+(cfbs_[0-9a-f]{64})$/i)?.[1]; @@ -515,40 +459,6 @@ const consoleGuardHandle: Handle = async ({ event, resolve }) => { return Response.json({ error: 'invalid service key' }, { status: 401 }); } - // GitHub Actions OIDC (Phase B): a - // `build`-mode connection deploys with NO stored credential at all - the - // workflow presents a short-lived token GitHub signed, describing the - // repository it ran in, and the connection table says which project that - // repository may deploy to. Same surfaces and same all-or-nothing contract - // as a deploy token: never a fall-through to session resolution. The - // build-env GET is the one read the bearer also opens - the workflow - // fetches its build vars and secrets before the build step - and it is - // deliberately NOT a deploy-token surface (see isBuildEnvSurface). - // - // Only attempted on those surfaces, so a three-segment console session - // bearer on any other route still reaches the session path below. - const oidcBearer = - access.projectId && - (isDeployTokenSurface(event.url.pathname, event.request.method) || - isBuildEnvSurface(event.url.pathname, event.request.method)) - ? event.request.headers - .get('authorization') - ?.match(/^Bearer\s+([\w-]+\.[\w-]+\.[\w-]+)$/)?.[1] - : undefined; - if (oidcBearer) { - const grant = await verifyGithubDeployGrant( - event.platform, - oidcBearer, - event.url.origin, - access.projectId! - ); - if (grant) { - event.locals.githubDeploy = grant; - return resolve(event); - } - return Response.json({ error: 'invalid GitHub deploy token' }, { status: 401 }); - } - // Public demo: anonymous visitors may drive ephemeral demo projects, whose // ids are unguessable and whose data self-destructs after the TTL. Demos // are throwaway - never registry rows, never owned - so the visit skips @@ -701,8 +611,7 @@ const cloudflareSentryHandle: Handle = async (input) => { * requests keep the full check. * * The skip is BEARER-shaped only, not any Authorization header: every - * credential this app accepts is a bearer (session token, `cfbd_`, `cfbs_`, - * OIDC), while `Basic` is one a browser CAN attach by itself (a + * credential this app accepts is a bearer (session token or `cfbs_`), while `Basic` is one a browser CAN attach by itself (a * `user:pass@host` top-level navigation), riding beside the victim's cookies * on a request the guard would then authenticate from those cookies. A * non-bearer Authorization therefore keeps the full check. diff --git a/src/lib/agent-registry.ts b/src/lib/agent-registry.ts index 7518c3aa..2521e4d2 100644 --- a/src/lib/agent-registry.ts +++ b/src/lib/agent-registry.ts @@ -1,7 +1,6 @@ import { z } from 'zod'; import authManifestJson from '../../agents/auth/cloudflarebase.agent.json'; import dbManifestJson from '../../agents/db/cloudflarebase.agent.json'; -import hostingManifestJson from '../../agents/hosting/cloudflarebase.agent.json'; import storageManifestJson from '../../agents/storage/cloudflarebase.agent.json'; /** @@ -113,7 +112,7 @@ export type RouteAccess = z.infer; */ export interface AppAgentEntry { manifest: AgentManifest; - binding: 'AUTH_AGENT' | 'DB_AGENT' | 'HOSTING_AGENT' | 'STORAGE_AGENT'; + binding: 'AUTH_AGENT' | 'DB_AGENT' | 'STORAGE_AGENT'; devHost: string; } @@ -130,11 +129,6 @@ export const AGENT_REGISTRY: Record = { binding: 'DB_AGENT', devHost: 'localhost:8789' }, - hosting: { - manifest: agentManifestSchema.parse(hostingManifestJson), - binding: 'HOSTING_AGENT', - devHost: 'localhost:8790' - }, storage: { manifest: agentManifestSchema.parse(storageManifestJson), binding: 'STORAGE_AGENT', diff --git a/src/lib/agents.ts b/src/lib/agents.ts index 1c5d4041..524d4718 100644 --- a/src/lib/agents.ts +++ b/src/lib/agents.ts @@ -737,317 +737,6 @@ export const dbServerFrameSchema = z 'Server frames on the live-query WebSocket: an initial snapshot in query order, then added/modified/removed deltas as writes happen.' }); -// --- Hosting agent (mirrors agents/hosting/src/agent.ts) --- - -export const hostingAppSchema = z - .object({ - name: z.string().describe('The operator-chosen app name.'), - subdomain: z - .string() - .describe('What was ACTUALLY claimed - auto-numbered when the wanted name was taken.'), - url: z.string().nullable().describe('Live URL; null while no serving domain is configured.'), - deployCount: z.number().int(), - lastDeployAt: z.iso.datetime().nullable(), - createdAt: z.iso.datetime() - }) - .meta({ - id: 'HostingApp', - description: - 'One deployed app: a user Worker (assets and/or modules) in the dispatch namespace.' - }); - -export const hostingDeploySchema = z - .object({ - id: z.string(), - appName: z.string(), - subdomain: z.string(), - url: z.string().nullable(), - status: z.enum(['live', 'stub']), - hasWorker: z.boolean(), - assetCount: z.number().int(), - assetBytes: z.number().int(), - moduleBytes: z.number().int(), - createdAt: z.iso.datetime() - }) - .meta({ - id: 'HostingDeploy', - description: 'One deploy. `stub` means recorded without a dispatch namespace (local/e2e).' - }); - -export const hostingOverviewSchema = z - .object({ - projectId: z.string(), - provisionedAt: z.iso.datetime().nullable(), - apps: z.array(hostingAppSchema), - recentDeploys: z.array(hostingDeploySchema), - totalDeploys: z.number().int(), - configured: z.boolean().describe('Whether deploys can complete on this install.'), - stub: z.boolean() - }) - .meta({ id: 'HostingOverview' }); - -export const hostingDeployPageSchema = z - .object({ - deploys: z.array(hostingDeploySchema), - total: z.number().int(), - cursor: z.string().nullable().describe('Keyset cursor for the next page; null on the last one.') - }) - .meta({ id: 'HostingDeployPage' }); - -export const hostingClaimSchema = z - .object({ - subdomain: z.string(), - appName: z.string(), - created: z.boolean().describe('False when an existing claim was reused (or on a dry run).') - }) - .meta({ - id: 'HostingClaim', - description: - 'A resolved subdomain claim. Taken names auto-number (`name-2`, `name-3`, ...) - the resolved subdomain is persisted on first claim and reused verbatim afterwards.' - }); - -export const hostingClaimRequestSchema = z - .object({ - app: z.string().describe('The wanted app name (subdomain charset, 3-48 chars).'), - dry: z.boolean().optional().describe('Only report what would be claimed.') - }) - .meta({ id: 'HostingClaimRequest' }); - -export const deployTokenSchema = z - .object({ - id: z.string(), - name: z.string(), - createdAt: z.iso.datetime(), - lastUsedAt: z.iso.datetime().nullable() - }) - .meta({ - id: 'DeployToken', - description: - 'Deploy-token metadata. The secret is shown once at mint and stored only as a SHA-256 digest.' - }); - -export const mintDeployTokenSchema = z - .object({ name: z.string().describe('A label, e.g. the repository this token deploys from.') }) - .meta({ id: 'MintDeployTokenRequest' }); - -export const githubConnectionSchema = z - .object({ - id: z.string(), - projectId: z.string().describe('The ROOT project; branches ride the same connection.'), - appName: z.string(), - installationId: z.number().int(), - repoId: z.number().int(), - repoFullName: z.string(), - defaultBranch: z.string(), - mode: z - .enum(['build', 'direct']) - .describe( - '`build` adds a workflow and trusts the Actions OIDC token; `direct` deploys the pushed tree from the webhook, with no runner and no file in the repository.' - ), - assetsDir: z - .string() - .nullable() - .describe( - 'Direct mode: repo-relative directory published as assets. Build mode: where the build lands.' - ), - buildCommand: z - .string() - .nullable() - .describe('Build mode: the workflow build command; null = the generic default.'), - rootDir: z - .string() - .nullable() - .describe('Build mode: monorepo root the build runs in; null = repository root.'), - productionBranch: z - .string() - .nullable() - .describe("Branch that deploys the root project; null = the repository's default branch."), - ignoredBranches: z - .array(z.string()) - .describe('Branch names and `*` globs whose pushes never deploy.'), - packageManager: z.string().nullable(), - createdAt: z.iso.datetime(), - lastEventAt: z.iso.datetime().nullable() - }) - .meta({ - id: 'GithubConnection', - description: - 'A repository connected to one project+app. Made on a ROOT project; a push to the production branch deploys the root and any other branch deploys `--`.' - }); - -export const githubConnectionPatchSchema = z - .object({ - buildCommand: z.string().nullable().optional().describe('Null resets to the generic default.'), - rootDir: z.string().nullable().optional(), - assetsDir: z.string().nullable().optional(), - productionBranch: z - .string() - .nullable() - .optional() - .describe("Null resets to the repository's default branch."), - ignoredBranches: z - .array(z.string()) - .max(20) - .optional() - .describe('Branch names or simple `*` globs (e.g. `renovate/*`).') - }) - .meta({ - id: 'GithubConnectionPatch', - description: - 'Editable build settings. Absent fields keep their value; null resets to the default. On a build-mode connection, saving rewrites the workflow file in the repository.' - }); - -export const mintedDeployTokenSchema = z - .object({ - id: z.string(), - name: z.string(), - token: z.string().describe('The `cfbd_...` secret - shown exactly once.'), - createdAt: z.iso.datetime() - }) - .meta({ id: 'MintedDeployToken' }); - -export const githubConnectRequestSchema = z - .object({ - installationId: z.number().int().positive(), - repoFullName: z.string().describe('`owner/repository`.'), - appName: z.string(), - mode: z.enum(['build', 'direct']), - assetsDir: z - .string() - .optional() - .describe('Direct: the published directory. Build: where the build lands.'), - buildCommand: z.string().optional(), - rootDir: z.string().optional().describe('Monorepo root (build mode).'), - packageManager: z.enum(['npm', 'pnpm', 'yarn', 'bun']).optional(), - wranglerTemplate: z - .string() - .optional() - .describe('A known wrangler.jsonc template id to commit for a repo that has none.') - }) - .meta({ id: 'GithubConnectRequest' }); - -export const hostingVarSchema = z - .object({ - name: z.string(), - value: z.string(), - createdAt: z.iso.datetime(), - updatedAt: z.iso.datetime() - }) - .meta({ - id: 'HostingVar', - description: - 'One stored runtime variable: applied as a plain_text binding on every deploy, and patched onto the live script when edited.' - }); - -export const hostingVarListSchema = z - .object({ vars: z.array(hostingVarSchema) }) - .meta({ id: 'HostingVarList' }); - -export const hostingVarsUpdateSchema = z - .object({ - vars: z - .record(z.string(), z.string()) - .describe( - 'The FULL set (UPPER_SNAKE names, single-line values <=5000 chars) - absent names are deleted.' - ) - }) - .meta({ id: 'HostingVarsUpdate' }); - -export const hostingVarsUpdatedSchema = z - .object({ - vars: z.array(hostingVarSchema), - patched: z - .boolean() - .describe( - 'Whether the live script was updated in place; false means the change applies at the next deploy.' - ), - warning: z.string().optional() - }) - .meta({ id: 'HostingVarsUpdated' }); - -export const hostingSecretMetaSchema = z - .object({ - name: z.string(), - createdAt: z.iso.datetime(), - updatedAt: z.iso.datetime() - }) - .meta({ - id: 'HostingSecretMeta', - description: - 'Secret name and timestamps. Values are write-through to Cloudflare and unrecoverable by design.' - }); - -export const hostingSecretListSchema = z - .object({ secrets: z.array(hostingSecretMetaSchema) }) - .meta({ id: 'HostingSecretList' }); - -export const hostingSecretRequestSchema = z - .object({ - name: z.string().describe('UPPER_SNAKE_CASE.'), - value: z.string().describe('1-5000 characters. Stored by Cloudflare, never by Cloudflarebase.') - }) - .meta({ id: 'HostingSecretRequest' }); - -export const hostingBuildEnvSchema = z - .object({ - vars: z.array(hostingVarSchema), - secrets: z.array(hostingSecretMetaSchema), - encryptionConfigured: z - .boolean() - .describe('Whether this install can store build secrets (HOSTING_MASTER_KEY is set).') - }) - .meta({ - id: 'HostingBuildEnv', - description: - "The operator view of an app's build-time environment: vars with values, secret names only - decrypted values never cross the operator surface." - }); - -export const hostingBuildEnvBundleSchema = z - .object({ - vars: z.record(z.string(), z.string()), - secrets: z.record(z.string(), z.string()) - }) - .meta({ - id: 'HostingBuildEnvBundle', - description: - 'The decrypted bundle a GitHub Actions runner exports before its build step. Served only to a verified OIDC bearer of the connection that owns the app.' - }); - -export const hostingBuildSecretRequestSchema = z - .object({ - value: z - .string() - .describe("1-5000 characters, single-line. Encrypted at rest under the install's master key.") - }) - .meta({ id: 'HostingBuildSecretRequest' }); - -export const hostingAnalyticsSchema = z - .object({ - appName: z.string(), - subdomain: z.string(), - days: z.number().int().describe('The window that was queried: 7, 30, or 90.'), - totals: z.object({ - requests: z.number(), - errors: z.number().describe('Responses with a 5xx status.'), - avgDurationMs: z.number() - }), - byDay: z.array(z.object({ day: z.string(), requests: z.number(), errors: z.number() })), - engine: z.object({ - dataset: z.string(), - enabled: z.boolean(), - status: z - .enum(['connected', 'local', 'write-only', 'error']) - .describe( - 'connected = Analytics Engine reads work; local = the dev stand-in; write-only = events are recorded but reads need CF_ANALYTICS_API_TOKEN; error = the query failed (see `error`).' - ), - error: z.string().optional() - }) - }) - .meta({ - id: 'HostingAnalytics', - description: 'Per-app request analytics: daily requests/errors and totals.' - }); - // --- Storage agent (mirrors agents/storage/src/{agent,bucket,schemas}.ts) --- /** `none` closes a side to the public API entirely - operator surfaces only. @@ -1210,17 +899,6 @@ export type DbRestorePoints = z.infer; export type DbReplicationMode = z.infer; export type DbReplica = z.infer; export type DbReplicationStatus = z.infer; -export type HostingApp = z.infer; -export type HostingDeploy = z.infer; -export type HostingOverview = z.infer; -export type HostingDeployPage = z.infer; -export type HostingClaim = z.infer; -export type DeployTokenInfo = z.infer; -export type GithubConnectionInfo = z.infer; -export type MintedDeployToken = z.infer; -export type HostingVar = z.infer; -export type HostingSecretMeta = z.infer; -export type HostingAnalytics = z.infer; export type StorageAccessMode = z.infer; export type StorageBucketSummary = z.infer; export type StorageBucketInfo = z.infer; diff --git a/src/lib/branch-match.ts b/src/lib/branch-match.ts deleted file mode 100644 index 528376dd..00000000 --- a/src/lib/branch-match.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { z } from 'zod'; - -/** - * Branch filters for a GitHub connection: which pushes never deploy, and - * which branch is production. Pure on purpose (no server imports) - the - * webhook handler, the OIDC grant verification, and the connection PATCH all - * import it, and `node:test` runs it directly. - */ - -/** A git branch name we accept as production. Charset-limited because these - * values are embedded into workflow YAML - quoting is a bug class, refusing - * is not (the assetsDirSchema precedent). */ -export const gitBranchSchema = z - .string() - .min(1) - .max(120) - .regex(/^[A-Za-z0-9._/-]+$/, 'branch names may use letters, digits, ., _, / and -'); - -/** An ignore filter: a branch name, or a simple `*` glob (`renovate/*`). */ -export const branchFilterSchema = z - .string() - .min(1) - .max(120) - .regex(/^[A-Za-z0-9._/*-]+$/, 'branch filters may use letters, digits, ., _, /, - and *'); - -/** Tolerant parse of the `ignored_branches` D1 column - a row written by - * hand (or a future format) degrades to "nothing ignored", never a throw. */ -export function parseIgnoredBranches(json: string | null): string[] { - if (!json) return []; - try { - const parsed: unknown = JSON.parse(json); - if (!Array.isArray(parsed)) return []; - return parsed.filter((entry): entry is string => typeof entry === 'string'); - } catch { - return []; - } -} - -const escapeRegex = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - -/** - * Whether a branch matches any filter. Exact match, or `*` as "any run of - * characters" (it crosses `/` on purpose - `release/*` matching - * `release/1.2/hotfix` is the least surprising reading, and GitHub's own - * `branches-ignore` treats `**` that way). - */ -export function branchIsIgnored(patterns: string[], branch: string): boolean { - return patterns.some((pattern) => { - if (!pattern.includes('*')) return pattern === branch; - const regex = new RegExp(`^${pattern.split('*').map(escapeRegex).join('.*')}$`); - return regex.test(branch); - }); -} diff --git a/src/lib/branch-match.unit.test.ts b/src/lib/branch-match.unit.test.ts deleted file mode 100644 index 727fef9f..00000000 --- a/src/lib/branch-match.unit.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { branchFilterSchema, branchIsIgnored, parseIgnoredBranches } from './branch-match'; - -/** - * Three places enforce ignored branches (webhook skip, workflow - * branches-ignore, OIDC grant refusal) and all three call THIS matcher, so - * its edges are pinned here once. - */ - -test('exact names match exactly', () => { - assert.equal(branchIsIgnored(['tmp'], 'tmp'), true); - assert.equal(branchIsIgnored(['tmp'], 'tmp2'), false); - assert.equal(branchIsIgnored(['tmp'], 'a/tmp'), false); - assert.equal(branchIsIgnored([], 'anything'), false); -}); - -test('a * glob matches any run of characters, across slashes', () => { - assert.equal(branchIsIgnored(['renovate/*'], 'renovate/react-19'), true); - assert.equal(branchIsIgnored(['renovate/*'], 'renovate/deep/nesting'), true); - assert.equal(branchIsIgnored(['renovate/*'], 'renovate'), false); - assert.equal(branchIsIgnored(['*-wip'], 'feature-wip'), true); - assert.equal(branchIsIgnored(['*'], 'anything/at-all'), true); -}); - -test('regex metacharacters in a filter are literal, not syntax', () => { - assert.equal(branchIsIgnored(['v1.0'], 'v1.0'), true); - // An unescaped dot would let `v1.0` match `v1x0`. - assert.equal(branchIsIgnored(['v1.0'], 'v1x0'), false); - assert.equal(branchIsIgnored(['a+b'], 'a+b'), true); - assert.equal(branchIsIgnored(['a+b'], 'aab'), false); -}); - -test('parseIgnoredBranches tolerates every malformed column value', () => { - assert.deepEqual(parseIgnoredBranches(null), []); - assert.deepEqual(parseIgnoredBranches(''), []); - assert.deepEqual(parseIgnoredBranches('not json'), []); - assert.deepEqual(parseIgnoredBranches('{"a":1}'), []); - assert.deepEqual(parseIgnoredBranches('[1, "keep", null]'), ['keep']); - assert.deepEqual(parseIgnoredBranches('["tmp","renovate/*"]'), ['tmp', 'renovate/*']); -}); - -test('the filter schema refuses what YAML embedding cannot carry', () => { - assert.equal(branchFilterSchema.safeParse('renovate/*').success, true); - assert.equal(branchFilterSchema.safeParse('release-1.2_x').success, true); - for (const bad of ['', "quo'te", 'two words', 'new\nline', 'tick`s', '$(sub)']) { - assert.equal(branchFilterSchema.safeParse(bad).success, false, bad); - } -}); diff --git a/src/lib/components/console-shell.svelte b/src/lib/components/console-shell.svelte index cdee4ba3..0fa48809 100644 --- a/src/lib/components/console-shell.svelte +++ b/src/lib/components/console-shell.svelte @@ -71,11 +71,11 @@
-

$ npx cloudflarebase init my-app

-

$ cloudflarebase deploy

+

$ npx cloudflarebase init

+

$ cloudflarebase key create server --env-file

- ✓ Deployed - - my-app.cfbase.dev + ✓ Key written + - .env.local

diff --git a/src/lib/components/github-mark.svelte b/src/lib/components/github-mark.svelte deleted file mode 100644 index 256313f8..00000000 --- a/src/lib/components/github-mark.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - - diff --git a/src/lib/hosting-workflow.ts b/src/lib/hosting-workflow.ts deleted file mode 100644 index 33741af2..00000000 --- a/src/lib/hosting-workflow.ts +++ /dev/null @@ -1,330 +0,0 @@ -/** - * The official GitHub Actions deploy workflow (* Phase B) - Workers-Builds-style push-to-deploy WITHOUT us running a build - * farm: GitHub's runners do the user's build, `cloudflarebase deploy` ships - * the output with the repo's deploy token. Single-sourced here so the - * dashboard's Connect GitHub card can never drift from what we document. - * - * Branch mapping happens in the CLI: the default git branch deploys the root - * project, any other branch deploys `--` (auto-created), so a - * preview per git branch falls out. The two env vars exist because - * actions/checkout leaves a detached HEAD - git alone cannot name the branch. - * - * Connected workflows carry the framework preset resolved at connect time - * (`src/lib/server/frameworks.ts`): the build command, the output directory - * (as CLOUDFLAREBASE_ASSETS - the CLI already reads it), and install steps - * matched to the repository's package manager. - */ - -export const DEPLOY_TOKEN_SECRET_NAME = 'CLOUDFLAREBASE_DEPLOY_TOKEN'; -/** The legacy single-file path. Connections made before per-app filenames - * keep it forever (their row's `workflowPath` is null); the manual - * paste-a-workflow flow still uses it too. */ -export const WORKFLOW_FILENAME = '.github/workflows/cloudflarebase.yml'; - -/** Per-app workflow path - what closes the two-apps-one-repo collision: each - * connection owns its own file. App names are already filename-safe - * (`[a-z0-9-]`). */ -export function workflowPathFor(appName: string): string { - return `.github/workflows/cloudflarebase-${appName}.yml`; -} - -export type WorkflowPackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'; - -export interface WorkflowBuildOptions { - packageManager?: WorkflowPackageManager; - /** Preset or operator-edited command; null keeps `npm run build --if-present`. */ - buildCommand?: string | null; - /** Monorepo root: install, build, and deploy all run HERE. Null = repo - * root. hashFiles guards are prefixed because they always resolve from - * the workspace root, working-directory or not. */ - rootDir?: string | null; -} - -/** - * The dependency install step per package manager. npm and yarn ride - * setup-node's cache (both binaries are preinstalled on the runner); pnpm and - * bun are provisioned in the step itself - corepack first, so a repository - * pinning a version via the `packageManager` field gets exactly that version, - * with a global install as the fallback for repositories that pin nothing. - */ -function installSteps(pm: WorkflowPackageManager, dir: string | null): string { - const inDir = (file: string): string => (dir ? `${dir}/${file}` : file); - const wd = dir ? `\n working-directory: ${dir}` : ''; - const guard = ` if: hashFiles('${inDir('package.json')}') != ''${wd}`; - if (pm === 'pnpm') { - return ` - name: Install dependencies -${guard} - run: | - corepack enable 2>/dev/null || true - command -v pnpm >/dev/null 2>&1 || npm install -g pnpm - pnpm install --frozen-lockfile || pnpm install`; - } - if (pm === 'bun') { - return ` - name: Install dependencies -${guard} - run: | - command -v bun >/dev/null 2>&1 || npm install -g bun - bun install --frozen-lockfile || bun install`; - } - if (pm === 'yarn') { - return ` - name: Install dependencies -${guard} - # --immutable is Yarn Berry, --frozen-lockfile is classic; the plain - # install covers a lockfile that has drifted, which both refuse. - run: | - corepack enable 2>/dev/null || true - yarn install --immutable || yarn install --frozen-lockfile || yarn install`; - } - return ` - name: Install dependencies -${guard} - # ci skips dependency resolution entirely; the fallback covers a - # lockfile that has drifted, which ci refuses and install repairs. - # --no-audit/--no-fund drop two registry round trips nothing reads here. - run: | - if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then - npm ci --prefer-offline --no-audit --no-fund || npm install --no-audit --no-fund - else - npm install --no-audit --no-fund - fi`; -} - -/** - * Everything between checkout and deploy. This runs on every push, so the - * install is worth tuning: restoring the package cache and using the frozen - * install turns a cold ~60s install into ~15s. Cache is CONDITIONAL, because - * a connected repository is not guaranteed to have a lockfile - and - * `cache: npm` with no lockfile FAILS the job outright rather than quietly - * skipping the cache, which would break the deploy we are supposed to be - * speeding up. pnpm and bun skip the cache: setup-node's cache needs the - * binary on PATH before this step, and they are installed after it. - */ -function buildSteps(options: WorkflowBuildOptions = {}): string { - const pm = options.packageManager ?? 'npm'; - const dir = options.rootDir ?? null; - const inDir = (file: string): string => (dir ? `${dir}/${file}` : file); - const cache = - pm === 'npm' - ? `\${{ hashFiles('${inDir('package-lock.json')}', '${inDir('npm-shrinkwrap.json')}') != '' && 'npm' || '' }}` - : pm === 'yarn' - ? `\${{ hashFiles('${inDir('yarn.lock')}') != '' && 'yarn' || '' }}` - : `''`; - // setup-node's cache hashes the ROOT lockfile by default; a monorepo's - // lives beside (or above) the root directory, so point it explicitly. - const cachePath = - dir && (pm === 'npm' || pm === 'yarn') - ? `\n cache-dependency-path: ${pm === 'yarn' ? inDir('yarn.lock') : inDir('package-lock.json')}` - : ''; - const wd = dir ? `\n working-directory: ${dir}` : ''; - // A custom command runs unconditionally (Hugo has no package.json at all); - // the default is guarded because `npm run` without one is a hard error. - const buildIf = options.buildCommand - ? wd - : `\n if: hashFiles('${inDir('package.json')}') != ''${wd}`; - const command = options.buildCommand ?? 'npm run build --if-present'; - return ` - uses: actions/setup-node@v4 - with: - node-version: 22 - # Restores the package cache. That also holds npx's cache, so the - # deploy step reuses it - npx still checks the registry for a newer - # CLI, since a name-only spec is re-resolved on every run. - cache: ${cache}${cachePath} -${installSteps(pm, dir)} - # Adjust to your stack; the command was chosen when the repository was - # connected and reconnecting rewrites it. - - name: Build${buildIf} - # Bounded separately from the job so a hang is ATTRIBUTED. A job-level - # timeout alone reports "the job was cancelled", which does not say - # which step stopped - and a build that never exits is the likeliest - # thing to stop here, since the runner cannot start the deploy until - # this process exits. - timeout-minutes: 10 - run: | - # Node picks its own heap limit from the machine, which is fine - # until a large SSR build reaches it - and reaching it does not - # crash cleanly, it degrades into GC thrashing that looks exactly - # like a hang, for hours. Set it explicitly to two thirds of this - # runner's RAM: enough that a bigger runner is actually used, - # conservative enough that the heap plus everything else stays - # inside the box (overshooting trades a clean heap error for the - # OOM killer, which is the worse failure). - # Guarded on \`free\` so a non-Linux runs-on still builds, and - # appended so a NODE_OPTIONS you set yourself still wins. - if command -v free >/dev/null 2>&1; then - export NODE_OPTIONS="--max-old-space-size=$(free -m | awk '/^Mem:/ {print int($2 * 2 / 3)}') $NODE_OPTIONS" - fi - ${command}`; -} - -export function deployWorkflowYaml(): string { - return `# Deploys this repository to Cloudflarebase on every push. -# The default branch deploys production; any other branch deploys an -# isolated preview at -.cfbase.dev. Requires the -# ${DEPLOY_TOKEN_SECRET_NAME} repository secret (minted on the Hosting page). -name: Deploy to Cloudflarebase - -on: - push: - branches: ['**'] - -concurrency: - group: cloudflarebase-\${{ github.ref }} - cancel-in-progress: true - -jobs: - deploy: - runs-on: ubuntu-latest - # GitHub's default is 360 minutes. A deploy that has not finished in 15 is - # not slow, it is stuck - a build waiting on a prompt, a prerender fetching - # a URL that never answers - and six hours of billed minutes is a bad way - # to find that out. Raise it if a genuine build needs longer. - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 -${buildSteps()} - - name: Deploy - # Bounded so a stalled upload is attributed here, not to a job cancel. - timeout-minutes: 5 - run: npx --yes @cloudflarebase/cli deploy - env: - ${DEPLOY_TOKEN_SECRET_NAME}: \${{ secrets.${DEPLOY_TOKEN_SECRET_NAME} }} - CLOUDFLAREBASE_GIT_BRANCH: \${{ github.ref_name }} - CLOUDFLAREBASE_DEFAULT_BRANCH: \${{ github.event.repository.default_branch }} -`; -} - -export interface ConnectedWorkflowInput { - /** Console origin - also the OIDC audience the token is minted for. */ - origin: string; - /** ROOT project id; the branch is derived from the pushed ref. */ - projectId: string; - appName: string; - /** Framework preset, resolved at connect time. All optional: a repo we - * could not inspect gets the same generic workflow as before. */ - packageManager?: WorkflowPackageManager; - buildCommand?: string | null; - /** Published as CLOUDFLAREBASE_ASSETS, relative to rootDir; null lets the - * CLI autodetect. */ - outputDir?: string | null; - /** Monorepo root directory - every step runs here. Null = repo root. */ - rootDir?: string | null; - /** Branch that deploys the root project. Null = the repository's default - * branch, resolved dynamically (`github.event.repository.default_branch`), - * which is what pre-existing workflows do. */ - productionBranch?: string | null; - /** Pushes to these never build - emitted as `branches-ignore` (GitHub - * refuses `branches` and `branches-ignore` on the same event, so the - * trigger emits exactly one). Charset pre-validated by branchFilterSchema. */ - ignoredBranches?: string[]; -} - -/** The trigger block: catch-all, or the ignore list - never both. */ -function pushTrigger(ignoredBranches: string[] | undefined): string { - if (!ignoredBranches?.length) return ` branches: ['**']`; - return ` branches-ignore: [${ignoredBranches.map((branch) => `'${branch}'`).join(', ')}]`; -} - -/** - * The step that turns the app's stored build environment into env vars for - * the build. Same OIDC identity the deploy step presents, minted here because - * $GITHUB_ENV is the only way to reach the BUILD step's environment. Secret - * values are masked before export so they never print in the job log. An app - * with no build env gets `{vars:{},secrets:{}}` - the step only fails when - * something is genuinely wrong (curl -sSf), and a build silently missing its - * secrets is worse than one that fails attributed. - */ -function fetchBuildEnvStep(input: { origin: string; projectId: string; appName: string }): string { - return ` - name: Fetch build environment - timeout-minutes: 2 - run: | - OIDC=$(curl -sSf -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \\ - "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$CLOUDFLAREBASE_URL" | jq -r '.value') - ENV_JSON=$(curl -sSf -H "Authorization: Bearer $OIDC" \\ - "$CLOUDFLAREBASE_URL/api/projects/$CLOUDFLAREBASE_PROJECT/hosting/apps/$CLOUDFLAREBASE_APP/build-env") - echo "$ENV_JSON" | jq -r '.secrets | to_entries[] | .value' | while IFS= read -r v; do echo "::add-mask::$v"; done - echo "$ENV_JSON" | jq -r '(.vars + .secrets) | to_entries[] | "\\(.key)=\\(.value)"' >> "$GITHUB_ENV" - env: - CLOUDFLAREBASE_URL: ${input.origin} - CLOUDFLAREBASE_PROJECT: ${input.projectId} - CLOUDFLAREBASE_APP: ${input.appName}`; -} - -/** - * The workflow written INTO the repository by the GitHub App (`build` mode). - * - * Two differences from the manual one above, both consequences of the App - * knowing who this repo is: - * - * - **No secret.** `id-token: write` lets the job mint a short-lived OIDC - * token describing the repository; the console verifies it against - * GitHub's public keys and looks the repository up in its connection - * table. Nothing is stored in the repo, so nothing can leak or need - * rotating. The CLI mints it - there is no extra step here. - * - **No `cloudflarebase.json` needed.** The console knows the project and - * app at write time and passes them as env, so connecting in the console - * is the whole setup: the operator never runs the CLI locally. - */ -export function connectedWorkflowYaml(input: ConnectedWorkflowInput): string { - const assetsLine = input.outputDir ? `\n CLOUDFLAREBASE_ASSETS: ${input.outputDir}` : ''; - // The CLI resolves root-vs-branch by comparing GIT_BRANCH against - // DEFAULT_BRANCH, so a user-set production branch simply becomes the - // literal value here - zero CLI changes, and pre-existing workflows keep - // the dynamic default. - const defaultBranch = input.productionBranch - ? input.productionBranch - : `\${{ github.event.repository.default_branch }}`; - return `# Deploys this repository to Cloudflarebase on every push. -# Managed by Cloudflarebase - saving build settings in the console (or -# reconnecting the repository) rewrites this file. -# The production branch deploys the root project; any other branch deploys an -# isolated preview at -. Authentication is GitHub's OIDC token, -# so this repository holds no Cloudflarebase secret. -name: Deploy to Cloudflarebase (${input.appName}) - -on: - push: -${pushTrigger(input.ignoredBranches)} - -concurrency: - group: cloudflarebase-${input.appName}-\${{ github.ref }} - cancel-in-progress: true - -jobs: - deploy: - runs-on: ubuntu-latest - # GitHub's default is 360 minutes. A deploy that has not finished in 15 is - # not slow, it is stuck - a build waiting on a prompt, a prerender fetching - # a URL that never answers - and six hours of billed minutes is a bad way - # to find that out. Raise it if a genuine build needs longer. - timeout-minutes: 15 - permissions: - contents: read - # Mints the identity token the console verifies. Without it the deploy - # has no credential at all and will be refused. - id-token: write - steps: - - uses: actions/checkout@v4 -${fetchBuildEnvStep(input)} -${buildSteps({ packageManager: input.packageManager, buildCommand: input.buildCommand, rootDir: input.rootDir })} - - name: Deploy${input.rootDir ? `\n working-directory: ${input.rootDir}` : ''} - # Bounded so a stalled upload is attributed here, not to a job cancel. - timeout-minutes: 5 - run: npx --yes @cloudflarebase/cli deploy - env: - CLOUDFLAREBASE_URL: ${input.origin} - CLOUDFLAREBASE_PROJECT: ${input.projectId} - CLOUDFLAREBASE_APP: ${input.appName}${assetsLine} - CLOUDFLAREBASE_GIT_BRANCH: \${{ github.ref_name }} - CLOUDFLAREBASE_DEFAULT_BRANCH: ${defaultBranch} -`; -} - -/** GitHub's new-file editor with the workflow pre-filled - the manual path, - * for consoles with no GitHub App configured. */ -export function workflowCreateUrl(repo: string): string { - return `https://github.com/${repo}/new/main?filename=${encodeURIComponent(WORKFLOW_FILENAME)}&value=${encodeURIComponent(deployWorkflowYaml())}`; -} - -/** Where the deploy token gets pasted. */ -export function secretsUrl(repo: string): string { - return `https://github.com/${repo}/settings/secrets/actions/new`; -} diff --git a/src/lib/hosting-workflow.unit.test.ts b/src/lib/hosting-workflow.unit.test.ts deleted file mode 100644 index 030f184e..00000000 --- a/src/lib/hosting-workflow.unit.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import assert from 'node:assert/strict'; -import { test } from 'node:test'; -import { - connectedWorkflowYaml, - deployWorkflowYaml, - WORKFLOW_FILENAME, - workflowPathFor -} from './hosting-workflow'; - -const base = { origin: 'https://cloudflarebase.com', projectId: 'demo-app', appName: 'landmatch' }; - -test('the default connected workflow matches the pre-preset shape', () => { - const yaml = connectedWorkflowYaml(base); - assert.match(yaml, /npm ci --prefer-offline/); - assert.match(yaml, /npm run build --if-present/); - // The default build is guarded: `npm run` without a package.json is a - // hard error, not a skip. - assert.match(yaml, /- name: Build\n {8}if: hashFiles\('package\.json'\) != ''/); - assert.doesNotMatch(yaml, /CLOUDFLAREBASE_ASSETS/); - assert.match(yaml, /id-token: write/); -}); - -test('a preset command replaces the default and drops its package.json guard', () => { - const yaml = connectedWorkflowYaml({ ...base, buildCommand: 'hugo --minify' }); - assert.match(yaml, /\n {10}hugo --minify\n/); - assert.doesNotMatch(yaml, /npm run build --if-present/); - assert.doesNotMatch(yaml, /- name: Build\n {8}if:/); -}); - -test('the output directory travels as CLOUDFLAREBASE_ASSETS', () => { - const yaml = connectedWorkflowYaml({ ...base, outputDir: '.output/public' }); - assert.match(yaml, /\n {10}CLOUDFLAREBASE_ASSETS: \.output\/public\n/); -}); - -test('pnpm repositories install with pnpm and skip the setup-node cache', () => { - const yaml = connectedWorkflowYaml({ - ...base, - packageManager: 'pnpm', - buildCommand: 'pnpm run build' - }); - assert.match(yaml, /pnpm install --frozen-lockfile \|\| pnpm install/); - assert.match(yaml, /cache: ''/); - assert.match(yaml, /corepack enable/); - assert.doesNotMatch(yaml, /npm ci/); -}); - -test('yarn keeps the cache and tries immutable before frozen-lockfile', () => { - const yaml = connectedWorkflowYaml({ ...base, packageManager: 'yarn' }); - assert.match(yaml, /yarn install --immutable \|\| yarn install --frozen-lockfile/); - assert.match(yaml, /hashFiles\('yarn\.lock'\)/); -}); - -test('every step that can hang carries its own timeout', () => { - for (const yaml of [connectedWorkflowYaml(base), deployWorkflowYaml()]) { - const buildTimeouts = yaml.match(/timeout-minutes: 10/g) ?? []; - const deployTimeouts = yaml.match(/timeout-minutes: 5/g) ?? []; - assert.equal(buildTimeouts.length, 1, 'the build step is bounded'); - assert.equal(deployTimeouts.length, 1, 'the deploy step is bounded'); - } -}); - -test('a root directory scopes every step and re-points the hashFiles guards', () => { - const yaml = connectedWorkflowYaml({ ...base, rootDir: 'sites/blabla' }); - // hashFiles resolves from the workspace root regardless of - // working-directory, so every guard must carry the prefix. - assert.match(yaml, /hashFiles\('sites\/blabla\/package\.json'\)/); - assert.doesNotMatch(yaml, /hashFiles\('package\.json'\)/); - assert.match( - yaml, - /hashFiles\('sites\/blabla\/package-lock\.json', 'sites\/blabla\/npm-shrinkwrap\.json'\)/ - ); - assert.match(yaml, /cache-dependency-path: sites\/blabla\/package-lock\.json/); - // Install, build, and deploy all run in the subdirectory. - const wd = yaml.match(/working-directory: sites\/blabla/g) ?? []; - assert.equal(wd.length, 3); -}); - -test('no root directory means no working-directory lines at all', () => { - const yaml = connectedWorkflowYaml(base); - assert.doesNotMatch(yaml, /working-directory/); - assert.doesNotMatch(yaml, /cache-dependency-path/); -}); - -test('per-app workflow paths never collide with the legacy shared file', () => { - assert.equal(workflowPathFor('landmatch'), '.github/workflows/cloudflarebase-landmatch.yml'); - assert.notEqual(workflowPathFor('landmatch'), workflowPathFor('docs')); - assert.notEqual(workflowPathFor('landmatch'), WORKFLOW_FILENAME); -}); - -test('the trigger emits branches XOR branches-ignore, never both', () => { - // GitHub refuses a push trigger that carries both keys. - const open = connectedWorkflowYaml(base); - assert.match(open, /branches: \['\*\*'\]/); - assert.doesNotMatch(open, /branches-ignore/); - - const filtered = connectedWorkflowYaml({ ...base, ignoredBranches: ['tmp', 'renovate/*'] }); - assert.match(filtered, /branches-ignore: \['tmp', 'renovate\/\*'\]/); - assert.doesNotMatch(filtered, /branches: \['\*\*'\]/); -}); - -test('a user-set production branch becomes the literal DEFAULT_BRANCH', () => { - // The CLI resolves root-vs-branch by comparing GIT_BRANCH against - // DEFAULT_BRANCH, so the literal is the whole mechanism - no CLI change. - const yaml = connectedWorkflowYaml({ ...base, productionBranch: 'release' }); - assert.match(yaml, /CLOUDFLAREBASE_DEFAULT_BRANCH: release\n/); - assert.doesNotMatch(yaml, /github\.event\.repository\.default_branch/); - - const dynamic = connectedWorkflowYaml(base); - assert.match( - dynamic, - /CLOUDFLAREBASE_DEFAULT_BRANCH: \$\{\{ github\.event\.repository\.default_branch \}\}/ - ); -}); - -test('the build-env fetch runs before the build and masks secrets', () => { - const yaml = connectedWorkflowYaml(base); - assert.match(yaml, /- name: Fetch build environment/); - // Order is the contract: env must exist before the build step reads it. - assert.ok(yaml.indexOf('Fetch build environment') < yaml.indexOf('- name: Build')); - // Secrets are masked BEFORE they land in $GITHUB_ENV, so they never print. - assert.ok(yaml.indexOf('::add-mask::') < yaml.indexOf('>> "$GITHUB_ENV"')); - // The OIDC audience is the console origin, same as the deploy step's token. - assert.match(yaml, /audience=\$CLOUDFLAREBASE_URL/); - // -sSf: a failed fetch fails the step - a build silently missing its - // secrets is worse than one that fails attributed. - assert.match(yaml, /curl -sSf/); -}); - -test('a fully-optioned workflow carries every setting at once', () => { - const yaml = connectedWorkflowYaml({ - ...base, - packageManager: 'pnpm', - buildCommand: 'pnpm run build', - outputDir: 'dist', - rootDir: 'apps/web', - productionBranch: 'release/stable', - ignoredBranches: ['tmp', 'wip-*'] - }); - assert.match(yaml, /branches-ignore: \['tmp', 'wip-\*'\]/); - assert.match(yaml, /CLOUDFLAREBASE_DEFAULT_BRANCH: release\/stable\n/); - assert.match(yaml, /CLOUDFLAREBASE_ASSETS: dist\n/); - assert.match(yaml, /pnpm run build/); - assert.match(yaml, /name: Deploy to Cloudflarebase \(landmatch\)/); - // Two apps in one repo must not cancel each other's runs. - assert.match(yaml, /group: cloudflarebase-landmatch-\$\{\{ github\.ref \}\}/); -}); - -test('YAML stays tab-free and the run blocks stay indented', () => { - const yaml = connectedWorkflowYaml({ - ...base, - packageManager: 'bun', - buildCommand: 'bun run build && bun run postbuild', - outputDir: 'dist', - rootDir: 'apps/web' - }); - assert.doesNotMatch(yaml, /\t/); - // Every non-empty line inside the file is indented or a top-level key. - for (const line of yaml.split('\n')) { - if (!line) continue; - assert.ok(/^( |#|name:|on:|concurrency:|jobs:)/.test(line), `unexpected line: ${line}`); - } -}); diff --git a/src/lib/openapi/hosting.ts b/src/lib/openapi/hosting.ts deleted file mode 100644 index 15824a8b..00000000 --- a/src/lib/openapi/hosting.ts +++ /dev/null @@ -1,470 +0,0 @@ -import { - deployTokenSchema, - githubConnectionPatchSchema, - githubConnectionSchema, - githubConnectRequestSchema, - hostingAnalyticsSchema, - hostingBuildEnvBundleSchema, - hostingBuildEnvSchema, - hostingBuildSecretRequestSchema, - hostingClaimRequestSchema, - hostingClaimSchema, - hostingDeployPageSchema, - hostingOverviewSchema, - hostingSecretListSchema, - hostingSecretRequestSchema, - hostingVarListSchema, - hostingVarsUpdateSchema, - hostingVarsUpdatedSchema, - mintDeployTokenSchema, - mintedDeployTokenSchema -} from '$lib/agents'; -import { jsonBody, jsonResponse, ref, UNAUTHORIZED, type AgentOpenApiModule } from './shared'; - -/** - * Hosting agent module (Phase B). Every - * endpoint here is operator-plane: apps are deployed BY the project owner, - * not called by their application, so nothing is public. Claims and deploy - * tokens are console-plane (control-plane D1) but documented under the same - * tag - to an operator they are one Hosting surface. - */ - -const HOSTING_TAG = 'Hosting'; - -const appParam = { - name: 'app', - in: 'path', - required: true, - description: 'The app name (not the subdomain - taken names auto-number).', - schema: { type: 'string' } -}; - -export const hostingOpenApi: AgentOpenApiModule = { - tags: [ - { - name: HOSTING_TAG, - description: [ - 'Apps and functions on Workers for Platforms: static assets and server', - 'code in one deploy, served at `.cfbase.dev` (root project) or', - '`-.cfbase.dev` (branch). Deploys ride an operator session', - 'or a project-scoped deploy token (`cfbd_...` bearer), which is also', - 'accepted on `POST /branches` so CI can create preview branches.' - ].join('\n') - } - ], - schemas: [ - hostingOverviewSchema, - hostingDeployPageSchema, - hostingClaimSchema, - hostingClaimRequestSchema, - hostingVarListSchema, - hostingVarsUpdateSchema, - hostingVarsUpdatedSchema, - hostingSecretListSchema, - hostingSecretRequestSchema, - hostingBuildEnvSchema, - hostingBuildEnvBundleSchema, - hostingBuildSecretRequestSchema, - hostingAnalyticsSchema, - deployTokenSchema, - mintDeployTokenSchema, - mintedDeployTokenSchema, - githubConnectionSchema, - githubConnectRequestSchema, - githubConnectionPatchSchema - ], - paths: { - '/hosting/overview': { - get: { - tags: [HOSTING_TAG], - summary: 'Hosting overview', - description: 'Apps, recent deploys, and whether this install can complete real deploys.', - security: [{ sessionCookie: [] }], - responses: { - '200': jsonResponse(hostingOverviewSchema, 'The overview.'), - '401': UNAUTHORIZED - } - } - }, - '/hosting/deploys': { - get: { - tags: [HOSTING_TAG], - summary: 'List deploys', - description: 'Keyset-paged deploy history, newest first. Filter with `?app=`.', - security: [{ sessionCookie: [] }], - parameters: [ - { name: 'app', in: 'query', schema: { type: 'string' } }, - { name: 'cursor', in: 'query', schema: { type: 'string' } }, - { name: 'limit', in: 'query', schema: { type: 'integer', maximum: 100 } } - ], - responses: { - '200': jsonResponse(hostingDeployPageSchema, 'One page of deploys.'), - '401': UNAUTHORIZED - } - } - }, - '/hosting/claims': { - post: { - tags: [HOSTING_TAG], - summary: 'Claim a subdomain', - description: - 'Resolves the subdomain for this project+app: the persisted claim wins; otherwise the wanted name (`` on the root, `-` on a branch) auto-numbers past collisions - it never fails on a taken name. `dry: true` previews without claiming.', - security: [{ sessionCookie: [] }], - requestBody: jsonBody(hostingClaimRequestSchema, 'The app to claim for.'), - responses: { - '200': jsonResponse(hostingClaimSchema, 'Existing claim reused (or dry run).'), - '201': jsonResponse(hostingClaimSchema, 'Subdomain claimed.'), - '400': { description: 'Invalid app name.' }, - '401': UNAUTHORIZED, - '403': { description: 'Demo projects cannot deploy apps.' }, - '409': { description: 'App limit reached.' } - } - } - }, - '/hosting/apps/{app}/deploys': { - post: { - tags: [HOSTING_TAG], - summary: 'Deploy an app', - description: - 'Multipart deploy: a `meta` JSON part plus `module:` and `asset:` file parts. The console resolves the subdomain claim, then the agent runs the Workers for Platforms upload. Accepts an operator session or a deploy token. The response reports the subdomain that was ACTUALLY claimed.', - security: [{ sessionCookie: [] }, { bearerAuth: [] }], - parameters: [appParam], - responses: { - '201': { description: 'Deployed; the body carries the deploy, subdomain, and URL.' }, - '400': { description: 'Invalid deploy payload or a cap was exceeded.' }, - '401': UNAUTHORIZED, - '403': { description: 'Demo projects cannot deploy apps.' }, - '429': { description: 'Daily deploy limit reached.' }, - '503': { description: 'Hosting is not configured on this install.' } - } - } - }, - '/hosting/apps/{app}/vars': { - get: { - tags: [HOSTING_TAG], - summary: 'List runtime variables', - description: - 'The stored plain-text variables for one app. They apply as bindings on every deploy; stored values win over the CLI-declared `meta.vars` of the same name.', - security: [{ sessionCookie: [] }], - parameters: [appParam], - responses: { - '200': jsonResponse(hostingVarListSchema, 'The variables.'), - '401': UNAUTHORIZED - } - }, - put: { - tags: [HOSTING_TAG], - summary: 'Replace runtime variables', - description: - 'Replaces the whole set - absent names are deleted. The store always succeeds; when the app has a live deploy the script is patched in place, reported by `patched` (false = the change applies at the next deploy).', - security: [{ sessionCookie: [] }], - parameters: [appParam], - requestBody: jsonBody(hostingVarsUpdateSchema, 'The full variable set.'), - responses: { - '200': jsonResponse(hostingVarsUpdatedSchema, 'The stored set and patch outcome.'), - '400': { description: 'Invalid name/value, or the 64-variable cap was exceeded.' }, - '401': UNAUTHORIZED - } - } - }, - '/hosting/apps/{app}/secrets': { - get: { - tags: [HOSTING_TAG], - summary: 'List secrets', - description: 'Names and timestamps only - values are write-through and unrecoverable.', - security: [{ sessionCookie: [] }], - parameters: [appParam], - responses: { - '200': jsonResponse(hostingSecretListSchema, 'The secret names.'), - '401': UNAUTHORIZED - } - }, - post: { - tags: [HOSTING_TAG], - summary: 'Set a secret', - description: - 'Writes one secret through to the deployed script (`cloudflarebase secret put`). Deploys use `keep_bindings`, so redeploys never drop it. Requires a deployed app.', - security: [{ sessionCookie: [] }], - parameters: [appParam], - requestBody: jsonBody(hostingSecretRequestSchema, 'The secret.'), - responses: { - '200': { description: 'Set.' }, - '400': { description: 'Invalid body, or the 64-secret cap was exceeded.' }, - '401': UNAUTHORIZED, - '404': { description: 'No such app (deploy first).' }, - '503': { description: 'Hosting is not configured on this install.' } - } - } - }, - '/hosting/apps/{app}/secrets/{name}': { - delete: { - tags: [HOSTING_TAG], - summary: 'Delete a secret', - description: 'Removes the script binding and the name record. Idempotent.', - security: [{ sessionCookie: [] }], - parameters: [ - appParam, - { name: 'name', in: 'path', required: true, schema: { type: 'string' } } - ], - responses: { - '200': { description: 'Deleted (or was already gone).' }, - '401': UNAUTHORIZED, - '502': { description: 'Cloudflare refused the deletion; the secret is still set.' } - } - } - }, - '/hosting/apps/{app}/analytics': { - get: { - tags: [HOSTING_TAG], - summary: 'App request analytics', - description: - 'Daily requests and 5xx errors for one app, from Analytics Engine. Degrades honestly: `engine.status` reports connected/local/write-only/error and the route never 5xxes on a query failure.', - security: [{ sessionCookie: [] }], - parameters: [ - appParam, - { name: 'days', in: 'query', schema: { type: 'integer', enum: [7, 30, 90] } } - ], - responses: { - '200': jsonResponse(hostingAnalyticsSchema, 'The series and totals.'), - '401': UNAUTHORIZED, - '404': { description: 'No such app (deploy first).' } - } - } - }, - '/hosting/apps/{app}/build-env': { - get: { - tags: [HOSTING_TAG], - summary: 'Read the build-time environment', - description: - 'Two personas. An operator session gets `HostingBuildEnv`: vars with values, secret NAMES only. A GitHub Actions OIDC bearer (the workflow of the connection that owns this app) gets `HostingBuildEnvBundle`: the decrypted values it exports before the build step. Build env is connection-scoped - branch builds read the root project.', - security: [{ sessionCookie: [] }, { bearerAuth: [] }], - parameters: [appParam], - responses: { - '200': { - description: 'The environment, shaped by the caller (see description).', - content: { - 'application/json': { - schema: { - oneOf: [ref(hostingBuildEnvSchema), ref(hostingBuildEnvBundleSchema)] - } - } - } - }, - '401': UNAUTHORIZED, - '503': { - description: 'Build secrets exist but HOSTING_MASTER_KEY is not set on this install.' - } - } - } - }, - '/hosting/apps/{app}/build-vars': { - put: { - tags: [HOSTING_TAG], - summary: 'Replace build-time variables', - description: - 'Replaces the whole set - absent names are deleted. Exported into the build environment by the connected workflow.', - security: [{ sessionCookie: [] }], - parameters: [appParam], - requestBody: jsonBody(hostingVarsUpdateSchema, 'The full variable set.'), - responses: { - '200': jsonResponse(hostingVarListSchema, 'The stored set.'), - '400': { description: 'Invalid name/value, or the 64-variable cap was exceeded.' }, - '401': UNAUTHORIZED - } - } - }, - '/hosting/apps/{app}/build-secrets/{name}': { - put: { - tags: [HOSTING_TAG], - summary: 'Set a build secret', - description: - "Encrypted at rest (AES-256-GCM) under the install's master key. Write-only on this surface: the console never reads a value back, and the runner's copy travels the OIDC build-env route.", - security: [{ sessionCookie: [] }], - parameters: [ - appParam, - { name: 'name', in: 'path', required: true, schema: { type: 'string' } } - ], - requestBody: jsonBody(hostingBuildSecretRequestSchema, 'The secret value.'), - responses: { - '200': { description: 'Set.' }, - '400': { description: 'Invalid name/value, or the 32-secret cap was exceeded.' }, - '401': UNAUTHORIZED, - '503': { description: 'HOSTING_MASTER_KEY is not set on this install.' } - } - }, - delete: { - tags: [HOSTING_TAG], - summary: 'Delete a build secret', - security: [{ sessionCookie: [] }], - parameters: [ - appParam, - { name: 'name', in: 'path', required: true, schema: { type: 'string' } } - ], - responses: { - '200': { description: 'Deleted (or was already gone).' }, - '401': UNAUTHORIZED - } - } - }, - '/hosting/tokens': { - get: { - tags: [HOSTING_TAG], - summary: 'List deploy tokens', - description: 'Metadata only - secrets are stored as SHA-256 digests and unrecoverable.', - security: [{ sessionCookie: [] }], - responses: { - '200': { - description: 'The tokens.', - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - tokens: { type: 'array', items: { $ref: '#/components/schemas/DeployToken' } } - }, - required: ['tokens'] - } - } - } - }, - '401': UNAUTHORIZED - } - }, - post: { - tags: [HOSTING_TAG], - summary: 'Mint a deploy token', - description: - 'Root projects only; the token covers the root and all its branches. The secret appears exactly once in this response.', - security: [{ sessionCookie: [] }], - requestBody: jsonBody(mintDeployTokenSchema, 'A label for the token.'), - responses: { - '201': jsonResponse(mintedDeployTokenSchema, 'The minted token.'), - '400': { description: 'Invalid name, or the project is a branch.' }, - '401': UNAUTHORIZED, - '409': { description: 'Token limit reached.' } - } - } - }, - '/hosting/tokens/{tokenId}': { - delete: { - tags: [HOSTING_TAG], - summary: 'Revoke a deploy token', - security: [{ sessionCookie: [] }], - parameters: [{ name: 'tokenId', in: 'path', required: true, schema: { type: 'string' } }], - responses: { - '200': { description: 'Revoked.' }, - '401': UNAUTHORIZED, - '404': { description: 'No such token.' } - } - } - }, - '/hosting/github': { - get: { - tags: [HOSTING_TAG], - summary: 'GitHub push-to-deploy state', - description: - 'Whether a GitHub App is configured on this console, the installations visible to this organization, and the repository connections.', - security: [{ sessionCookie: [] }], - responses: { - '200': { - description: 'The state.', - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - configured: { type: 'boolean' }, - installations: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'integer' }, - accountLogin: { type: 'string' } - }, - required: ['id', 'accountLogin'] - } - }, - connections: { - type: 'array', - items: { $ref: '#/components/schemas/GithubConnection' } - } - }, - required: ['configured', 'installations', 'connections'] - } - } - } - }, - '401': UNAUTHORIZED - } - } - }, - '/hosting/github/connections': { - post: { - tags: [HOSTING_TAG], - summary: 'Connect a repository', - description: - 'Connects a repository to this project+app (root projects only). `build` mode commits a deploy workflow into the repository and trusts its Actions OIDC token; `direct` mode deploys the pushed tree from the webhook with no file in the repository. Claims the subdomain first.', - security: [{ sessionCookie: [] }], - requestBody: jsonBody(githubConnectRequestSchema, 'The repository and its settings.'), - responses: { - '201': { - description: 'Connected; the body carries the connection and the claimed subdomain.' - }, - '400': { description: 'Invalid request, or the project is a branch.' }, - '401': UNAUTHORIZED, - '403': { description: 'The installation belongs to another organization.' }, - '404': { description: 'The repository is not in this installation.' }, - '503': { description: 'No GitHub App is configured on this console.' } - } - } - }, - '/hosting/github/connections/{app}': { - patch: { - tags: [HOSTING_TAG], - summary: 'Edit build settings', - description: - 'Edits a connection at any time: build command, root directory, output directory, production branch, ignored branches. On a build-mode connection the workflow file is rewritten in the repository FIRST - a failed commit changes nothing.', - security: [{ sessionCookie: [] }], - parameters: [appParam], - requestBody: jsonBody(githubConnectionPatchSchema, 'The fields to change.'), - responses: { - '200': { - description: 'Updated.', - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - connection: { $ref: '#/components/schemas/GithubConnection' }, - workflowRewritten: { type: 'boolean' } - }, - required: ['connection', 'workflowRewritten'] - } - } - } - }, - '400': { - description: - 'Invalid value, build fields on a direct connection, or the production branch would be ignored.' - }, - '401': UNAUTHORIZED, - '404': { description: 'No such connection.' }, - '503': { description: 'No GitHub App is configured on this console.' } - } - }, - delete: { - tags: [HOSTING_TAG], - summary: 'Disconnect a repository', - description: 'Deletes the connection; the committed workflow file is removed best-effort.', - security: [{ sessionCookie: [] }], - parameters: [appParam], - responses: { - '200': { description: 'Disconnected.' }, - '401': UNAUTHORIZED, - '404': { description: 'No such connection.' } - } - } - } - } -}; diff --git a/src/lib/openapi/index.ts b/src/lib/openapi/index.ts index 47ea2d01..b54c0c68 100644 --- a/src/lib/openapi/index.ts +++ b/src/lib/openapi/index.ts @@ -2,7 +2,6 @@ import { z } from 'zod'; import { authOpenApi } from './auth'; import { consoleOpenApi } from './console'; import { dbOpenApi } from './db'; -import { hostingOpenApi } from './hosting'; import { storageOpenApi } from './storage'; import type { AgentOpenApiModule } from './shared'; @@ -21,13 +20,7 @@ import type { AgentOpenApiModule } from './shared'; * what z.toJSONSchema emits, so the component schemas need no translation. */ -const MODULES: AgentOpenApiModule[] = [ - authOpenApi, - dbOpenApi, - hostingOpenApi, - storageOpenApi, - consoleOpenApi -]; +const MODULES: AgentOpenApiModule[] = [authOpenApi, dbOpenApi, storageOpenApi, consoleOpenApi]; /** Named schemas that become components.schemas entries. */ const registry = z.registry<{ id: string }>(); @@ -107,7 +100,7 @@ export function buildOpenApiDocument({ projectId, origin }: OpenApiOptions) { 'it bypasses access modes, document validators, and permission keys exactly as an operator ' + 'session does, and it is scoped to THIS project only - not to sibling branches, because for ' + 'data the branch is the isolation boundary. It cannot mint credentials, re-role operators, ' + - 'deploy, or delete its own project. ' + + 'or delete its own project. ' + '**Any request carrying an `Origin` header is refused, whatever the key**, so a key pasted ' + 'into frontend code fails at your desk instead of shipping in a JS bundle; server HTTP ' + 'clients send no Origin. Note that SvelteKit refuses form content types (`text/plain`, ' + diff --git a/src/lib/openapi/shared.ts b/src/lib/openapi/shared.ts index bf06080e..6c9be7f4 100644 --- a/src/lib/openapi/shared.ts +++ b/src/lib/openapi/shared.ts @@ -49,7 +49,7 @@ export const UNAUTHORIZED = { * * Apply this only where `isServiceKeySurface` actually admits a key - the db * and storage prefixes, the auth agent's `/admin/*`, `/overview`, and - * `/analytics`. Everything else (hosting, the registry, `/chat`) stays + * `/analytics`. Everything else (the registry, `/chat`) stays * session-only, and saying otherwise in the reference would send people to * write code against a 401. */ diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index b1302f02..19ba663c 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -44,23 +44,6 @@ const SCHEMA_STATEMENTS = [ created_at integer DEFAULT (unixepoch() * 1000) NOT NULL )`, `CREATE INDEX IF NOT EXISTS chat_message_thread ON chat_message (project_id, client_key, created_at)`, - `CREATE TABLE IF NOT EXISTS app ( - subdomain text PRIMARY KEY NOT NULL, - project_id text NOT NULL, - app_name text NOT NULL, - created_at integer DEFAULT (unixepoch() * 1000) NOT NULL - )`, - `CREATE INDEX IF NOT EXISTS app_project ON app (project_id, app_name)`, - `CREATE TABLE IF NOT EXISTS deploy_token ( - id text PRIMARY KEY NOT NULL, - project_id text NOT NULL, - name text NOT NULL, - token_hash text NOT NULL, - created_at integer DEFAULT (unixepoch() * 1000) NOT NULL, - last_used_at integer - )`, - `CREATE INDEX IF NOT EXISTS deploy_token_project ON deploy_token (project_id)`, - `CREATE INDEX IF NOT EXISTS deploy_token_hash ON deploy_token (token_hash)`, `CREATE TABLE IF NOT EXISTS service_key ( id text PRIMARY KEY NOT NULL, project_id text NOT NULL, @@ -71,37 +54,7 @@ const SCHEMA_STATEMENTS = [ last_used_at integer )`, `CREATE INDEX IF NOT EXISTS service_key_project ON service_key (project_id)`, - `CREATE INDEX IF NOT EXISTS service_key_hash ON service_key (key_hash)`, - `CREATE TABLE IF NOT EXISTS github_installation ( - id integer PRIMARY KEY NOT NULL, - org_id text, - account_login text NOT NULL, - installed_by text NOT NULL, - created_at integer DEFAULT (unixepoch() * 1000) NOT NULL - )`, - `CREATE INDEX IF NOT EXISTS github_installation_org ON github_installation (org_id)`, - `CREATE TABLE IF NOT EXISTS github_connection ( - id text PRIMARY KEY NOT NULL, - project_id text NOT NULL, - app_name text NOT NULL, - installation_id integer NOT NULL, - repo_id integer NOT NULL, - repo_full_name text NOT NULL, - default_branch text NOT NULL, - mode text NOT NULL, - assets_dir text, - build_command text, - root_dir text, - production_branch text, - ignored_branches text, - workflow_path text, - package_manager text, - created_at integer DEFAULT (unixepoch() * 1000) NOT NULL, - last_event_at integer - )`, - `CREATE UNIQUE INDEX IF NOT EXISTS github_connection_app ON github_connection (project_id, app_name)`, - `CREATE INDEX IF NOT EXISTS github_connection_repo ON github_connection (repo_id)`, - `CREATE INDEX IF NOT EXISTS github_connection_installation ON github_connection (installation_id)` + `CREATE INDEX IF NOT EXISTS service_key_hash ON service_key (key_hash)` ]; /** @@ -115,12 +68,6 @@ const UPGRADE_STATEMENTS = [ `ALTER TABLE project ADD COLUMN parent_id text`, `ALTER TABLE project ADD COLUMN branch_name text`, `ALTER TABLE project ADD COLUMN org_id text`, - `ALTER TABLE github_connection ADD COLUMN build_command text`, - `ALTER TABLE github_connection ADD COLUMN root_dir text`, - `ALTER TABLE github_connection ADD COLUMN production_branch text`, - `ALTER TABLE github_connection ADD COLUMN ignored_branches text`, - `ALTER TABLE github_connection ADD COLUMN workflow_path text`, - `ALTER TABLE github_connection ADD COLUMN package_manager text`, `CREATE INDEX IF NOT EXISTS project_parent ON project (parent_id)`, `CREATE INDEX IF NOT EXISTS project_org ON project (org_id)` ]; diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index aaf1bd91..ab1c96c2 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -1,12 +1,5 @@ import { sql } from 'drizzle-orm'; -import { - index, - integer, - primaryKey, - sqliteTable, - text, - uniqueIndex -} from 'drizzle-orm/sqlite-core'; +import { index, integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core'; /** * Control-plane schema, held in D1 on the dashboard Worker. @@ -71,68 +64,13 @@ export const demoProject = sqliteTable( (table) => [index('demo_project_created_at').on(table.createdAt)] ); -/** - * Hosting subdomain claims (Phase B). The - * dispatch namespace is global, so claims are control-plane state - no agent - * may own the namespace without every project depending on that one instance. - * One row per project+app: `project_id` is the FULL registry id (a branch is - * its own registry row, so it is its own claim row), and `subdomain` is what - * was ACTUALLY claimed under the auto-numbering rule - persisted on first - * claim and reused verbatim, never re-derived, so URLs stay stable when - * neighboring claims appear or are released. - */ -export const app = sqliteTable( - 'app', - { - /** The claimed subdomain of cfbase.dev; also the dispatch script name. */ - subdomain: text('subdomain').primaryKey(), - projectId: text('project_id').notNull(), - /** The operator-chosen app name the subdomain was derived from. */ - appName: text('app_name').notNull(), - createdAt: integer('created_at', { mode: 'timestamp_ms' }) - .notNull() - .default(sql`(unixepoch() * 1000)`) - }, - (table) => [index('app_project').on(table.projectId, table.appName)] -); - -export type AppRow = typeof app.$inferSelect; - -/** - * Project-scoped deploy tokens (Phase B) - - * the durable credential CI deploys ride, minted on ROOT projects and valid - * for the root and its branches. Only the SHA-256 digest is stored, so a - * control-plane leak never yields a working credential; the guard accepts - * the `cfbd_` bearer solely on the deploy and branch-create endpoints. - */ -export const deployToken = sqliteTable( - 'deploy_token', - { - id: text('id').primaryKey(), - projectId: text('project_id').notNull(), - name: text('name').notNull(), - tokenHash: text('token_hash').notNull(), - createdAt: integer('created_at', { mode: 'timestamp_ms' }) - .notNull() - .default(sql`(unixepoch() * 1000)`), - lastUsedAt: integer('last_used_at', { mode: 'timestamp_ms' }) - }, - (table) => [ - index('deploy_token_project').on(table.projectId), - index('deploy_token_hash').on(table.tokenHash) - ] -); - -export type DeployTokenRow = typeof deployToken.$inferSelect; - /** * Project service keys - the credential a SERVER * can hold, for the cases with no user to relay: crons, queue consumers, - * webhook handlers, seed scripts, and backends we do not host. + * webhook handlers, seed scripts, and backends. * - * Scoped to ONE registry row, deliberately NOT to the root-and-branches family - * deploy tokens use: for data the branch IS the isolation boundary, and a - * preview key that reached production rows would make branches a lie. + * Scoped to ONE registry row, deliberately NOT to a root-and-branches family: + * for data the branch IS the isolation boundary, and a preview key that reached production rows would make branches a lie. * * Only the SHA-256 digest is stored, so a control-plane leak yields no working * credential, and the secret is unrecoverable after the one-time reveal. @@ -159,108 +97,6 @@ export const serviceKey = sqliteTable( export type ServiceKeyRow = typeof serviceKey.$inferSelect; -/** - * GitHub App installations, bound to the console org that installed them. - * - * The binding is what stops one operator connecting another account's repo: - * `installation_id` arrives from GitHub on a redirect the operator controls, - * so it is only trustworthy at the moment the signed install `state` comes - * back. The callback records the binding once; every later connect checks it - * instead of trusting the id again. Null `org_id` matches a null-org project - * (legacy/self-hosted rows, already visible to any operator). - */ -export const githubInstallation = sqliteTable( - 'github_installation', - { - /** GitHub's numeric installation id - the unit access tokens are minted for. */ - id: integer('id').primaryKey(), - orgId: text('org_id'), - /** The GitHub account the App is installed on (`acme`), for display. */ - accountLogin: text('account_login').notNull(), - /** Console user id that completed the install - audit, and the fallback - * owner when the project carries no org. */ - installedBy: text('installed_by').notNull(), - createdAt: integer('created_at', { mode: 'timestamp_ms' }) - .notNull() - .default(sql`(unixepoch() * 1000)`) - }, - (table) => [index('github_installation_org').on(table.orgId)] -); - -export type GithubInstallationRow = typeof githubInstallation.$inferSelect; - -/** - * A repository connected to one project+app - the push-to-deploy link. - * - * Minted on a ROOT project and valid for its branches, exactly like a deploy - * token: a push to the default branch deploys the root, any other branch - * deploys `--`. Two modes, chosen per connection: - * - * - `build` writes `.github/workflows/cloudflarebase.yml` into the repo and - * trusts the Actions OIDC token it presents. GitHub's runners do the build; - * we run no build farm and hold no repo secret. - * - `direct` needs no runner and no file in the repo at all: the push webhook - * hands the pushed tree straight to the hosting agent. Only viable when - * there is nothing to build, which is why `assets_dir` is captured here. - * - * `repo_id` is the numeric id, not the name: repositories get renamed and the - * webhook must still find its connection. `repo_full_name` is what the OIDC - * `repository` claim is matched against, and is re-synced from webhook - * payloads so a rename cannot silently break build-mode trust. - */ -export const githubConnection = sqliteTable( - 'github_connection', - { - id: text('id').primaryKey(), - /** The ROOT project; branches ride the same connection. */ - projectId: text('project_id').notNull(), - appName: text('app_name').notNull(), - installationId: integer('installation_id').notNull(), - repoId: integer('repo_id').notNull(), - repoFullName: text('repo_full_name').notNull(), - defaultBranch: text('default_branch').notNull(), - mode: text('mode').$type<'build' | 'direct'>().notNull(), - /** Direct mode: repo-relative directory published as assets ('' = root). - * Build mode: where the build lands (null = CLI autodetects). */ - assetsDir: text('assets_dir'), - /** Build mode: the workflow's build command (framework preset or - * operator-edited); null = the generic `npm run build --if-present`. */ - buildCommand: text('build_command'), - /** Build mode: monorepo root - install/build/deploy run here. - * Null = repository root. */ - rootDir: text('root_dir'), - /** Branch that deploys the ROOT project; null = the repo's default - * branch. Any other branch still deploys `--`. */ - productionBranch: text('production_branch'), - /** JSON array of branch names and simple `*` globs; pushes to a matching - * branch never deploy (webhook skip, workflow branches-ignore, and OIDC - * grant refusal - all three enforce it). */ - ignoredBranches: text('ignored_branches'), - /** Workflow file this connection committed (per-app names fix the - * two-apps-one-repo collision); null = the legacy - * `.github/workflows/cloudflarebase.yml`. */ - workflowPath: text('workflow_path'), - /** Package manager detected at connect (npm/pnpm/yarn/bun), persisted so - * a build-settings edit can re-render the workflow's install steps. - * Null (legacy rows) = generic npm steps. */ - packageManager: text('package_manager'), - createdAt: integer('created_at', { mode: 'timestamp_ms' }) - .notNull() - .default(sql`(unixepoch() * 1000)`), - /** Last push we accepted - the Hosting page's "last deploy from GitHub". */ - lastEventAt: integer('last_event_at', { mode: 'timestamp_ms' }) - }, - (table) => [ - // One repo per app. Not unique on repo_id: the same repository legitimately - // deploys to several projects (a monorepo, or prod and a scratch project). - uniqueIndex('github_connection_app').on(table.projectId, table.appName), - index('github_connection_repo').on(table.repoId), - index('github_connection_installation').on(table.installationId) - ] -); - -export type GithubConnectionRow = typeof githubConnection.$inferSelect; - /** * Which agents a project has enabled. Groundwork from the agent contract: v1 * default-enables every registry agent and offers no opt-out UI, and deletion diff --git a/src/lib/server/frameworks.ts b/src/lib/server/frameworks.ts deleted file mode 100644 index f32b71d7..00000000 --- a/src/lib/server/frameworks.ts +++ /dev/null @@ -1,314 +0,0 @@ -/** - * Framework presets for GitHub connections (* Phase B) - the CF-Pages-style table that turns "we read your package.json" - * into a populated build command and output directory at connect time. - * - * Deliberately a pure module with zero imports so `npm run test:unit` can pin - * the detection matrix without booting anything. The connect dialog shows what - * was detected and the operator can always override; detection only decides - * the PREFILL, never the contract. - * - * Two failure philosophies encoded here: - * - A framework that needs a Cloudflare adapter it does not have gets a NOTE, - * not a silent generic preset - the build would succeed and deploy the - * wrong thing (or nothing), which is worse than saying so up front. - * - A preset whose output directory we know is stated EXPLICITLY even when - * the deploy would fail without it (Next.js without `output: 'export'`), - * because a loud "out does not exist" beats autodetection finding the - * repo's `public/` SOURCE directory and publishing that as the site. - */ - -export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'; - -export interface RepoFacts { - /** dependencies + devDependencies, merged (version ranges unused). */ - dependencies: Record; - scripts: Record; - packageManager: PackageManager; - /** File and directory names at the repository root. */ - rootEntries: string[]; - /** wrangler.jsonc / wrangler.json / wrangler.toml at the root. */ - hasWranglerConfig: boolean; - /** next.config.* source, fetched only when `next` is a dependency. */ - nextConfigSource: string | null; -} - -export interface FrameworkPreset { - id: string; - label: string; - /** 'build' needs a runner; 'direct' is deployable as committed. */ - mode: 'build' | 'direct'; - /** Null = no runnable build (direct mode, or a missing script). */ - buildCommand: string | null; - /** Null = wrangler-driven or autodetected at deploy time. */ - outputDir: string | null; - /** Guidance the dialog surfaces - adapter missing, path caveats. */ - note: string | null; - /** Set when the repo NEEDS a wrangler config it does not have and we - * know exactly what it should say - connect commits it, nobody types it. */ - wrangler: WranglerTemplate | null; -} - -export interface WranglerTemplate { - main: string; - assetsDirectory: string; - compatibilityFlags: string[]; -} - -/** - * The wrangler configs connect can commit on the operator's behalf. Only - * frameworks whose adapter has a FIXED output contract belong here - the - * server generates the file from this map and never from client content. - */ -export const WRANGLER_TEMPLATES = { - 'sveltekit-cloudflare': { - main: '.svelte-kit/cloudflare/_worker.js', - assetsDirectory: '.svelte-kit/cloudflare', - compatibilityFlags: ['nodejs_als'] - }, - 'astro-cloudflare': { - main: 'dist/_worker.js/index.js', - assetsDirectory: 'dist', - compatibilityFlags: ['nodejs_compat'] - }, - 'nextjs-opennext': { - main: '.open-next/worker.js', - assetsDirectory: '.open-next/assets', - compatibilityFlags: ['nodejs_compat'] - } -} as const satisfies Record; - -export type WranglerTemplateId = keyof typeof WRANGLER_TEMPLATES; - -/** Matches the hosting agent's default compatibility date. */ -const TEMPLATE_COMPATIBILITY_DATE = '2026-07-10'; - -/** The wrangler.jsonc connect commits. The CLI reads main, assets.directory, - * and the compatibility settings out of it at deploy time. */ -export function wranglerConfigJsonc(id: WranglerTemplateId, appName: string): string { - const template = WRANGLER_TEMPLATES[id]; - const flags = template.compatibilityFlags.map((flag) => `"${flag}"`).join(', '); - return `// Generated by Cloudflarebase when this repository was connected. -// The build output declared here is what deploys; edit freely - -// reconnecting never overwrites this file. -{ - "name": "${appName}", - "main": "${template.main}", - "compatibility_date": "${TEMPLATE_COMPATIBILITY_DATE}", - "compatibility_flags": [${flags}], - "assets": { - "directory": "${template.assetsDirectory}", - "binding": "ASSETS" - } -} -`; -} - -/** Lockfile first, `packageManager` field as the tiebreak - a repo with a - * pnpm lockfile is a pnpm repo no matter what the field says is newest. */ -export function detectPackageManager( - rootEntries: string[], - packageManagerField: string | null | undefined -): PackageManager { - const has = (name: string): boolean => rootEntries.includes(name); - if (has('pnpm-lock.yaml')) return 'pnpm'; - if (has('bun.lockb') || has('bun.lock')) return 'bun'; - if (has('yarn.lock')) return 'yarn'; - if (has('package-lock.json') || has('npm-shrinkwrap.json')) return 'npm'; - const field = packageManagerField?.split('@')[0]; - if (field === 'pnpm' || field === 'yarn' || field === 'bun') return field; - return 'npm'; -} - -const WRANGLER_CONFIGS = ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml']; - -export function hasWranglerConfig(rootEntries: string[]): boolean { - return WRANGLER_CONFIGS.some((name) => rootEntries.includes(name)); -} - -/** ` run - - - Hosting · {data.projectId} · Cloudflarebase - - - -
-
- -
-

Hosting

-

- Apps and functions on Workers for Platforms - one deploy, served at your subdomain. -

-
-
- - {#if data.demo} - - - - - Hosting needs a real project - - - Demo projects are throwaway and cannot deploy apps. Sign in and create a project - then - cloudflarebase init connects a repository and every - deploy ships to <app>.cfbase.dev. - - - - {:else if overview} - {#if !overview.configured} - - - Hosting is not configured - - This install has no Workers for Platforms dispatch namespace. Deploys are refused with a - 503 until DISPATCH, - DISPATCH_NAMESPACE, - CF_ACCOUNT_ID, and - CF_HOSTING_API_TOKEN are set on the hosting worker. - - - - {/if} - - - - - Apps - - An app is one Worker in the dispatch namespace: static assets, server code, or both. The - subdomain shown is what was actually claimed - taken names auto-number instead of failing. - - - - {#if apps.length === 0} -
-

- No apps yet -

-

- In your app's repository: - npx @cloudflarebase/cli init - then - npx @cloudflarebase/cli deploy -

-
- {:else} -
- {#each apps as app (app.name)} - -
-
- -
- -
- {#if app.deployCount === 0} -

Deploys on the next push

- {:else} -

{app.deployCount} deploy{app.deployCount === 1 ? '' : 's'}

- {#if app.lastDeployAt} -

last {timeAgo(app.lastDeployAt)}

- {/if} - {/if} -
- -
- {/each} -
- {/if} -
-
- - - - - Deploys - - - - - - - {#if data.isRoot} - - - - - Deploy access - - - How a deploy authenticates. Pick one - you do not need both. - - - - - - GitHub - Deploy token - - - -

- Optional. CI's durable credential, for pipelines GitHub does not run: valid only for - deploys and branch creation on this project and its branches. Secrets are shown once - and stored hashed - revoking deletes the hash. -

- {#if data.tokens.length} -
- {#each data.tokens as token (token.id)} -
-
-

{token.name}

-

- minted {timeAgo(token.createdAt)}{token.lastUsedAt - ? ` · last used ${timeAgo(token.lastUsedAt)}` - : ' · never used'} -

-
- -
- {/each} -
- {:else} -

No tokens minted.

- {/if} - -
- - -

- Push-to-deploy without a build farm, and without a credential you have to store. The - default branch ships production; every other branch gets its own isolated preview at <app>-<branch>. -

- {#if data.github.configured} - {#if data.github.connections.length} - -
-
- {#each data.github.connections as connection (connection.appName)} -
- - - - {connection.repoFullName} - -
-

- deploys {connection.appName} - - on push to - {connection.productionBranch ?? connection.defaultBranch} -

-

- {connection.mode === 'direct' - ? 'No build step - published directly' - : "Builds on GitHub's runners, authenticated by identity token"}{connection.lastEventAt - ? ` · last push ${timeAgo(connection.lastEventAt)}` - : ''} -

-
- -
- {/each} -
- -
- {:else} -
-

- Connect a repository and every push deploys itself. A site with no build step - needs no workflow file at all; one that builds gets a workflow that - authenticates with GitHub's identity token instead of a stored secret. -

- -
- {/if} - {:else if !githubToken} -
-
- - -
- -
- {#if githubError} -

{githubError}

- {/if} - {:else} -
    -
  1. -

    - 1. Add the deploy token as a repository secret named - {DEPLOY_TOKEN_SECRET_NAME} -

    -
    - {githubToken} - - -
    -

    - Shown once - it is stored hashed. Revoke it any time above. -

    -
  2. -
  3. -

    - 2. Commit the workflow as - {WORKFLOW_FILENAME} -

    -
    - - -
    -
    {deployWorkflowYaml()}
    -
  4. -
  5. -

    3. Push

    -

    - Every commit deploys automatically from then on - Workers-Builds-style, with - GitHub running the build. Make sure the repo carries the - cloudflarebase.json written by - cloudflarebase init. -

    -
  6. -
- {/if} -
-
-
-
- {/if} - {/if} -
- - claim.appName)} - {preferInstallation} -/> - - - { - if (!open) disconnectTarget = null; - }} -> - - - Disconnect {disconnectTarget?.repoFullName}? - - Pushes stop deploying immediately. - {#if disconnectTarget?.mode === 'build'} - The workflow file this connection added is removed from the repository too. - {/if} - Everything already deployed keeps serving. - - - - Cancel - - {disconnectBusy ? 'Disconnecting…' : 'Disconnect'} - - - - - - - - - - Mint a deploy token - - Valid for deploys and branch creation on this project family. Stored hashed - the secret - below is shown exactly once. - - - {#if minted} -
-

- Token {minted.name}: -

-
- {minted.token} - -
- -
- {:else} -
-
- - -
- {#if mintError} -

{mintError}

- {/if} - - - -
- {/if} -
-
- - - !open && (revokeTarget = null)} -> - - - Revoke {revokeTarget?.name}? - - Deploys using this token stop working immediately. This cannot be undone - mint a new token - to reconnect CI. - - - - Cancel - - {revokeBusy ? 'Revoking…' : 'Revoke'} - - - - diff --git a/src/routes/(app)/dashboard/[projectId]/hosting/apps/[appName]/[[tab=hostingapp]]/+page.server.ts b/src/routes/(app)/dashboard/[projectId]/hosting/apps/[appName]/[[tab=hostingapp]]/+page.server.ts deleted file mode 100644 index 0c49f726..00000000 --- a/src/routes/(app)/dashboard/[projectId]/hosting/apps/[appName]/[[tab=hostingapp]]/+page.server.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { - hostingBuildEnvSchema, - hostingOverviewSchema, - hostingSecretListSchema, - hostingVarListSchema -} from '$lib/agents'; -import { isDemoProjectId } from '$lib/console'; -import { - agentSegment, - agentUrl, - assertProjectId, - requireAgent, - serverError -} from '$lib/server/agents'; -import { getConnection } from '$lib/server/github-connect'; -import { appNameSchema, listAppClaims } from '$lib/server/hosting'; -import { getBranchContext } from '$lib/server/registry'; -import { error, redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; - -/** - * The per-app page (the CF Workers dashboard shape): Overview, Deployments, - * Analytics, Settings as URL tabs. One load serves them all - the pieces are - * small, and tab switches are client-side navigations to sibling routes. - * Analytics data is client-fetched (range changes should not re-run this). - */ -export const load: PageServerLoad = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - if (isDemoProjectId(projectId)) { - // No demo hosting - the hub shows the upsell. - redirect(303, `/dashboard/${projectId}/hosting`); - } - if (!appNameSchema.safeParse(params.appName).success) { - error(404, 'no such app'); - } - const appName = params.appName; - - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const fetchAgent = async (subPath: string) => { - const response = await agent.fetch(agentUrl(url.origin, entry, projectId, subPath)); - if (!response.ok) serverError(502, 'the hosting agent is unavailable'); - return response.json(); - }; - - const overview = hostingOverviewSchema.safeParse(await fetchAgent('/overview')); - if (!overview.success) serverError(502, 'the hosting agent is unavailable'); - - // The agent only learns an app at first deploy; a claim-only app (a - // connected repository that has not pushed yet) still gets a page. - const deployed = overview.data.apps.find((app) => app.name === appName) ?? null; - const claim = deployed - ? null - : ((await listAppClaims(platform, projectId)).find((row) => row.appName === appName) ?? null); - if (!deployed && !claim) error(404, 'no such app'); - const app = deployed ?? { - name: appName, - subdomain: claim!.subdomain, - url: null, - deployCount: 0, - lastDeployAt: null, - createdAt: claim!.createdAt - }; - - const segment = agentSegment(appName); - const vars = hostingVarListSchema.safeParse(await fetchAgent(`/apps/${segment}/vars`)); - const secrets = hostingSecretListSchema.safeParse(await fetchAgent(`/apps/${segment}/secrets`)); - - // Connections live on ROOT projects only; a branch page simply has no - // build section (its build env is the root connection's). - const context = await getBranchContext(platform, projectId); - const isRoot = !context || context.current === null; - const connection = isRoot ? await getConnection(platform, projectId, appName) : null; - const buildEnv = connection - ? hostingBuildEnvSchema.safeParse(await fetchAgent(`/apps/${segment}/build-env`)) - : null; - - return { - projectId, - appName, - tab: params.tab ?? 'overview', - app, - configured: overview.data.configured, - stub: overview.data.stub, - isRoot, - connection, - vars: vars.success ? vars.data.vars : [], - secrets: secrets.success ? secrets.data.secrets : [], - buildEnv: buildEnv?.success ? buildEnv.data : null - }; -}; diff --git a/src/routes/(app)/dashboard/[projectId]/hosting/apps/[appName]/[[tab=hostingapp]]/+page.svelte b/src/routes/(app)/dashboard/[projectId]/hosting/apps/[appName]/[[tab=hostingapp]]/+page.svelte deleted file mode 100644 index 9fc2fde7..00000000 --- a/src/routes/(app)/dashboard/[projectId]/hosting/apps/[appName]/[[tab=hostingapp]]/+page.svelte +++ /dev/null @@ -1,706 +0,0 @@ - - - - {data.appName} · Hosting · {data.projectId} · Cloudflarebase - - - -
-
- - All apps - -
- -
-

{data.appName}

- {#if data.app.url} - - - {data.app.url.replace('https://', '')} - - - - {:else} -

{data.app.subdomain}

- {/if} -
- {#if data.app.deployCount === 0} - Awaiting first deploy - {/if} -
- -
- - {#if data.tab === 'overview'} -
- - - Deploys - {data.app.deployCount} - - - - - Last deploy - - {data.app.lastDeployAt ? timeAgo(data.app.lastDeployAt) : '—'} - - - - - - Source - - {#if data.connection} - - - {data.connection.repoFullName} - - {:else} - CLI deploys - {/if} - - - -
- - {#if data.connection} - - - Push to deploy - - Pushes to - {data.connection.productionBranch ?? data.connection.defaultBranch} - deploy production; every other branch gets an isolated preview at - <app>-<branch>. - {#if data.connection.ignoredBranches.length} - Ignored: {data.connection.ignoredBranches.join(', ')}. - {/if} - - - - {/if} - - - - Latest deploys - - - - - - {:else if data.tab === 'deployments'} - - - Deployments - Every deploy of this app, newest first. - - - - - - {:else if data.tab === 'analytics'} - - -
- Requests - - Requests and 5xx errors served at - {data.app.subdomain}. - -
- - - - {analyticsRangeOptions.find((option) => option.value === analyticsRange)?.label} - - - {#each analyticsRangeOptions as option (option.value)} - - {/each} - - - -
- - {#if !analytics} - - {:else if ['connected', 'local'].includes(analytics.engine.status)} -
-
-

Requests

-

{analytics.totals.requests.toLocaleString()}

-
-
-

Errors (5xx)

-

{analytics.totals.errors.toLocaleString()}

-
-
-

Avg duration

-

{Math.round(analytics.totals.avgDurationMs)} ms

-
-
- - point.date) : 6, - format: (value: Date) => - value.toLocaleDateString( - undefined, - analyticsRange === '7' - ? { weekday: 'short' } - : { month: 'short', day: 'numeric' } - ) - } - }} - > - {#snippet marks()} - - - - {/snippet} - {#snippet tooltip()} - - value instanceof Date - ? value.toLocaleDateString(undefined, { - weekday: 'long', - month: 'long', - day: 'numeric' - }) - : String(value)} - /> - {/snippet} - - - {:else} -
- {analytics.engine.status === 'write-only' - ? 'Requests are being recorded. Add Analytics Engine read credentials (CF_ACCOUNT_ID + CF_ANALYTICS_API_TOKEN) to visualize them.' - : 'Analytics Engine reads are temporarily unavailable.'} -
- {/if} -
-
- {:else if data.tab === 'settings'} - - - Variables and secrets - - The app's environment, at runtime and at build time. Text values upload as plain-text - bindings on every deploy (they win over CLI-declared vars of the same name) and apply to - the live script when saved; secrets are written through to Cloudflare and survive - redeploys - their values are never shown again. GitHub builds export this whole set before - the build step, so framework-inlined values are present too. - - - - 0} - secretsDisabledReason="Secrets attach to the deployed script - deploy once first." - save={saveRuntimeEnv} - /> - - - - {#if data.connection} - - - Build environment - - Build-only overrides. Variables and secrets above are already exported into the GitHub - Actions build; anything set here exists only at build time and wins over a matching - name. Secrets are encrypted at rest and fetched by the workflow with its OIDC identity; - branch builds share this set. - - - - - - - - - - Build settings - - {#if data.connection.mode === 'build'} - Saving updates the workflow file in - {data.connection.repoFullName} - the committed workflow - is where these settings live. - {:else} - This connection deploys the pushed tree directly - no build step. Reconnect the - repository to change the mode. - {/if} - - - -
- {#if data.connection.mode === 'build'} -
- - -
-
- - -
- {/if} -
- - -
-
- - -

- Deploys the root project; every other branch deploys its own preview. -

-
-
- - -

- Comma-separated names or * globs; pushes to these never - deploy. -

-
-
-
- - {#if settingsError} - {settingsError} - {/if} - {#if settingsNotice} - {settingsNotice} - {/if} -
-
-
- {/if} - - - - Danger zone - - Deleting stops {data.app.subdomain} serving, erases its - deploy history and environment, and releases the subdomain for anyone to claim. - - - - - - - {/if} -
- - - - - - Delete {data.appName}? - - The deployed site at {data.app.subdomain} stops serving, - its deploy history is erased, and the subdomain is released for anyone to claim. A connected repository - is disconnected too. This cannot be undone. - - -
- - - {#if deleteError} -

{deleteError}

- {/if} -
- - - - -
-
diff --git a/src/routes/(app)/dashboard/[projectId]/hosting/connect-github-dialog.svelte b/src/routes/(app)/dashboard/[projectId]/hosting/connect-github-dialog.svelte deleted file mode 100644 index af997ceb..00000000 --- a/src/routes/(app)/dashboard/[projectId]/hosting/connect-github-dialog.svelte +++ /dev/null @@ -1,497 +0,0 @@ - - - - - - - Connect a repository - - - Push to deploy. The default branch ships production; every other branch gets its own - isolated preview. - - - - {#if !installations.length} -
-

- Install the Cloudflarebase app on your GitHub account and pick the repositories it may - reach. You can change that selection on GitHub at any time. -

- -
- {:else} -
-
- - (installationId = value ? Number(value) : null)} - > - - {accountLabel} - - - {#each installations as installation (installation.id)} - - {installation.accountLogin} - - {/each} - - -
- -
-
- - -
-
- {#if reposLoading} -

Loading repositories…

- {:else if !filtered.length} -

- {repos.length ? 'No repository matches that search.' : 'No repositories available.'} -

- {:else} - {#each filtered as repo (repo.id)} - - {/each} - {/if} -
- -
- - {#if selected} -
- {#if inspecting} -

- Inspecting {selected.fullName}… -

- {:else} -
- - - {#if takenApps.includes(appName.trim())} -

- Reuses the existing app {appName.trim()}. -

- {/if} -
- - {#if framework} -

- Detected {framework.label} - {#if rootDir}in {rootDir}{/if} -

- {/if} - {#if mode === 'build'} -
- - selected && inspect(selected, rootDir.trim())} - /> - {#if rootDirMissing} -

- {rootDir.trim()} does not exist on - {selected.defaultBranch}. -

- {/if} -
-
-
- - -
-
- - -
-
- {/if} - {#if mode === 'build' && writesWrangler} -

- A wrangler.jsonc pointing at the adapter's output is - committed with the workflow - there is nothing to write by hand. -

- {/if} - {#if framework?.note} -

- {framework.note} -

- {/if} - -

{summary}

- - {#if !showSettings} - - {:else} - -
- - -
-
- - -
-
- {#if mode === 'direct'} -
- - -
- {/if} - {/if} - {/if} -
- {/if} - - {#if error} -

{error}

- {/if} -
- - - - - - {/if} -
-
diff --git a/src/routes/(app)/dashboard/[projectId]/hosting/deploys-table.svelte b/src/routes/(app)/dashboard/[projectId]/hosting/deploys-table.svelte deleted file mode 100644 index 8786abc6..00000000 --- a/src/routes/(app)/dashboard/[projectId]/hosting/deploys-table.svelte +++ /dev/null @@ -1,125 +0,0 @@ - - -{#if deploys.length === 0} -

No deploys yet.

-{:else} -
- {#each deploys as deploy (deploy.id)} -
- - {deploy.status} - -
-

- {showApp ? `${deploy.appName} · ` : ''}{deploy.subdomain} -

-

- {deploy.hasWorker ? 'worker + ' : ''}{deploy.assetCount} asset{deploy.assetCount === 1 - ? '' - : 's'} · {kb(deploy.assetBytes + deploy.moduleBytes)} -

-
-

{timeAgo(deploy.createdAt)}

-
- {/each} -
-
- - {pageStart + 1}–{pageStart + deploys.length} of {total} - -
- - -
-
-{/if} diff --git a/src/routes/(app)/dashboard/[projectId]/hosting/vars-editor.svelte b/src/routes/(app)/dashboard/[projectId]/hosting/vars-editor.svelte deleted file mode 100644 index 7c0fd821..00000000 --- a/src/routes/(app)/dashboard/[projectId]/hosting/vars-editor.svelte +++ /dev/null @@ -1,256 +0,0 @@ - - - - -
- {#if rows.length === 0} -

- No variables yet. Text values upload as plain-text bindings; secrets are encrypted and never - shown again. -

- {:else} -
- {#each rows as row, index (row.key)} -
- { - if (!value || row.storedSecret) return; - row.type = value as 'text' | 'secret'; - }} - disabled={row.storedSecret || (!secretsEnabled && row.type === 'text')} - > - - {row.type === 'secret' ? 'Secret' : 'Text'} - - - - - - - - - -
- {/each} -
- {/if} - - {#if !secretsEnabled && secretsDisabledReason} -

{secretsDisabledReason}

- {/if} - -
- - - {#if invalid && dirty} - {invalid} - {/if} - {#if error} - {error} - {/if} - {#if notice} - {notice} - {/if} -
-
diff --git a/src/routes/(app)/dashboard/[projectId]/settings/+page.svelte b/src/routes/(app)/dashboard/[projectId]/settings/+page.svelte index 3bd3ae7d..400d91d9 100644 --- a/src/routes/(app)/dashboard/[projectId]/settings/+page.svelte +++ b/src/routes/(app)/dashboard/[projectId]/settings/+page.svelte @@ -361,10 +361,10 @@ ? 'a branch' : 'a project'}. {:else if isBranch} - Deleting this branch erases its users, data, and deploys. The root project is + Deleting this branch erases its users, data, and files. The root project is untouched. {:else} - Deleting this project erases its users, data, and deploys in every agent - branches + Deleting this project erases its users, data, and files in every agent - branches included. {/if}

diff --git a/src/routes/(marketing)/+page.svelte b/src/routes/(marketing)/+page.svelte index 13302680..f08e46e5 100644 --- a/src/routes/(marketing)/+page.svelte +++ b/src/routes/(marketing)/+page.svelte @@ -27,7 +27,6 @@ Radio, Star, Boxes, - Zap, HardDrive, Minus } from '@lucide/svelte'; @@ -145,14 +144,12 @@ } ]; - // Every primitive here has shipped: Functions is hosting on Workers for - // Platforms, Realtime is the db gateway. Nothing on this page is a promise - + // Every primitive here has shipped: Realtime is the db gateway. Nothing on this page is a promise - // an unbuilt card is a liability the moment someone signs up for it. const roadmap = [ { icon: KeyRound, name: 'Auth', live: true }, { icon: Database, name: 'Database', live: true }, { icon: HardDrive, name: 'Storage', live: true }, - { icon: Zap, name: 'Functions', live: true }, { icon: Radio, name: 'Realtime', live: true } ]; @@ -167,7 +164,7 @@ }, { q: 'Can I run it on my own Cloudflare account?', - a: 'Yes - it is open source under Apache-2.0 at github.com/cloudflarebase/cloudflarebase. It is three Workers deployed in order with one command (npm run deploy:all), and the README walks through it for your own account. No secrets are required: each project generates its own signing key.' + a: 'Yes - it is open source under Apache-2.0 at github.com/cloudflarebase/cloudflarebase. It is four Workers deployed in order with one command (npm run deploy:all), and the README walks through it for your own account. No secrets are required: each project generates its own signing key.' }, { q: 'Is this production-ready?', diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index dfa191c8..ef4798b0 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -99,11 +99,11 @@ {/if} diff --git a/src/routes/api/github/callback/+server.ts b/src/routes/api/github/callback/+server.ts deleted file mode 100644 index 5971d9c4..00000000 --- a/src/routes/api/github/callback/+server.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - appJwt, - githubAppConfig, - githubFetch, - INSTALL_STATE_COOKIE, - verifyInstallState -} from '$lib/server/github'; -import { getConsoleSession } from '$lib/server/console'; -import { recordInstallation } from '$lib/server/github-connect'; -import { getProjectOwnership } from '$lib/server/registry'; -import { redirect } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; - -/** - * Where GitHub returns after an App install (* Phase B). - * - * This is the ONE moment an `installation_id` is trustworthy, and recording - * the binding here is what every later connect checks instead of trusting the - * id again. What makes it trustworthy is the SIGNED STATE, not a session: - * this arrives as a cross-site top-level navigation from github.com, and a - * session cookie is not reliably present on one - requiring it stranded - * operators mid-install with a bare 401. The state is minted only for a - * signed-in operator on a specific project, expires in minutes, and cannot - * be forged without the webhook secret. - * - * The residual risk is narrow and deliberate: someone holding a live state - * string could bind THEIR installation to that operator's project. They gain - * nothing by it - `installationCoversProject` still gates every use on org - * membership, so the installation becomes usable by the victim, not the - * attacker - and the window is minutes. The session is still cross-checked - * whenever the browser sends one. - * - * It always lands the operator somewhere useful; a failure is a message on - * the Hosting page, never a bare error. - */ -export const GET: RequestHandler = async ({ url, platform, request, cookies }) => { - const config = githubAppConfig(platform); - if (!config) redirect(303, '/dashboard'); - - // Query first, cookie second. GitHub does echo `state` on the install - // redirect, but the cookie keeps the return leg working if that ever - // changes or the App is configured differently. - const state = await verifyInstallState( - config, - url.searchParams.get('state') ?? cookies.get(INSTALL_STATE_COOKIE) ?? null - ); - cookies.delete(INSTALL_STATE_COOKIE, { path: '/' }); - const installationId = Number(url.searchParams.get('installation_id')); - if (!state || !Number.isSafeInteger(installationId) || installationId <= 0) { - redirect(303, '/dashboard?github=invalid'); - } - - // This is an open route, so the guard never resolved a session (the /login - // precedent). Resolve it ourselves: absent is expected and fine, but a - // session belonging to someone ELSE means the state was replayed in a - // different browser and must not be honoured. - const operator = await getConsoleSession( - platform, - url.origin, - request.headers.get('cookie'), - null, - request.headers.get('cf-connecting-ip') - ); - if (operator && operator.id !== state.userId) { - redirect(303, '/dashboard?github=invalid'); - } - - const hosting = `/dashboard/${state.projectId}/hosting`; - - // GitHub is the authority on which account was installed on - the operator - // picks it inside GitHub's own UI and never tells us. - const response = await githubFetch( - await appJwt(config), - 'GET', - `/app/installations/${installationId}` - ); - const account = (response.body as { account?: { login?: string } } | null)?.account?.login; - if (!response.ok || !account) { - redirect(303, `${hosting}?github=unreachable`); - } - - const ownership = await getProjectOwnership(platform, state.projectId); - await recordInstallation(platform, { - installationId, - orgId: ownership.orgId, - accountLogin: account, - userId: state.userId - }); - - redirect(303, `${hosting}?installation=${installationId}`); -}; diff --git a/src/routes/api/github/webhook/+server.ts b/src/routes/api/github/webhook/+server.ts deleted file mode 100644 index f255d17f..00000000 --- a/src/routes/api/github/webhook/+server.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { githubAppConfig, verifyWebhookSignature } from '$lib/server/github'; -import { forgetInstallation } from '$lib/server/github-connect'; -import { handlePush, type PushPayload } from '$lib/server/github-deploy'; -import type { RequestHandler } from './$types'; - -/** - * GitHub's webhook endpoint (Phase B). - * - * PUBLIC BY EXCEPTION, and the only route under `/api` that is: GitHub has no - * session and never will. The HMAC signature IS the authentication - it is - * checked before the payload is parsed, and an unsigned or wrongly signed - * request is a 401 that touches nothing. - * - * Answers 200 for anything it understands but does not act on, so GitHub's - * delivery log shows green for tag pushes and build-mode repositories rather - * than a wall of red the operator has to learn to ignore. - */ -export const POST: RequestHandler = async ({ request, url, platform }) => { - const config = githubAppConfig(platform); - if (!config) { - return Response.json({ error: 'no GitHub App is configured' }, { status: 404 }); - } - - // The RAW body, not re-serialized JSON: the digest is over exactly the - // bytes GitHub sent, and JSON.stringify would not reproduce them. - const raw = await request.text(); - const signed = await verifyWebhookSignature( - config.webhookSecret, - raw, - request.headers.get('x-hub-signature-256') - ); - if (!signed) { - return Response.json({ error: 'invalid signature' }, { status: 401 }); - } - - const event = request.headers.get('x-github-event') ?? ''; - let payload: Record; - try { - payload = JSON.parse(raw) as Record; - } catch { - return Response.json({ error: 'invalid payload' }, { status: 400 }); - } - - if (event === 'ping') return Response.json({ ok: true }); - - // An uninstalled app must stop deploying immediately - the connections are - // dead the moment the installation is, and leaving them would strand rows - // that can never mint a token again. - if (event === 'installation' && payload.action === 'deleted') { - const installationId = (payload.installation as { id?: number } | undefined)?.id; - if (installationId) await forgetInstallation(platform, installationId); - return Response.json({ ok: true }); - } - - if (event !== 'push') return Response.json({ ok: true, ignored: event }); - - const outcomes = await handlePush(platform, payload as PushPayload, url.origin); - return Response.json({ ok: true, outcomes }); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/+server.ts deleted file mode 100644 index 1c3f9efb..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/+server.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { isDemoProjectId } from '$lib/console'; -import { getDb } from '$lib/server/db'; -import { app } from '$lib/server/db/schema'; -import { assertProjectId, requireAgent } from '$lib/server/agents'; -import { disconnectRepository } from '$lib/server/github-connect'; -import { releaseAppClaim } from '$lib/server/hosting'; -import { and, eq } from 'drizzle-orm'; -import type { RequestHandler } from './$types'; - -/** - * Deletes an app: its deployed script and history in the agent, its GitHub - * connection (so the next push cannot quietly resurrect it), and finally its - * subdomain claim. Strictly in that order - the claim is released LAST, - * because a freed name whose script still serves would hand the subdomain to - * the next claimant while the old tenant's code answers on it. - * - * Operator sessions only (guard default; deploy tokens are deliberately not - * admitted here - a CI credential that can deploy must not be able to erase). - */ -export const DELETE: RequestHandler = async ({ params, platform }) => { - const projectId = assertProjectId(params.projectId); - if (isDemoProjectId(projectId)) { - return Response.json({ error: 'demo projects cannot deploy apps' }, { status: 403 }); - } - const appName = params.appName; - - // 404 before any side effect: deleting an app this project never claimed - // must not touch connections or the agent. - const db = await getDb(platform); - const [claim] = await db - .select() - .from(app) - .where(and(eq(app.projectId, projectId), eq(app.appName, appName))) - .limit(1); - if (!claim) { - return Response.json({ error: 'no such app' }, { status: 404 }); - } - - // Drop the connection first: a surviving one would re-register the app on - // the very next push. Best effort on the workflow file, like disconnect. - const disconnected = await disconnectRepository(platform, projectId, appName); - - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const erase = await agent.fetch( - `https://${entry.manifest.worker}/internal/projects/${encodeURIComponent(projectId)}/apps/${encodeURIComponent(appName)}`, - { method: 'DELETE' } - ); - if (!erase.ok) { - const body = (await erase.json().catch(() => null)) as { error?: string } | null; - return Response.json( - { error: body?.error ?? 'the hosting agent could not erase the app' }, - { status: 502 } - ); - } - - await releaseAppClaim(platform, projectId, appName); - return Response.json({ - deleted: true, - subdomain: claim.subdomain, - workflowRemoved: disconnected.workflowRemoved - }); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/analytics/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/analytics/+server.ts deleted file mode 100644 index e9bf9781..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/analytics/+server.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { - agentSegment, - agentUrl, - assertProjectId, - requireAgent, - toNativeResponse -} from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** Operator proxy: per-app request analytics (`?days=7|30|90`). */ -export const GET: RequestHandler = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${agentSegment(params.appName)}/analytics${url.search}` - ); - const response = await agent.fetch(target); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-env/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-env/+server.ts deleted file mode 100644 index f57819ac..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-env/+server.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { - agentSegment, - agentUrl, - assertProjectId, - requireAgent, - toNativeResponse -} from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** - * The build-env read, two personas: - * - * - **GitHub Actions OIDC** (`locals.githubDeploy`): the runner fetching the - * env it exports before the build step. Answers the DECRYPTED bundle over - * the agent's service-binding-only /internal route. The grant names exactly - * one connection, and that connection's app is the only build env the token - * may read - an ordinary 404 otherwise, indistinguishable from absence. - * Build env is connection-scoped: branch builds read the ROOT project's - * store (`grant.projectId` IS the root). - * - **Operator session** (the guard's default): the settings UI's read - - * vars with values, secret NAMES only, never a decrypted value. - */ -export const GET: RequestHandler = async ({ params, url, platform, locals }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - - const grant = locals.githubDeploy; - if (grant) { - if (grant.appName !== params.appName) { - return Response.json({ error: 'not found' }, { status: 404 }); - } - // Synthetic host: the internal route sits outside /agents/* on purpose, - // reachable only over the service binding (the erase-route precedent). - const response = await agent.fetch( - `https://${entry.manifest.worker}/internal/projects/${encodeURIComponent(grant.projectId)}/apps/${encodeURIComponent(grant.appName)}/build-env` - ); - return toNativeResponse(response as unknown as Response); - } - - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${agentSegment(params.appName)}/build-env` - ); - const response = await agent.fetch(target); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-secrets/[name]/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-secrets/[name]/+server.ts deleted file mode 100644 index f6062a76..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-secrets/[name]/+server.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { - agentSegment, - agentUrl, - assertProjectId, - requireAgent, - toNativeResponse -} from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** - * Operator proxy: one build secret. PUT encrypts the value at rest in the - * hosting agent (503 until the install sets HOSTING_MASTER_KEY); DELETE drops - * it. Values are write-only on this surface - the console never reads one - * back, and the runner's copy travels the OIDC build-env route instead. - */ - -const targetFor = ( - origin: string, - projectId: string, - appName: string, - name: string -): { entry: typeof AGENT_REGISTRY.hosting; target: string } => { - const entry = AGENT_REGISTRY.hosting; - return { - entry, - target: agentUrl( - origin, - entry, - projectId, - `/apps/${agentSegment(appName)}/build-secrets/${agentSegment(name)}` - ) - }; -}; - -export const PUT: RequestHandler = async ({ params, request, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const { entry, target } = targetFor(url.origin, projectId, params.appName, params.name); - const agent = requireAgent(platform, entry); - const response = await agent.fetch(target, { - method: 'PUT', - headers: { 'content-type': request.headers.get('content-type') ?? 'application/json' }, - body: await request.arrayBuffer() - }); - return toNativeResponse(response as unknown as Response); -}; - -export const DELETE: RequestHandler = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const { entry, target } = targetFor(url.origin, projectId, params.appName, params.name); - const agent = requireAgent(platform, entry); - const response = await agent.fetch(target, { method: 'DELETE' }); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-vars/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-vars/+server.ts deleted file mode 100644 index 3193a284..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/build-vars/+server.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { - agentSegment, - agentUrl, - assertProjectId, - requireAgent, - toNativeResponse -} from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** Operator proxy: replace an app's build-time vars (the whole set - absent - * names are deletions). The workflow fetches them before its build step. */ -export const PUT: RequestHandler = async ({ params, request, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${agentSegment(params.appName)}/build-vars` - ); - const response = await agent.fetch(target, { - method: 'PUT', - headers: { 'content-type': request.headers.get('content-type') ?? 'application/json' }, - body: await request.arrayBuffer() - }); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/deploys/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/deploys/+server.ts deleted file mode 100644 index b888d12d..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/deploys/+server.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { isDemoProjectId } from '$lib/console'; -import { agentUrl, assertProjectId, requireAgent, toNativeResponse } from '$lib/server/agents'; -import { resolveAppClaim } from '$lib/server/hosting'; -import type { RequestHandler } from './$types'; - -/** - * The deploy endpoint (Phase B). The console - * resolves the subdomain claim BEFORE proxying - claims are control-plane - * state, the agent owns no global namespace - then pushes the result to the - * agent's service-binding-only link route and forwards the multipart deploy. - * The agent only ever deploys to subdomains recorded for this project, which - * is what makes the operator surface safe. - * - * Reachable with an operator session (guard default) OR a deploy token (the - * guard admits `cfbd_` bearers on exactly this POST for the token's family). - */ -export const POST: RequestHandler = async ({ params, request, url, platform }) => { - const projectId = assertProjectId(params.projectId); - if (isDemoProjectId(projectId)) { - // No demo hosting: anonymous code execution is an abuse machine. The - // dashboard shows an upsell card instead. - return Response.json({ error: 'demo projects cannot deploy apps' }, { status: 403 }); - } - - // Reuse the persisted claim or mint one under the auto-numbering rule. - const claim = await resolveAppClaim(platform, projectId, params.appName); - if (!claim.ok) { - return Response.json({ error: claim.error }, { status: claim.status }); - } - - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - - // Synthetic host: the link route sits outside /agents/* on purpose, - // reachable only over the service binding (the erase-route precedent). - const push = await agent.fetch( - `https://${entry.manifest.worker}/internal/projects/${encodeURIComponent(projectId)}/apps/${encodeURIComponent(claim.appName)}`, - { - method: 'PUT', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ subdomain: claim.subdomain }) - } - ); - if (!push.ok) { - const body = (await push.json().catch(() => null)) as { error?: string } | null; - return Response.json( - { error: body?.error ?? 'the hosting agent refused the app' }, - { status: push.status } - ); - } - - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${encodeURIComponent(claim.appName)}/deploys` - ); - // Pass url + init (not a Request object): in dev the service binding is a - // miniflare proxy that can't consume Requests from the Node realm. Full - // headers so the multipart boundary survives. - const response = await agent.fetch(target, { - method: 'POST', - headers: [...request.headers], - body: await request.arrayBuffer() - }); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/secrets/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/secrets/+server.ts deleted file mode 100644 index fa835ebf..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/secrets/+server.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { - agentSegment, - agentUrl, - assertProjectId, - requireAgent, - toNativeResponse -} from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** Operator proxy: list an app's secret NAMES - the values live at Cloudflare - * and are unrecoverable by design. */ -export const GET: RequestHandler = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${agentSegment(params.appName)}/secrets` - ); - const response = await agent.fetch(target); - return toNativeResponse(response as unknown as Response); -}; - -/** Operator proxy: set one secret on a deployed app (`cloudflarebase secret put`). */ -export const POST: RequestHandler = async ({ params, request, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${agentSegment(params.appName)}/secrets` - ); - const response = await agent.fetch(target, { - method: 'POST', - headers: { 'content-type': request.headers.get('content-type') ?? 'application/json' }, - body: await request.arrayBuffer() - }); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/secrets/[name]/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/secrets/[name]/+server.ts deleted file mode 100644 index cc2ecebb..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/secrets/[name]/+server.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { - agentSegment, - agentUrl, - assertProjectId, - requireAgent, - toNativeResponse -} from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** Operator proxy: delete one secret from a deployed app. Idempotent - the - * agent removes the script binding (404-tolerant) and drops the name row. */ -export const DELETE: RequestHandler = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${agentSegment(params.appName)}/secrets/${agentSegment(params.name)}` - ); - const response = await agent.fetch(target, { method: 'DELETE' }); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/vars/+server.ts b/src/routes/api/projects/[projectId]/hosting/apps/[appName]/vars/+server.ts deleted file mode 100644 index 8b3eb3ee..00000000 --- a/src/routes/api/projects/[projectId]/hosting/apps/[appName]/vars/+server.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { - agentSegment, - agentUrl, - assertProjectId, - requireAgent, - toNativeResponse -} from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** - * Operator proxy: an app's stored runtime vars. GET lists; PUT replaces the - * whole set (the console form submits the full table, so absent names are - * deletions) and best-effort patches the live script. - */ - -export const GET: RequestHandler = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${agentSegment(params.appName)}/vars` - ); - const response = await agent.fetch(target); - return toNativeResponse(response as unknown as Response); -}; - -export const PUT: RequestHandler = async ({ params, request, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const target = agentUrl( - url.origin, - entry, - projectId, - `/apps/${agentSegment(params.appName)}/vars` - ); - const response = await agent.fetch(target, { - method: 'PUT', - headers: { 'content-type': request.headers.get('content-type') ?? 'application/json' }, - body: await request.arrayBuffer() - }); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/claims/+server.ts b/src/routes/api/projects/[projectId]/hosting/claims/+server.ts deleted file mode 100644 index f2cba3df..00000000 --- a/src/routes/api/projects/[projectId]/hosting/claims/+server.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { assertProjectId, requireAgent } from '$lib/server/agents'; -import { listAppClaims, resolveAppClaim } from '$lib/server/hosting'; -import { z } from 'zod'; -import type { RequestHandler } from './$types'; - -/** - * Subdomain claims, console-plane (the control plane owns the global - * namespace). GET lists this project's claims; POST resolves one - with - * `dry: true` it only reports what WOULD be claimed, which is how - * bare `cloudflarebase init` shows the auto-numbered suggestion before - * claiming. - */ - -const claimBodySchema = z.object({ - app: z.unknown(), - dry: z.boolean().optional() -}); - -export const GET: RequestHandler = async ({ params, platform }) => { - const projectId = assertProjectId(params.projectId); - return Response.json({ claims: await listAppClaims(platform, projectId) }); -}; - -export const POST: RequestHandler = async ({ params, request, platform }) => { - const projectId = assertProjectId(params.projectId); - const body = claimBodySchema.safeParse(await request.json().catch(() => null)); - if (!body.success) { - return Response.json({ error: 'expected { app, dry? }' }, { status: 400 }); - } - - const claim = await resolveAppClaim(platform, projectId, body.data.app, { - dry: body.data.dry - }); - if (!claim.ok) { - return Response.json({ error: claim.error }, { status: claim.status }); - } - - // A real claim is pushed to the agent right away so the app shows up on - // the Hosting page before its first deploy. Best-effort: the deploy route - // re-pushes, so an unreachable agent here costs nothing but the preview. - if (!body.data.dry) { - const entry = AGENT_REGISTRY.hosting; - try { - const agent = requireAgent(platform, entry); - await agent.fetch( - `https://${entry.manifest.worker}/internal/projects/${encodeURIComponent(projectId)}/apps/${encodeURIComponent(claim.appName)}`, - { - method: 'PUT', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ subdomain: claim.subdomain }) - } - ); - } catch { - // the claim row is persisted; the first deploy completes the link - } - } - - return Response.json( - { subdomain: claim.subdomain, appName: claim.appName, created: claim.created }, - { status: claim.created ? 201 : 200 } - ); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/deploys/+server.ts b/src/routes/api/projects/[projectId]/hosting/deploys/+server.ts deleted file mode 100644 index 87f7dbf0..00000000 --- a/src/routes/api/projects/[projectId]/hosting/deploys/+server.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { agentUrl, assertProjectId, requireAgent, toNativeResponse } from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** Operator proxy: keyset-paged deploy history (?app=&cursor=&limit=). */ -export const GET: RequestHandler = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const response = await agent.fetch( - agentUrl(url.origin, entry, projectId, `/deploys${url.search}`) - ); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/github/+server.ts b/src/routes/api/projects/[projectId]/hosting/github/+server.ts deleted file mode 100644 index 7c9fa0c5..00000000 --- a/src/routes/api/projects/[projectId]/hosting/github/+server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { assertProjectId } from '$lib/server/agents'; -import { githubAppConfig } from '$lib/server/github'; -import { listConnections, listInstallationsForOrg } from '$lib/server/github-connect'; -import { getProjectOwnership } from '$lib/server/registry'; -import type { RequestHandler } from './$types'; - -/** - * GitHub push-to-deploy state for the Hosting page (* Phase B). `configured: false` is the self-hosted default - no App - * credentials, so the page offers the manual deploy-token flow instead. - */ -export const GET: RequestHandler = async ({ params, platform }) => { - const projectId = assertProjectId(params.projectId); - const config = githubAppConfig(platform); - if (!config) { - return Response.json({ configured: false, installations: [], connections: [] }); - } - - // Installations are offered per owning org, so a teammate's install is - // reusable and a stranger's is invisible. - const ownership = await getProjectOwnership(platform, projectId); - return Response.json({ - configured: true, - installations: await listInstallationsForOrg(platform, ownership.orgId), - connections: await listConnections(platform, projectId) - }); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/github/connections/+server.ts b/src/routes/api/projects/[projectId]/hosting/github/connections/+server.ts deleted file mode 100644 index f46bdd73..00000000 --- a/src/routes/api/projects/[projectId]/hosting/github/connections/+server.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { assertProjectId } from '$lib/server/agents'; -import { connectRepository, listConnections } from '$lib/server/github-connect'; -import type { RequestHandler } from './$types'; - -/** - * Repository connections, console-plane (* Phase B). Made on ROOT projects; a push to a git branch deploys - * `--` through the same connection. - */ - -export const GET: RequestHandler = async ({ params, platform }) => { - const projectId = assertProjectId(params.projectId); - return Response.json({ connections: await listConnections(platform, projectId) }); -}; - -export const POST: RequestHandler = async ({ params, request, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const result = await connectRepository( - platform, - projectId, - await request.json().catch(() => null), - // The origin is baked into the workflow AND is the OIDC audience, so it - // has to be this deployment's own - never a value from the request body. - url.origin - ); - if (!result.ok) { - return Response.json({ error: result.error }, { status: result.status }); - } - return Response.json( - { - connection: result.connection, - subdomain: result.subdomain, - workflowWritten: result.workflowWritten - }, - { status: 201 } - ); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/github/connections/[appName]/+server.ts b/src/routes/api/projects/[projectId]/hosting/github/connections/[appName]/+server.ts deleted file mode 100644 index 78a0ba4b..00000000 --- a/src/routes/api/projects/[projectId]/hosting/github/connections/[appName]/+server.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { assertProjectId } from '$lib/server/agents'; -import { disconnectRepository, updateConnectionSettings } from '$lib/server/github-connect'; -import type { RequestHandler } from './$types'; - -/** - * Edits build settings at any time. For a build-mode connection the workflow - * file in the repository is rewritten FIRST (the settings live in it), then - * the row - a failed commit changes nothing. - */ -export const PATCH: RequestHandler = async ({ params, request, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const result = await updateConnectionSettings( - platform, - projectId, - params.appName, - await request.json().catch(() => null), - url.origin - ); - if (!result.ok) { - return Response.json({ error: result.error }, { status: result.status }); - } - return Response.json({ - connection: result.connection, - workflowRewritten: result.workflowRewritten - }); -}; - -/** - * Disconnecting IS deleting the row - a repository with no connection cannot - * deploy, whatever it still holds in `.github/workflows`. Removing the - * workflow file is attempted too, and reported, but never blocks. - */ -export const DELETE: RequestHandler = async ({ params, platform }) => { - const projectId = assertProjectId(params.projectId); - const result = await disconnectRepository(platform, projectId, params.appName); - if (!result.ok) { - return Response.json({ error: 'no such connection' }, { status: 404 }); - } - return Response.json({ ok: true, workflowRemoved: result.workflowRemoved }); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/github/install/+server.ts b/src/routes/api/projects/[projectId]/hosting/github/install/+server.ts deleted file mode 100644 index 2cf0f2a6..00000000 --- a/src/routes/api/projects/[projectId]/hosting/github/install/+server.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { assertProjectId } from '$lib/server/agents'; -import { isDemoProjectId } from '$lib/console'; -import { - githubAppConfig, - INSTALL_STATE_COOKIE, - installUrl, - signInstallState -} from '$lib/server/github'; -import type { RequestHandler } from './$types'; - -/** - * Where the operator goes to install the App and pick repositories. - * - * The `state` is signed and carries the operator and the project, which is - * what lets the callback trust the `installation_id` GitHub hands back on a - * redirect anyone could otherwise craft. - * - * It rides BOTH the URL and a short-lived cookie. GitHub's install redirect - * is a different flow from user authorization, and whether it echoes `state` - * back depends on how the App is configured - so the cookie is what makes - * the return leg work regardless, without weakening anything: it is the same - * signed token, verified the same way. - */ -export const GET: RequestHandler = async ({ params, platform, locals, cookies, url }) => { - const projectId = assertProjectId(params.projectId); - if (isDemoProjectId(projectId)) { - return Response.json({ error: 'demo projects cannot deploy apps' }, { status: 403 }); - } - const config = githubAppConfig(platform); - if (!config) { - return Response.json({ error: 'no GitHub App is configured on this console' }, { status: 503 }); - } - if (!locals.consoleUser) { - // The guard already required a session; this keeps the type honest. - return Response.json({ error: 'sign in first' }, { status: 401 }); - } - - const state = await signInstallState(config, { projectId, userId: locals.consoleUser.id }); - cookies.set(INSTALL_STATE_COOKIE, state, { - path: '/', - httpOnly: true, - // The return leg is a top-level navigation from github.com, so Lax is - // what lets the cookie ride along; Strict would drop it. - sameSite: 'lax', - // http in local dev would otherwise refuse to store it. - secure: url.protocol === 'https:', - maxAge: 15 * 60 - }); - return Response.json({ url: installUrl(config, state) }); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/github/repos/+server.ts b/src/routes/api/projects/[projectId]/hosting/github/repos/+server.ts deleted file mode 100644 index f16e4672..00000000 --- a/src/routes/api/projects/[projectId]/hosting/github/repos/+server.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { assertProjectId } from '$lib/server/agents'; -import { githubAppConfig } from '$lib/server/github'; -import { installationCoversProject, reportInstallationFailure } from '$lib/server/github-connect'; -import { inspectRepo, listInstallationRepos } from '$lib/server/github-repo'; -import type { RequestHandler } from './$types'; - -/** - * The repository picker's data. Scoped twice: the installation must belong to - * the org that owns this project, and the installation token itself only - * reaches repositories the operator selected on GitHub. - * - * With `?repo=/` it also inspects that repository and reports - * whether it needs a runner - which is what preselects build or direct mode. - */ -export const GET: RequestHandler = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const config = githubAppConfig(platform); - if (!config) { - return Response.json({ error: 'no GitHub App is configured on this console' }, { status: 503 }); - } - - const installationId = Number(url.searchParams.get('installation')); - if (!Number.isSafeInteger(installationId) || installationId <= 0) { - return Response.json({ error: 'installation is required' }, { status: 400 }); - } - const covers = await installationCoversProject(platform, installationId, projectId); - if (!covers.ok) { - return Response.json({ error: covers.error }, { status: 403 }); - } - - const listed = await listInstallationRepos(config, installationId); - if (listed.repos === null) { - const answer = await reportInstallationFailure(platform, installationId, listed); - return Response.json(answer.body, { status: answer.status }); - } - const repos = listed.repos; - - const wanted = url.searchParams.get('repo'); - if (!wanted) return Response.json({ repos }); - - const repo = repos.find((candidate) => candidate.fullName === wanted); - if (!repo) { - return Response.json({ error: 'that repository is not in this installation' }, { status: 404 }); - } - // Optional monorepo root: inspection reads THAT directory's package.json - // and listing, so the presets describe the app, not the workspace shell. - const dir = url.searchParams.get('dir')?.trim() ?? ''; - if (dir && !/^[A-Za-z0-9._/-]{1,200}$/.test(dir)) { - return Response.json({ error: 'invalid root directory' }, { status: 400 }); - } - if (dir.startsWith('/') || dir.split('/').includes('..')) { - return Response.json({ error: 'invalid root directory' }, { status: 400 }); - } - return Response.json({ - repos, - inspection: await inspectRepo( - config, - installationId, - repo.fullName, - repo.defaultBranch, - dir || null - ) - }); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/overview/+server.ts b/src/routes/api/projects/[projectId]/hosting/overview/+server.ts deleted file mode 100644 index 66315bd4..00000000 --- a/src/routes/api/projects/[projectId]/hosting/overview/+server.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AGENT_REGISTRY } from '$lib/agent-registry'; -import { agentUrl, assertProjectId, requireAgent, toNativeResponse } from '$lib/server/agents'; -import type { RequestHandler } from './$types'; - -/** Operator proxy: the project's hosting overview (apps, recent deploys). */ -export const GET: RequestHandler = async ({ params, url, platform }) => { - const projectId = assertProjectId(params.projectId); - const entry = AGENT_REGISTRY.hosting; - const agent = requireAgent(platform, entry); - const response = await agent.fetch(agentUrl(url.origin, entry, projectId, '/overview')); - return toNativeResponse(response as unknown as Response); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/tokens/+server.ts b/src/routes/api/projects/[projectId]/hosting/tokens/+server.ts deleted file mode 100644 index 9a6f95ca..00000000 --- a/src/routes/api/projects/[projectId]/hosting/tokens/+server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { assertProjectId } from '$lib/server/agents'; -import { listDeployTokens, mintDeployToken } from '$lib/server/hosting'; -import type { RequestHandler } from './$types'; - -/** - * Deploy tokens, console-plane (Phase B). - * Minted on ROOT projects from the Hosting page; the secret appears exactly - * once in the mint response and only its SHA-256 digest is stored. - */ - -export const GET: RequestHandler = async ({ params, platform }) => { - const projectId = assertProjectId(params.projectId); - return Response.json({ tokens: await listDeployTokens(platform, projectId) }); -}; - -export const POST: RequestHandler = async ({ params, request, platform }) => { - const projectId = assertProjectId(params.projectId); - const result = await mintDeployToken(platform, projectId, await request.json().catch(() => null)); - if (!result.ok) { - return Response.json({ error: result.error }, { status: result.status }); - } - return Response.json( - { id: result.id, name: result.name, token: result.token, createdAt: result.createdAt }, - { status: 201 } - ); -}; diff --git a/src/routes/api/projects/[projectId]/hosting/tokens/[tokenId]/+server.ts b/src/routes/api/projects/[projectId]/hosting/tokens/[tokenId]/+server.ts deleted file mode 100644 index efa3dcbd..00000000 --- a/src/routes/api/projects/[projectId]/hosting/tokens/[tokenId]/+server.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { assertProjectId } from '$lib/server/agents'; -import { revokeDeployToken } from '$lib/server/hosting'; -import type { RequestHandler } from './$types'; - -/** Revocation IS deletion: a token whose row is gone can never verify again. */ -export const DELETE: RequestHandler = async ({ params, platform }) => { - const projectId = assertProjectId(params.projectId); - const revoked = await revokeDeployToken(platform, projectId, params.tokenId); - if (!revoked) { - return Response.json({ error: 'no such token' }, { status: 404 }); - } - return Response.json({ revoked: true }); -}; diff --git a/src/worker-configuration.d.ts b/src/worker-configuration.d.ts index 932fcb34..8d336965 100644 --- a/src/worker-configuration.d.ts +++ b/src/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types ./src/worker-configuration.d.ts` (hash: 3bf5ab17fc624cda1dd2a097eeabc421) +// Generated by Wrangler by running `wrangler types ./src/worker-configuration.d.ts` (hash: 1a1f3d01bddc3bfa72182474e1b76628) // Runtime types generated with workerd@1.20260722.1 2026-07-10 nodejs_als,nodejs_compat interface __BaseEnv_Env { KV?: KVNamespace; @@ -12,16 +12,12 @@ interface __BaseEnv_Env { DEMO_MODE?: "true"; AUTH_AGENT: Fetcher /* auth-agent-local */ | Fetcher /* auth-agent-test */ | Fetcher /* auth-agent */ | Fetcher /* auth-agent-preview */; DB_AGENT: Fetcher /* db-agent-local */ | Fetcher /* db-agent-test */ | Fetcher /* db-agent */ | Fetcher /* db-agent-preview */; - HOSTING_AGENT: Fetcher /* hosting-agent-local */ | Fetcher /* hosting-agent-test */ | Fetcher /* hosting-agent */ | Fetcher /* hosting-agent-preview */; STORAGE_AGENT: Fetcher /* storage-agent-local */ | Fetcher /* storage-agent-test */ | Fetcher /* storage-agent */ | Fetcher /* storage-agent-preview */; CONSOLE_SETUP_TOKEN?: "e2e-console-setup-token-not-a-secret"; PUBLIC_SENTRY_DSN?: "https://50ed6e26b886124ac39e983efb72144d@o4510375271530496.ingest.de.sentry.io/4511764380778576" | "https://093f7505d223a871223f9a15130ef116@o4510375271530496.ingest.de.sentry.io/4511764377043024" | ""; PUBLIC_SENTRY_ENV?: "production" | "preview"; } declare namespace Cloudflare { - interface GlobalProps { - mainModule: typeof import("../.svelte-kit/cloudflare/_worker"); - } interface LocalEnv { KV: KVNamespace; DB: D1Database; @@ -33,7 +29,6 @@ declare namespace Cloudflare { DEMO_MODE: "true"; AUTH_AGENT: Fetcher /* auth-agent-local */; DB_AGENT: Fetcher /* db-agent-local */; - HOSTING_AGENT: Fetcher /* hosting-agent-local */; STORAGE_AGENT: Fetcher /* storage-agent-local */; } interface TestEnv { @@ -46,7 +41,6 @@ declare namespace Cloudflare { DEMO_MODE: "true"; AUTH_AGENT: Fetcher /* auth-agent-test */; DB_AGENT: Fetcher /* db-agent-test */; - HOSTING_AGENT: Fetcher /* hosting-agent-test */; STORAGE_AGENT: Fetcher /* storage-agent-test */; } interface ProductionEnv { @@ -60,7 +54,6 @@ declare namespace Cloudflare { PUBLIC_SENTRY_ENV: "production"; AUTH_AGENT: Fetcher /* auth-agent */; DB_AGENT: Fetcher /* db-agent */; - HOSTING_AGENT: Fetcher /* hosting-agent */; STORAGE_AGENT: Fetcher /* storage-agent */; } interface PreviewEnv { @@ -74,7 +67,6 @@ declare namespace Cloudflare { PUBLIC_SENTRY_ENV: "preview"; AUTH_AGENT: Fetcher /* auth-agent-preview */; DB_AGENT: Fetcher /* db-agent-preview */; - HOSTING_AGENT: Fetcher /* hosting-agent-preview */; STORAGE_AGENT: Fetcher /* storage-agent-preview */; } interface Env extends __BaseEnv_Env {} diff --git a/wrangler.e2e.jsonc b/wrangler.e2e.jsonc index c5689521..d4e4319c 100644 --- a/wrangler.e2e.jsonc +++ b/wrangler.e2e.jsonc @@ -25,7 +25,6 @@ "services": [ { "binding": "AUTH_AGENT", "service": "auth-agent-test" }, { "binding": "DB_AGENT", "service": "db-agent-test" }, - { "binding": "HOSTING_AGENT", "service": "hosting-agent-test" }, { "binding": "STORAGE_AGENT", "service": "storage-agent-test" } ], // DEMO_MODE mirrors env.test so dashboard specs can drive demo projects diff --git a/wrangler.jsonc b/wrangler.jsonc index ddd6a084..9eb247ba 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -79,7 +79,6 @@ "services": [ { "binding": "AUTH_AGENT", "service": "auth-agent" }, { "binding": "DB_AGENT", "service": "db-agent" }, - { "binding": "HOSTING_AGENT", "service": "hosting-agent" }, { "binding": "STORAGE_AGENT", "service": "storage-agent" } ], @@ -171,7 +170,6 @@ "services": [ { "binding": "AUTH_AGENT", "service": "auth-agent-local" }, { "binding": "DB_AGENT", "service": "db-agent-local" }, - { "binding": "HOSTING_AGENT", "service": "hosting-agent-local" }, { "binding": "STORAGE_AGENT", "service": "storage-agent-local" } ], "ratelimits": [ @@ -218,7 +216,6 @@ "services": [ { "binding": "AUTH_AGENT", "service": "auth-agent-test" }, { "binding": "DB_AGENT", "service": "db-agent-test" }, - { "binding": "HOSTING_AGENT", "service": "hosting-agent-test" }, { "binding": "STORAGE_AGENT", "service": "storage-agent-test" } ], "ratelimits": [ @@ -265,7 +262,6 @@ "services": [ { "binding": "AUTH_AGENT", "service": "auth-agent" }, { "binding": "DB_AGENT", "service": "db-agent" }, - { "binding": "HOSTING_AGENT", "service": "hosting-agent" }, { "binding": "STORAGE_AGENT", "service": "storage-agent" } ], "ratelimits": [ @@ -289,7 +285,7 @@ // are the point: AUTH_AGENT targets the preview agent and DB binds the // preview control plane. A separate Worker is also a separate SECRET // STORE - preview inherits nothing from production, so optional secrets - // (the GitHub App four, CONSOLE_SETUP_TOKEN) exist here only if put + // (CONSOLE_SETUP_TOKEN among them) exist here only if put // here. "preview": { "name": "cloudflarebase-com-preview", @@ -326,7 +322,6 @@ "services": [ { "binding": "AUTH_AGENT", "service": "auth-agent-preview" }, { "binding": "DB_AGENT", "service": "db-agent-preview" }, - { "binding": "HOSTING_AGENT", "service": "hosting-agent-preview" }, { "binding": "STORAGE_AGENT", "service": "storage-agent-preview" } ] } From 309a98ddb6667cae21f7aa3970c1f847f721c22f Mon Sep 17 00:00:00 2001 From: clibequilibrium <105135724+clibequilibrium@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:09:37 -0400 Subject: [PATCH 2/8] CLI drops managed hosting With hosting gone, bare `init` only links the directory to a console project (no subdomain claim), `deploy` is the wrangler path alone, and `secret` - which set secrets on hosted apps - is removed. `key` keeps working off the same cloudflarebase.json; older files that still carry app/vars/assets are read, not refused. --- cli/src/commands/deploy.ts | 240 +----------------------------- cli/src/commands/init.ts | 8 +- cli/src/commands/key.ts | 17 +-- cli/src/commands/managed-init.ts | 78 ++-------- cli/src/commands/secret.ts | 178 ---------------------- cli/src/index.ts | 11 +- cli/src/lib/agents.ts | 4 - cli/src/lib/assetsignore.ts | 65 -------- cli/src/lib/managed.ts | 245 ++----------------------------- cli/src/lib/oidc.ts | 63 -------- 10 files changed, 44 insertions(+), 865 deletions(-) delete mode 100644 cli/src/commands/secret.ts delete mode 100644 cli/src/lib/assetsignore.ts delete mode 100644 cli/src/lib/oidc.ts diff --git a/cli/src/commands/deploy.ts b/cli/src/commands/deploy.ts index 0d4598fd..ada2d250 100644 --- a/cli/src/commands/deploy.ts +++ b/cli/src/commands/deploy.ts @@ -1,242 +1,16 @@ -import { readdir, readFile } from 'node:fs/promises'; +import { readFile } from 'node:fs/promises'; import path from 'node:path'; -import { loadConfig } from '../lib/config.js'; -import { blank, bold, dim, info, step, success, UserError } from '../lib/log.js'; -import { - collectAssets, - findAssetsDirectory, - hostingFetch, - managedConfigFromEnv, - readManagedConfig, - readUserWranglerConfig, - resolveGitBranch, - sanitizeBranchName, - targetProjectId, - type ManagedConfig -} from '../lib/managed.js'; -import { actionsIdToken, inGithubActions } from '../lib/oidc.js'; +import { blank, dim, info, step, success, UserError } from '../lib/log.js'; import { run, runOrFail } from '../lib/run.js'; import { readTrustedOrigins } from '../lib/wrangler-config.js'; /** - * `cloudflarebase deploy` - deploy, branching on context: - * - * - `cloudflarebase.json` present (written by bare `cloudflarebase init`), OR - * the CLOUDFLAREBASE_PROJECT/APP/URL trio in the environment (written into - * the workflow by the console's GitHub App): MANAGED deploy against the - * console's hosting API. The git branch decides the target: the default - * branch deploys the root, anything else deploys `--` - * (auto-created), so preview-per-git-branch falls out. - * - otherwise: the self-hosted wrangler path, unchanged. + * `cloudflarebase deploy` - deploy this Worker with wrangler. There is nothing + * to configure before sign-in works: the agent trusts the deployment's own + * origin automatically, so a fresh deploy is usable the moment the URL exists. */ export async function deployCommand(projectDir: string, rest: string[] = []): Promise { - // The file wins: a directory someone deliberately linked is never - // redirected by an ambient variable. - const managed = (await readManagedConfig(projectDir)) ?? managedConfigFromEnv(); - if (managed) { - await managedDeploy(projectDir, managed, parseManagedFlags(rest)); - return; - } - if (rest.length) { - throw new UserError( - 'Deploy flags need an initialized project.', - 'Run `cloudflarebase init` first - flags like --branch only apply to managed deploys.' - ); - } - await selfHostedDeploy(projectDir); -} - -interface ManagedFlags { - branch?: string; - token?: string; -} - -function parseManagedFlags(rest: string[]): ManagedFlags { - const flags: ManagedFlags = {}; - for (let i = 0; i < rest.length; i += 1) { - const arg = rest[i]; - if (arg === '--branch') flags.branch = rest[++i]; - else if (arg === '--token') flags.token = rest[++i]; - else throw new UserError(`Unknown flag "${arg}".`); - } - return flags; -} - -/** The bundling output directory; keep it out of version control. */ -const BUNDLE_DIR = '.cloudflarebase/dist'; - -async function managedDeploy( - projectDir: string, - managed: ManagedConfig, - flags: ManagedFlags -): Promise { - // Three credentials, most explicit first: an operator-supplied deploy - // token, GitHub's identity token (a connected repository stores nothing - - // the console verifies the token against GitHub's public keys), then the - // stored login for interactive runs. - let token = flags.token ?? process.env.CLOUDFLAREBASE_DEPLOY_TOKEN; - if (!token) { - // The audience is the console origin; a token minted for anything else - // is refused, which is what stops one being replayed here. - token = (await actionsIdToken(managed.origin)) ?? undefined; - if (token) step('Authenticating with the GitHub identity token'); - } - if (!token) { - const config = await loadConfig(); - if (config.origin !== managed.origin) { - throw new UserError( - `This directory is linked to ${managed.origin}, but you are signed in to ${config.origin}.`, - `Run \`cloudflarebase login ${managed.origin}\` (or set CLOUDFLAREBASE_DEPLOY_TOKEN).` - ); - } - token = config.token; - } - - // Git branch -> target project id. `--branch` overrides; `main` aliases - // the root and never appears in a URL. - const gitBranch = flags.branch ?? (await resolveGitBranch(projectDir)); - const branch = gitBranch ? sanitizeBranchName(gitBranch) : null; - const target = targetProjectId(managed.project, branch); - - if (target !== managed.project) { - // Ensure the branch row exists (409 = already does). This is one of the - // two endpoints a deploy token is valid on, so CI's new git branches - // mint their preview project on first deploy. - const created = await hostingFetch( - managed.origin, - token, - `/api/projects/${managed.project}/branches`, - { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ branch }) - } - ); - if (!created.ok && created.status !== 409) { - throw new UserError(`Could not create branch "${branch}": ${await failureText(created)}`); - } - step(`Deploying branch ${bold(branch!)} (${target})`); - } else { - step(`Deploying ${bold(managed.project)}`); - } - - // Bundle the Worker when the user's wrangler config declares one - - // wrangler does the bundling (--dry-run --outdir), we upload the output. - const wrangler = await readUserWranglerConfig(projectDir); - const form = new FormData(); - let mainModule: string | undefined; - let moduleCount = 0; - if (wrangler?.main) { - step('Bundling the Worker with wrangler'); - await runOrFail('npx', ['wrangler', 'deploy', '--dry-run', `--outdir=${BUNDLE_DIR}`], { - cwd: projectDir, - capture: true, - failure: 'wrangler bundling failed.' - }); - const outDir = path.join(projectDir, BUNDLE_DIR); - const emitted = (await readdir(outDir)).filter((name) => /\.(?:js|mjs)$/.test(name)); - if (!emitted.length) { - throw new UserError('wrangler emitted no JavaScript bundle.'); - } - const entryGuess = `${path.basename(wrangler.main).replace(/\.[jt]sx?$/, '')}.js`; - mainModule = - emitted.length === 1 - ? emitted[0] - : (emitted.find((name) => name === entryGuess) ?? 'index.js'); - if (!mainModule || !emitted.includes(mainModule)) { - throw new UserError( - 'Could not identify the entry module in the wrangler bundle.', - `Emitted: ${emitted.join(', ')}` - ); - } - for (const name of emitted) { - const bytes = await readFile(path.join(outDir, name)); - form.append(`module:${name}`, new Blob([new Uint8Array(bytes)]), name); - moduleCount += 1; - } - } - - // Assets: wrangler's directory, the cloudflarebase.json override, or the - // conventional build outputs. A bare assets directory deploys as an - // assets-only Worker. - const assetsDir = await findAssetsDirectory( - projectDir, - managed.assets, - wrangler?.assetsDirectory - ); - let assetCount = 0; - if (assetsDir) { - for (const file of await collectAssets(assetsDir)) { - form.append( - `asset:${file.name}`, - new Blob([new Uint8Array(file.bytes)]), - path.basename(file.name) - ); - assetCount += 1; - } - } - if (!moduleCount && !assetCount) { - throw new UserError( - 'Nothing to deploy - no Worker main and no assets directory.', - 'Build your app first, or set "assets" in cloudflarebase.json.\n' + - 'Framework apps need their Cloudflare adapter: SvelteKit @sveltejs/adapter-cloudflare, ' + - 'Next.js @opennextjs/cloudflare (adapter-auto and plain `next build` emit nothing deployable).' - ); - } - - form.append( - 'meta', - JSON.stringify({ - mainModule, - compatibilityDate: wrangler?.compatibilityDate, - compatibilityFlags: wrangler?.compatibilityFlags, - vars: managed.vars - }) - ); - - step(`Uploading ${moduleCount} module(s) and ${assetCount} asset(s)`); - const response = await hostingFetch( - managed.origin, - token, - `/api/projects/${target}/hosting/apps/${managed.app}/deploys`, - { method: 'POST', body: form } - ); - if (!response.ok) { - throw new UserError(`Deploy failed: ${await failureText(response)}`); - } - const result = (await response.json()) as { - subdomain: string; - url: string | null; - deploy: { status: string }; - }; - - blank(); - success(result.url ? `Deployed to ${bold(result.url)}` : `Deployed as ${bold(result.subdomain)}`); - if (result.subdomain !== (branch ? `${managed.app}-${branch}` : managed.app)) { - // Auto-numbering claimed a neighbor - say so, never hide it. - info(` ${dim('·')} The wanted name was taken; this app is claimed as ${result.subdomain}.`); - } - if (result.deploy.status === 'stub') { - info(` ${dim('·')} This console records deploys without a dispatch namespace (stub mode).`); - } -} - -async function failureText(response: Response): Promise { - if (response.status === 401) { - return inGithubActions() - ? 'the credential was refused - reconnect the repository from the console Hosting page, or check CLOUDFLAREBASE_DEPLOY_TOKEN' - : 'the credential was refused - the deploy token may be revoked, or sign in again'; - } - const body = (await response.json().catch(() => null)) as { error?: string } | null; - return body?.error ?? `the console responded ${response.status}`; -} - -/** - * The self-hosted wrangler path - unchanged. There is nothing to configure - * before sign-in works: the agent trusts the deployment's own origin - * automatically, so a fresh deploy is usable the moment the URL exists. - */ -async function selfHostedDeploy(projectDir: string): Promise { + if (rest.length) throw new UserError(`Unknown flag "${rest[0]}".`); const configPath = path.join(projectDir, 'wrangler.jsonc'); let configText: string; try { @@ -244,7 +18,7 @@ async function selfHostedDeploy(projectDir: string): Promise { } catch { throw new UserError( 'No wrangler.jsonc found - nothing to deploy.', - 'Run `cloudflarebase init ` to scaffold a project, or bare `cloudflarebase init` for managed hosting.' + 'Run `cloudflarebase init ` to scaffold a project.' ); } diff --git a/cli/src/commands/init.ts b/cli/src/commands/init.ts index a3cbe4dc..8b6f1703 100644 --- a/cli/src/commands/init.ts +++ b/cli/src/commands/init.ts @@ -15,10 +15,10 @@ import { runOrFail } from '../lib/run.js'; * agent package's own fragment, so there is exactly one definition of a * working configuration and `init` can never drift from it. * - * BARE `cloudflarebase init` (no name) is the managed-hosting setup instead: - * wrangler/Netlify vocabulary for "initialize cloudflarebase in the current - * directory" - it connects this app to a console project and writes - * cloudflarebase.json (managed-init.ts). + * BARE `cloudflarebase init` (no name) is the managed setup instead: wrangler + * vocabulary for "initialize cloudflarebase in the current directory" - it + * connects this directory to a console project and writes cloudflarebase.json + * (managed-init.ts). */ export async function initCommand(cwd: string, args: string[]): Promise { const name = args[0]; diff --git a/cli/src/commands/key.ts b/cli/src/commands/key.ts index 12d2cce0..ce0412e6 100644 --- a/cli/src/commands/key.ts +++ b/cli/src/commands/key.ts @@ -2,7 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { loadConfig } from '../lib/config.js'; import { blank, bold, dim, info, success, UserError, warn } from '../lib/log.js'; -import { hostingFetch, readManagedConfig, targetProjectId } from '../lib/managed.js'; +import { projectFetch, readManagedConfig, targetProjectId } from '../lib/managed.js'; /** * `cloudflarebase key create|list|revoke` - project service keys. @@ -15,8 +15,8 @@ import { hostingFetch, readManagedConfig, targetProjectId } from '../lib/managed * service keys, or it could grow and outlive itself. So this command needs * `cloudflarebase login`, exactly like `secret put`. * - * Keys are scoped to ONE project - never a root and its branches, the way - * deploy tokens are - because for data the branch IS the isolation boundary. + * Keys are scoped to ONE project - never a root and its branches - because + * for data the branch IS the isolation boundary. * `--branch` therefore targets a specific registry row rather than a family. */ @@ -85,14 +85,13 @@ export async function keyCommand(projectDir: string, rest: string[]): Promise` - * stays the self-hosted scaffold; the wrangler/Netlify-style bare form is - * "initialize cloudflarebase here": pick (or create) a project, claim an app - * subdomain - showing the auto-numbered suggestion first when the wanted - * name is taken, because collisions never fail - and write - * `cloudflarebase.json`, which is what flips `cloudflarebase deploy` into - * managed mode. + * Bare `cloudflarebase init` - connect the CURRENT directory to a project on a + * managed console. `init ` stays the self-hosted scaffold; the + * wrangler-style bare form is "initialize cloudflarebase here": pick (or + * create) a project and write `cloudflarebase.json`, which is what `key` + * reads to know which project it acts on. */ interface Flags { project?: string; - app?: string; - yes?: boolean; } function parseFlags(rest: string[]): Flags { @@ -26,8 +21,6 @@ function parseFlags(rest: string[]): Flags { for (let i = 0; i < rest.length; i += 1) { const arg = rest[i]; if (arg === '--project') flags.project = rest[++i]; - else if (arg === '--app') flags.app = rest[++i]; - else if (arg === '--yes' || arg === '-y') flags.yes = true; else throw new UserError(`Unknown flag "${arg}".`); } return flags; @@ -45,7 +38,7 @@ export async function managedInitCommand(projectDir: string, rest: string[]): Pr const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); try { - // 1. Pick or create the ROOT project (branches are decided per deploy). + // Pick or create the ROOT project; commands take --branch for branches. let projectId = flags.project; if (!projectId) { const response = await consoleFetch(config, '/api/registry/projects'); @@ -83,69 +76,20 @@ export async function managedInitCommand(projectDir: string, rest: string[]): Pr } } if (projectId.includes('--')) { - throw new UserError('Initialize against the ROOT project - branches are decided per deploy.'); - } - - // 2. Choose the app name, defaulting to the project id. - let appName = flags.app; - if (!appName) { - const suggestion = APP_NAME.test(projectId) ? projectId : undefined; - const answer = ( - await rl.question(`App name${suggestion ? ` [${suggestion}]` : ''}: `) - ).trim(); - appName = answer || suggestion || ''; - } - if (!APP_NAME.test(appName) || appName.includes('--')) { throw new UserError( - `"${appName}" is not a valid app name.`, - 'Use 3-48 lowercase letters, numbers, and hyphens (no "--").' + 'Initialize against the ROOT project - commands take --branch for branches.' ); } - // 3. Preview the claim, then take it. Taken names auto-number rather - // than fail, so the preview is what makes the numbering consensual in - // an interactive session - CI deploys just take it. - const preview = await consoleFetch(config, `/api/projects/${projectId}/hosting/claims`, { - method: 'POST', - body: JSON.stringify({ app: appName, dry: true }) - }); - if (!preview.ok) { - throw new UserError(`Could not claim "${appName}": ${await errorText(preview)}`); - } - const suggested = (await preview.json()) as { subdomain: string }; - if (suggested.subdomain !== appName) { - info(`${bold(appName)} is taken - the next free subdomain is ${bold(suggested.subdomain)}.`); - } - if (!flags.yes) { - const confirm = ( - await rl.question(`Claim ${bold(suggested.subdomain)}.cfbase.dev? [Y/n] `) - ).trim(); - if (confirm && !/^y(es)?$/i.test(confirm)) { - throw new UserError('Nothing claimed.'); - } - } - const claim = await consoleFetch(config, `/api/projects/${projectId}/hosting/claims`, { - method: 'POST', - body: JSON.stringify({ app: appName }) - }); - if (!claim.ok) { - throw new UserError(`Could not claim "${appName}": ${await errorText(claim)}`); - } - const claimed = (await claim.json()) as { subdomain: string }; - - // 4. Write the marker that flips `deploy` into managed mode. const file = await writeManagedConfig(projectDir, { project: projectId, - app: appName, origin: config.origin }); blank(); - success(`Initialized: ${bold(projectId)} as ${bold(claimed.subdomain)}`); - info(` ${dim('·')} ${file} written - commit it; \`cloudflarebase deploy\` is now managed.`); - info( - ` ${dim('·')} Root deploys serve at ${bold(`${claimed.subdomain}.cfbase.dev`)}; a branch serves at ${claimed.subdomain}-.cfbase.dev.` - ); + success(`Initialized: ${bold(projectId)} on ${config.origin}`); + info(` ${dim('·')} ${file} written - commit it.`); + info(` ${dim('·')} Next: \`cloudflarebase key create --env-file\` mints a server key.`); } finally { rl.close(); } diff --git a/cli/src/commands/secret.ts b/cli/src/commands/secret.ts deleted file mode 100644 index 2055a3c0..00000000 --- a/cli/src/commands/secret.ts +++ /dev/null @@ -1,178 +0,0 @@ -import readline from 'node:readline/promises'; -import { loadConfig } from '../lib/config.js'; -import { blank, bold, dim, info, success, UserError } from '../lib/log.js'; -import { - hostingFetch, - readManagedConfig, - resolveGitBranch, - sanitizeBranchName, - targetProjectId -} from '../lib/managed.js'; - -/** - * `cloudflarebase secret put|list|delete` - secrets on the initialized app - * (`wrangler secret` vocabulary on purpose). - * - * `put` PATCHes the deployed script's settings with `keep_bindings`, so - * redeploys never drop what is set here; values are write-through and - * unrecoverable, which is why `list` shows names only. Operator sessions only - * - deploy tokens deliberately cannot touch secrets. - */ - -const USAGE = [ - 'Usage: cloudflarebase secret put [--value ] [--branch ]', - ' cloudflarebase secret list [--branch ]', - ' cloudflarebase secret delete [--branch ]' -].join('\n'); - -interface SecretTarget { - origin: string; - token: string; - app: string; - /** The branch-resolved registry project id the request dials. */ - projectId: string; - /** The short branch name, for messages; null on the root. */ - branch: string | null; -} - -/** The shared preamble: managed config, matching login, branch resolution. */ -async function resolveTarget(projectDir: string, branchFlag?: string): Promise { - const managed = await readManagedConfig(projectDir); - if (!managed) { - throw new UserError('This directory is not initialized.', 'Run `cloudflarebase init` first.'); - } - const config = await loadConfig(); - if (config.origin !== managed.origin) { - throw new UserError( - `This directory is linked to ${managed.origin}, but you are signed in to ${config.origin}.`, - `Run \`cloudflarebase login ${managed.origin}\` first.` - ); - } - const gitBranch = branchFlag ?? (await resolveGitBranch(projectDir)); - const branch = gitBranch ? sanitizeBranchName(gitBranch) : null; - return { - origin: managed.origin, - token: config.token, - app: managed.app, - projectId: targetProjectId(managed.project, branch), - branch - }; -} - -function assertSecretName(name: string | undefined): string { - if (!name) throw new UserError(USAGE); - if (!/^[A-Z][A-Z0-9_]*$/.test(name)) { - throw new UserError(`"${name}" is not a valid secret name.`, 'Use UPPER_SNAKE_CASE.'); - } - return name; -} - -async function fail(response: Response, verb: string): Promise { - const body = (await response.json().catch(() => null)) as { error?: string } | null; - throw new UserError( - `${verb} failed: ${body?.error ?? `the console responded ${response.status}`}` - ); -} - -export async function secretCommand(projectDir: string, rest: string[]): Promise { - const [subcommand, ...args] = rest; - - if (subcommand === 'put') { - const name = assertSecretName(args.shift()); - let value: string | undefined; - let branchFlag: string | undefined; - for (let i = 0; i < args.length; i += 1) { - const arg = args[i]; - if (arg === '--value') value = args[++i]; - else if (arg === '--branch') branchFlag = args[++i]; - else throw new UserError(`Unknown flag "${arg}".`); - } - - const target = await resolveTarget(projectDir, branchFlag); - if (value === undefined) { - const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); - try { - value = await rl.question(`Value for ${bold(name)}: `); - } finally { - rl.close(); - } - } - if (!value) throw new UserError('The secret value is empty.'); - - const response = await hostingFetch( - target.origin, - target.token, - `/api/projects/${target.projectId}/hosting/apps/${target.app}/secrets`, - { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ name, value }) - } - ); - if (!response.ok) await fail(response, 'Setting the secret'); - - blank(); - success( - `Secret ${bold(name)} set on ${bold(target.app)}${target.branch ? ` (${target.branch})` : ''}` - ); - return; - } - - if (subcommand === 'list') { - let branchFlag: string | undefined; - for (let i = 0; i < args.length; i += 1) { - if (args[i] === '--branch') branchFlag = args[++i]; - else throw new UserError(`Unknown flag "${args[i]}".`); - } - - const target = await resolveTarget(projectDir, branchFlag); - const response = await hostingFetch( - target.origin, - target.token, - `/api/projects/${target.projectId}/hosting/apps/${target.app}/secrets` - ); - if (!response.ok) await fail(response, 'Listing secrets'); - const body = (await response.json()) as { - secrets: { name: string; updatedAt: string }[]; - }; - - blank(); - if (!body.secrets.length) { - info( - `No secrets on ${bold(target.app)}${target.branch ? ` (${target.branch})` : ''}. ` + - dim('Set one with `cloudflarebase secret put `.') - ); - return; - } - for (const secret of body.secrets) { - info(`${bold(secret.name)} ${dim(`updated ${secret.updatedAt.slice(0, 10)}`)}`); - } - return; - } - - if (subcommand === 'delete') { - const name = assertSecretName(args.shift()); - let branchFlag: string | undefined; - for (let i = 0; i < args.length; i += 1) { - if (args[i] === '--branch') branchFlag = args[++i]; - else throw new UserError(`Unknown flag "${args[i]}".`); - } - - const target = await resolveTarget(projectDir, branchFlag); - const response = await hostingFetch( - target.origin, - target.token, - `/api/projects/${target.projectId}/hosting/apps/${target.app}/secrets/${name}`, - { method: 'DELETE' } - ); - if (!response.ok) await fail(response, 'Deleting the secret'); - - blank(); - success( - `Secret ${bold(name)} deleted from ${bold(target.app)}${target.branch ? ` (${target.branch})` : ''}` - ); - return; - } - - throw new UserError(USAGE); -} diff --git a/cli/src/index.ts b/cli/src/index.ts index ad6b539d..39d17976 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -9,7 +9,6 @@ import { keyCommand } from './commands/key.js'; import { loginCommand } from './commands/login.js'; import { logoutCommand } from './commands/logout.js'; import { schemaCommand } from './commands/schema.js'; -import { secretCommand } from './commands/secret.js'; import { blank, bold, dim, error, info, UserError } from './lib/log.js'; const usage = (): void => { @@ -21,12 +20,7 @@ const usage = (): void => { ` cloudflarebase init ${dim('connect this directory to a managed console project')}` ); info(` cloudflarebase add ${dim('install an agent into an existing Worker')}`); - info( - ` cloudflarebase deploy ${dim('deploy - managed once initialized, wrangler otherwise')}` - ); - info( - ` cloudflarebase secret ${dim('put | list | delete a secret on the deployed app')}` - ); + info(` cloudflarebase deploy ${dim('deploy this Worker with wrangler')}`); info(` cloudflarebase key ${dim('create | list | revoke a project service key')}`); info( ` cloudflarebase login ${dim('authenticate against a console (browser approval)')}` @@ -59,9 +53,6 @@ async function main(): Promise { case 'deploy': await deployCommand(cwd, rest); return; - case 'secret': - await secretCommand(cwd, rest); - return; case 'key': await keyCommand(cwd, rest); return; diff --git a/cli/src/lib/agents.ts b/cli/src/lib/agents.ts index 3df24489..a6dbd095 100644 --- a/cli/src/lib/agents.ts +++ b/cli/src/lib/agents.ts @@ -33,10 +33,6 @@ export const AGENTS: Record = { packageName: '@cloudflarebase/db', description: 'Firestore-style documents with live queries - one Durable Object per collection' }, - hosting: { - packageName: '@cloudflarebase/hosting', - description: 'Apps and functions on Workers for Platforms - assets and code in one deploy' - }, storage: { packageName: '@cloudflarebase/storage', description: 'Object storage on R2 - buckets of files with per-bucket access modes' diff --git a/cli/src/lib/assetsignore.ts b/cli/src/lib/assetsignore.ts deleted file mode 100644 index 808dbb2d..00000000 --- a/cli/src/lib/assetsignore.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * `.assetsignore` support for managed deploys. - * - * Cloudflare's framework adapters write one into their output directory - * (SvelteKit's adapter-cloudflare emits `_worker.js`, `_routes.json`, - * `_headers`, `_redirects`), and wrangler honours it when IT uploads assets. - * A managed deploy uploads through us instead, so ignoring the file meant - * publishing the customer's SERVER bundle as a public asset - fetchable at - * `/_worker.js` on their site. This is the wrangler-compatible subset: - * gitignore-style lines, `*`/`**`/`?` wildcards, `#` comments. Negation is - * not supported - a line starting with `!` is skipped rather than guessed at. - */ - -const RESERVED_ROOT_FILES = new Set(['_worker.js', '_routes.json', '_headers', '_redirects']); - -export interface AssetFilter { - /** True when `path` (root-relative, no leading slash) must not deploy. */ - ignores(path: string): boolean; -} - -function patternToRegex(pattern: string): RegExp | null { - // Directory patterns match everything under them; root-anchored patterns - // (any with a slash) match from the top, bare names match at any depth. - const dirOnly = pattern.endsWith('/'); - let body = dirOnly ? pattern.slice(0, -1) : pattern; - const anchored = body.includes('/'); - body = body.replace(/^\//, '').replace(/^\*\*\//, ''); - if (!body) return null; - - // Split on `**` first so it never collides with the single-`*` rewrite. - const escaped = body - .split('**') - .map((piece) => - piece - .replace(/[.+^${}()|[\]\\]/g, '\\$&') - .replace(/\*/g, '[^/]*') - .replace(/\?/g, '[^/]') - ) - .join('.*'); - const prefix = anchored ? '^' : '(?:^|/)'; - // A match is the entry itself or anything beneath it (files under an - // ignored directory are ignored with it). - return new RegExp(`${prefix}${escaped}(?:$|/)`); -} - -/** - * Parses `.assetsignore` content into a filter. The four Cloudflare - * convention files are ALWAYS ignored at the root, ignore file or not: they - * are deploy configuration, never content - `_worker.js` doubly so. - */ -export function assetFilter(ignoreFileContent: string | null): AssetFilter { - const patterns: RegExp[] = []; - for (const rawLine of (ignoreFileContent ?? '').split('\n')) { - const line = rawLine.trim(); - if (!line || line.startsWith('#') || line.startsWith('!')) continue; - const regex = patternToRegex(line); - if (regex) patterns.push(regex); - } - return { - ignores(path: string): boolean { - if (RESERVED_ROOT_FILES.has(path)) return true; - return patterns.some((pattern) => pattern.test(path)); - } - }; -} diff --git a/cli/src/lib/managed.ts b/cli/src/lib/managed.ts index 98d2a6d3..eb3c445b 100644 --- a/cli/src/lib/managed.ts +++ b/cli/src/lib/managed.ts @@ -1,15 +1,11 @@ -import { readdir, readFile, stat, writeFile } from 'node:fs/promises'; +import { readFile, writeFile } from 'node:fs/promises'; import path from 'node:path'; -import { parse as parseJsonc } from 'jsonc-parser'; -import { assetFilter } from './assetsignore.js'; import { UserError } from './log.js'; -import { run } from './run.js'; /** - * Managed hosting support (Phase B): - * `cloudflarebase.json` marks a project as linked to a console, and `deploy` - * branches on its presence - present means managed deploy against the - * console's hosting API, absent means the self-hosted wrangler path. + * Managed projects: `cloudflarebase.json` links a directory to a project on a + * console, so commands that act on that project (`key`) know which one and + * where without flags on every call. Written by bare `cloudflarebase init`. */ export const MANAGED_FILE = 'cloudflarebase.json'; @@ -17,20 +13,11 @@ export const MANAGED_FILE = 'cloudflarebase.json'; // Mirrors projectIdSchema in the console and the agents (48 chars: a branch // id is `--`, so the ceiling has to hold both). const PROJECT_ID = /^[a-z0-9][a-z0-9-]{0,47}$/; -const BRANCH_NAME = /^[a-z0-9][a-z0-9-]{0,15}$/; -/** Mirrors the console's appNameSchema. */ -export const APP_NAME = /^[a-z0-9][a-z0-9-]{2,47}$/; export interface ManagedConfig { - /** The ROOT project id; the branch is decided per deploy. */ + /** The ROOT project id; commands take `--branch` to target a branch. */ project: string; - app: string; origin: string; - /** Plain-text vars injected into the deployed Worker. */ - vars?: Record; - /** Assets directory override (else wrangler.jsonc `assets.directory`, - * else the first of dist/build/public/_site/out that exists). */ - assets?: string; } export async function readManagedConfig(projectDir: string): Promise { @@ -40,51 +27,20 @@ export async function readManagedConfig(projectDir: string): Promise; if ( typeof parsed.project !== 'string' || - typeof parsed.app !== 'string' || typeof parsed.origin !== 'string' || - !PROJECT_ID.test(parsed.project) || - !APP_NAME.test(parsed.app) + !PROJECT_ID.test(parsed.project) ) { throw new UserError( `${MANAGED_FILE} is malformed.`, 'Run `cloudflarebase init` again to reconnect this directory.' ); } - return { - project: parsed.project, - app: parsed.app, - origin: new URL(parsed.origin).origin, - vars: typeof parsed.vars === 'object' && parsed.vars ? parsed.vars : undefined, - assets: typeof parsed.assets === 'string' ? parsed.assets : undefined - }; -} - -/** - * The same configuration from the environment, for a repository connected - * through the console's GitHub App. - * - * The console knows the project and app when it writes the workflow, so it - * passes them as env - which means connecting in the console is the entire - * setup and the repository needs no `cloudflarebase.json` and no local CLI - * run. Null unless all three are present, so a stray variable can never - * silently redirect a deploy that has a file. - */ -export function managedConfigFromEnv(): ManagedConfig | null { - const origin = process.env.CLOUDFLAREBASE_URL?.trim(); - const project = process.env.CLOUDFLAREBASE_PROJECT?.trim(); - const app = process.env.CLOUDFLAREBASE_APP?.trim(); - if (!origin || !project || !app) return null; - if (!PROJECT_ID.test(project) || !APP_NAME.test(app)) { - throw new UserError( - 'CLOUDFLAREBASE_PROJECT or CLOUDFLAREBASE_APP is malformed.', - 'Reconnect the repository from the console Hosting page to rewrite the workflow.' - ); - } - const assets = process.env.CLOUDFLAREBASE_ASSETS?.trim(); - return { project, app, origin: new URL(origin).origin, assets: assets || undefined }; + return { project: parsed.project, origin: new URL(parsed.origin).origin }; } export async function writeManagedConfig( @@ -96,16 +52,12 @@ export async function writeManagedConfig( return file; } -/** - * Authenticated fetch against the console's hosting surface. Deliberately not - * `consoleFetch`: the bearer here may be a deploy token, whose 401 means - * "revoked or wrong project", not "sign in again". - */ -export async function hostingFetch( +/** Authenticated fetch against a project surface on the linked console. */ +export async function projectFetch( origin: string, token: string, route: string, - init: { method?: string; body?: string | FormData; headers?: Record } = {} + init: { method?: string; body?: string; headers?: Record } = {} ): Promise { return fetch(`${origin}${route}`, { method: init.method ?? 'GET', @@ -123,64 +75,7 @@ export async function hostingFetch( }); } -/** - * Git branch -> cloudflarebase branch. The DEFAULT git branch maps to the - * root project (`main` never appears anywhere - it aliases the root); any - * other branch maps to `--` after charset sanitizing. Returns - * null for the root. - */ -export async function resolveGitBranch(projectDir: string): Promise { - // CI first: actions/checkout leaves a detached HEAD, so `rev-parse` cannot - // name the branch there. The official workflow passes both vars; plain - // GitHub Actions still gets GITHUB_REF_NAME with a main/master heuristic. - const envBranch = - process.env.CLOUDFLAREBASE_GIT_BRANCH?.trim() || process.env.GITHUB_REF_NAME?.trim(); - if (envBranch) { - const envDefault = process.env.CLOUDFLAREBASE_DEFAULT_BRANCH?.trim(); - const isDefault = envDefault - ? envBranch === envDefault - : envBranch === 'main' || envBranch === 'master'; - return isDefault ? null : envBranch; - } - - const current = await run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { - cwd: projectDir, - capture: true - }); - if (current.code !== 0) return null; // not a git repository -> root - const branch = current.stdout.trim(); - if (!branch || branch === 'HEAD') return null; // detached -> root - - const head = await run('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], { - cwd: projectDir, - capture: true - }); - const defaultBranch = head.code === 0 ? head.stdout.trim().replace(/^origin\//, '') : null; - if (defaultBranch ? branch === defaultBranch : branch === 'main' || branch === 'master') { - return null; - } - return branch; -} - -/** Charset-sanitizes a git branch name into a cloudflarebase branch name. */ -export function sanitizeBranchName(branch: string): string { - const cleaned = branch - .toLowerCase() - .replace(/[^a-z0-9-]+/g, '-') - .replace(/-{2,}/g, '-') - .replace(/^-+|-+$/g, '') - .slice(0, 16) - .replace(/-+$/, ''); - if (!BRANCH_NAME.test(cleaned)) { - throw new UserError( - `Git branch "${branch}" cannot be mapped to a cloudflarebase branch name.`, - 'Branch names are 1-16 lowercase letters, numbers, and hyphens. Pass --branch to pick one.' - ); - } - return cleaned; -} - -/** Composes the target project id for a deploy, honouring the 48-char ceiling. */ +/** Composes the target project id, honouring the 48-char ceiling. */ export function targetProjectId(root: string, branch: string | null): string { if (!PROJECT_ID.test(root) || root.includes('--')) { throw new UserError(`"${root}" is not a valid root project id.`); @@ -195,117 +90,3 @@ export function targetProjectId(root: string, branch: string | null): string { } return id; } - -export interface CollectedFile { - /** URL path (`/index.html`) for assets, file name for modules. */ - name: string; - bytes: Buffer; -} - -// Mirror the agent's caps so failures happen before any upload starts. -const MAX_ASSET_COUNT = 5000; -const MAX_ASSET_TOTAL_BYTES = 40 * 1024 * 1024; - -/** - * Recursively collects an assets directory into `/path` entries, honouring - * the directory's `.assetsignore` (framework adapters write one - SvelteKit's - * lists `_worker.js`, which must never deploy as a public asset). - */ -export async function collectAssets(dir: string): Promise { - const files: CollectedFile[] = []; - let total = 0; - const ignore = assetFilter( - await readFile(path.join(dir, '.assetsignore'), 'utf8').catch(() => null) - ); - - async function walk(current: string, prefix: string): Promise { - const entries = await readdir(current, { withFileTypes: true }); - for (const entry of entries) { - // Dotfiles stay out of a deploy - except `.well-known`, which is a - // public directory by definition (specs put challenges there). - if (entry.name === 'node_modules') continue; - if (entry.name.startsWith('.') && entry.name !== '.well-known') continue; - const relative = prefix ? `${prefix.slice(1)}/${entry.name}` : entry.name; - if (ignore.ignores(relative)) continue; - const full = path.join(current, entry.name); - if (entry.isDirectory()) { - await walk(full, `${prefix}/${entry.name}`); - continue; - } - if (!entry.isFile()) continue; - const bytes = await readFile(full); - total += bytes.length; - files.push({ name: `${prefix}/${entry.name}`, bytes }); - if (files.length > MAX_ASSET_COUNT) { - throw new UserError(`More than ${MAX_ASSET_COUNT} asset files in ${dir}.`); - } - if (total > MAX_ASSET_TOTAL_BYTES) { - throw new UserError(`Assets in ${dir} exceed 40 MB.`); - } - } - } - - await walk(dir, ''); - return files; -} - -/** The user's wrangler config, when one exists - the managed deploy respects - * its `main`, `assets.directory`, and compatibility settings. */ -export async function readUserWranglerConfig(projectDir: string): Promise<{ - main?: string; - assetsDirectory?: string; - compatibilityDate?: string; - compatibilityFlags?: string[]; -} | null> { - for (const name of ['wrangler.jsonc', 'wrangler.json']) { - let raw: string; - try { - raw = await readFile(path.join(projectDir, name), 'utf8'); - } catch { - continue; - } - const config = parseJsonc(raw) as Record | undefined; - if (!config) return null; - const assets = config.assets as { directory?: string } | undefined; - return { - main: typeof config.main === 'string' ? config.main : undefined, - assetsDirectory: typeof assets?.directory === 'string' ? assets.directory : undefined, - compatibilityDate: - typeof config.compatibility_date === 'string' ? config.compatibility_date : undefined, - compatibilityFlags: Array.isArray(config.compatibility_flags) - ? (config.compatibility_flags as string[]).filter( - (flag) => typeof flag === 'string' && /^[a-z0-9_]+$/.test(flag) - ) - : undefined - }; - } - return null; -} - -/** Finds the assets directory: explicit override, wrangler config, then the - * conventional build outputs. Null when nothing exists. */ -export async function findAssetsDirectory( - projectDir: string, - override: string | undefined, - fromWrangler: string | undefined -): Promise { - const candidates = override - ? [override] - : fromWrangler - ? [fromWrangler] - : // `.output/public` before `public`: a Nuxt repo has BOTH, and - // `public/` is its source directory, not the built site. - ['dist', 'build', '.output/public', 'public', '_site', 'out']; - for (const candidate of candidates) { - const full = path.resolve(projectDir, candidate); - try { - if ((await stat(full)).isDirectory()) return full; - } catch { - // keep looking - } - } - if (override || fromWrangler) { - throw new UserError(`Assets directory "${override ?? fromWrangler}" does not exist.`); - } - return null; -} diff --git a/cli/src/lib/oidc.ts b/cli/src/lib/oidc.ts deleted file mode 100644 index 3b53ccb9..00000000 --- a/cli/src/lib/oidc.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { UserError } from './log.js'; - -/** - * GitHub Actions OIDC (Phase B). - * - * A repository connected through the console's GitHub App deploys with NO - * stored credential: the workflow asks GitHub for a short-lived token - * describing the repository it is running in, and the console verifies that - * against GitHub's public keys. There is nothing to rotate and nothing to - * leak, which is the same reason this repository publishes to npm this way. - * - * The CLI mints it rather than the workflow, so the generated YAML stays a - * checkout, a build, and a deploy - no token plumbing for anyone to get - * wrong. - */ - -/** Whether this process is inside a GitHub Actions run. */ -export function inGithubActions(): boolean { - return process.env.GITHUB_ACTIONS === 'true'; -} - -/** - * Mints an identity token for `audience` (the console's origin - GitHub will - * mint for any audience asked, so the console checks it to refuse tokens - * minted for someone else). - * - * Null when this is not an Actions run. Throws when it IS one but the token - * service is unreachable, because the cause is nearly always a missing - * `id-token: write` permission and silently falling through to "no - * credential" would report that as a confusing 401 much later. - */ -export async function actionsIdToken(audience: string): Promise { - const url = process.env.ACTIONS_ID_TOKEN_REQUEST_URL; - const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN; - if (!url || !requestToken) { - if (!inGithubActions()) return null; - throw new UserError( - 'This workflow cannot mint a GitHub identity token.', - 'Add `permissions: { id-token: write }` to the job, or set CLOUDFLAREBASE_DEPLOY_TOKEN.' - ); - } - - const response = await fetch(`${url}&audience=${encodeURIComponent(audience)}`, { - headers: { authorization: `Bearer ${requestToken}`, accept: 'application/json' } - }).catch((cause: unknown) => { - throw new UserError( - "Could not reach GitHub's identity token service.", - cause instanceof Error ? cause.message : undefined - ); - }); - if (!response.ok) { - throw new UserError( - `GitHub refused to mint an identity token (${response.status}).`, - 'Check that the job declares `permissions: { id-token: write }`.' - ); - } - - const body = (await response.json().catch(() => null)) as { value?: string } | null; - if (!body?.value) { - throw new UserError("GitHub's identity token response carried no token."); - } - return body.value; -} From c99fbe433a7c58300199254b1cd1857585440362 Mon Sep 17 00:00:00 2001 From: clibequilibrium <105135724+clibequilibrium@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:09:37 -0400 Subject: [PATCH 3/8] Remove the hosting agent package The console and CLI no longer reference it, so the package, its deploy workflows, its quality/release entries, and the docs describing hosting go too. --- .../hosting-agent-deploy-preview.yaml | 60 - .../workflows/hosting-agent-deploy-prod.yaml | 60 - .github/workflows/quality.yaml | 10 +- .github/workflows/release.yaml | 4 +- AGENTS.md | 9 +- README.md | 14 +- agents/auth/wrangler.jsonc | 2 +- agents/hosting/.gitignore | 167 - agents/hosting/.prettierrc | 6 - agents/hosting/AGENTS.md | 135 - agents/hosting/CLAUDE.md | 4 - agents/hosting/LICENSE | 202 - agents/hosting/NOTICE | 18 - agents/hosting/README.md | 57 - agents/hosting/cloudflarebase.agent.json | 55 - agents/hosting/drizzle.config.ts | 11 - agents/hosting/drizzle/0000_red_la_nuit.sql | 19 - .../hosting/drizzle/0001_slippery_prism.sql | 36 - .../hosting/drizzle/0002_amazing_kronos.sql | 1 - .../hosting/drizzle/meta/0000_snapshot.json | 137 - .../hosting/drizzle/meta/0001_snapshot.json | 349 - .../hosting/drizzle/meta/0002_snapshot.json | 356 - agents/hosting/drizzle/meta/_journal.json | 27 - agents/hosting/drizzle/migrations.js | 14 - agents/hosting/package-lock.json | 5912 ------ agents/hosting/package.json | 73 - .../hosting/scripts/generate-migrations.mjs | 73 - agents/hosting/src/agent.ts | 1433 -- agents/hosting/src/bindings.test-d.ts | 42 - agents/hosting/src/bindings.ts | 73 - agents/hosting/src/build-env.ts | 20 - agents/hosting/src/build-env.unit.test.ts | 32 - agents/hosting/src/cloudflare.ts | 447 - agents/hosting/src/cloudflare.unit.test.ts | 168 - agents/hosting/src/crypto.ts | 73 - agents/hosting/src/crypto.unit.test.ts | 71 - agents/hosting/src/db/schema.ts | 116 - agents/hosting/src/env.d.ts | 8 - agents/hosting/src/index.ts | 424 - agents/hosting/src/migrations.ts | 102 - agents/hosting/src/outbound.ts | 20 - agents/hosting/src/route-access.ts | 105 - agents/hosting/src/route-access.unit.test.ts | 73 - agents/hosting/src/schemas.ts | 97 - agents/hosting/src/tar.fixture.ts | 20 - agents/hosting/src/tar.ts | 171 - agents/hosting/src/tar.unit.test.ts | 94 - agents/hosting/template/worker-entry.ts | 20 - .../hosting/template/wrangler-fragment.jsonc | 71 - agents/hosting/tsconfig.build.json | 32 - agents/hosting/tsconfig.json | 50 - agents/hosting/worker-configuration.d.ts | 14893 ---------------- agents/hosting/wrangler.jsonc | 277 - agents/hosting/wrangler.outbound.jsonc | 38 - agents/storage/src/access.ts | 4 +- agents/storage/src/demo.ts | 2 +- agents/storage/src/index.ts | 4 +- agents/storage/src/schemas.ts | 2 +- agents/storage/wrangler.jsonc | 2 +- 59 files changed, 18 insertions(+), 26777 deletions(-) delete mode 100644 .github/workflows/hosting-agent-deploy-preview.yaml delete mode 100644 .github/workflows/hosting-agent-deploy-prod.yaml delete mode 100644 agents/hosting/.gitignore delete mode 100644 agents/hosting/.prettierrc delete mode 100644 agents/hosting/AGENTS.md delete mode 100644 agents/hosting/CLAUDE.md delete mode 100644 agents/hosting/LICENSE delete mode 100644 agents/hosting/NOTICE delete mode 100644 agents/hosting/README.md delete mode 100644 agents/hosting/cloudflarebase.agent.json delete mode 100644 agents/hosting/drizzle.config.ts delete mode 100644 agents/hosting/drizzle/0000_red_la_nuit.sql delete mode 100644 agents/hosting/drizzle/0001_slippery_prism.sql delete mode 100644 agents/hosting/drizzle/0002_amazing_kronos.sql delete mode 100644 agents/hosting/drizzle/meta/0000_snapshot.json delete mode 100644 agents/hosting/drizzle/meta/0001_snapshot.json delete mode 100644 agents/hosting/drizzle/meta/0002_snapshot.json delete mode 100644 agents/hosting/drizzle/meta/_journal.json delete mode 100644 agents/hosting/drizzle/migrations.js delete mode 100644 agents/hosting/package-lock.json delete mode 100644 agents/hosting/package.json delete mode 100644 agents/hosting/scripts/generate-migrations.mjs delete mode 100644 agents/hosting/src/agent.ts delete mode 100644 agents/hosting/src/bindings.test-d.ts delete mode 100644 agents/hosting/src/bindings.ts delete mode 100644 agents/hosting/src/build-env.ts delete mode 100644 agents/hosting/src/build-env.unit.test.ts delete mode 100644 agents/hosting/src/cloudflare.ts delete mode 100644 agents/hosting/src/cloudflare.unit.test.ts delete mode 100644 agents/hosting/src/crypto.ts delete mode 100644 agents/hosting/src/crypto.unit.test.ts delete mode 100644 agents/hosting/src/db/schema.ts delete mode 100644 agents/hosting/src/env.d.ts delete mode 100644 agents/hosting/src/index.ts delete mode 100644 agents/hosting/src/migrations.ts delete mode 100644 agents/hosting/src/outbound.ts delete mode 100644 agents/hosting/src/route-access.ts delete mode 100644 agents/hosting/src/route-access.unit.test.ts delete mode 100644 agents/hosting/src/schemas.ts delete mode 100644 agents/hosting/src/tar.fixture.ts delete mode 100644 agents/hosting/src/tar.ts delete mode 100644 agents/hosting/src/tar.unit.test.ts delete mode 100644 agents/hosting/template/worker-entry.ts delete mode 100644 agents/hosting/template/wrangler-fragment.jsonc delete mode 100644 agents/hosting/tsconfig.build.json delete mode 100644 agents/hosting/tsconfig.json delete mode 100644 agents/hosting/worker-configuration.d.ts delete mode 100644 agents/hosting/wrangler.jsonc delete mode 100644 agents/hosting/wrangler.outbound.jsonc diff --git a/.github/workflows/hosting-agent-deploy-preview.yaml b/.github/workflows/hosting-agent-deploy-preview.yaml deleted file mode 100644 index 4087cacb..00000000 --- a/.github/workflows/hosting-agent-deploy-preview.yaml +++ /dev/null @@ -1,60 +0,0 @@ -name: Hosting Agent Preview - -on: - workflow_dispatch: - push: - branches: - - preview - paths: - - 'agents/hosting/**' - - 'src/lib/**' - -permissions: - contents: read - -jobs: - deploy-preview: - runs-on: ubuntu-latest - name: Deploy Preview - # Cloudflare credentials live in this GitHub environment, not repository - # secrets, so only jobs that declare it - running refs its protection - # rules allow - can read them. - environment: preview - steps: - - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ vars.NODE_VERSION }} - - - name: Install dependencies - run: npm ci - - # The dispatch-namespace binding in env.preview names this service, so - # it must exist before the agent deploys. Preview has its OWN outbound - # (hosting-outbound-preview): this workflow redeploys it on every push, - # and a shared instance would put preview-branch outbound code on - # production apps' egress. - - name: Deploy Outbound Worker - uses: cloudflare/wrangler-action@v3 - with: - wranglerVersion: ${{ vars.WRANGLER_VERSION }} - apiToken: ${{ secrets.CF_API_TOKEN }} - accountId: ${{ secrets.CF_ACCOUNT_ID}} - workingDirectory: 'agents/hosting' - packageManager: npm - command: deploy --config wrangler.outbound.jsonc --env preview - - - name: Deploy Preview - uses: cloudflare/wrangler-action@v3 - with: - wranglerVersion: ${{ vars.WRANGLER_VERSION }} - apiToken: ${{ secrets.CF_API_TOKEN }} - accountId: ${{ secrets.CF_ACCOUNT_ID}} - workingDirectory: 'agents/hosting' - packageManager: npm - # Needs the cfbase-apps-preview namespace to exist first (Phase B - # launch checklist); serving stays off (env.preview has no - # HOSTING_DOMAIN), so this only exercises the upload path. - environment: preview diff --git a/.github/workflows/hosting-agent-deploy-prod.yaml b/.github/workflows/hosting-agent-deploy-prod.yaml deleted file mode 100644 index 72ccaf5f..00000000 --- a/.github/workflows/hosting-agent-deploy-prod.yaml +++ /dev/null @@ -1,60 +0,0 @@ -name: Hosting Agent Prod - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - 'agents/hosting/**' - - 'src/lib/**' - -permissions: - contents: read - -jobs: - deploy-prod: - runs-on: ubuntu-latest - name: Deploy Prod - # Cloudflare credentials live in this GitHub environment, not repository - # secrets, so only jobs that declare it - running refs its protection - # rules allow - can read them. - environment: production - steps: - - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ vars.NODE_VERSION }} - - - name: Install dependencies - run: npm ci - - # The dispatch-namespace binding in env.production names this service, - # so it must exist before the agent deploys. - - name: Deploy Outbound Worker - uses: cloudflare/wrangler-action@v3 - with: - wranglerVersion: ${{ vars.WRANGLER_VERSION }} - apiToken: ${{ secrets.CF_API_TOKEN }} - accountId: ${{ secrets.CF_ACCOUNT_ID}} - workingDirectory: 'agents/hosting' - packageManager: npm - command: deploy --config wrangler.outbound.jsonc - - - name: Deploy Prod - uses: cloudflare/wrangler-action@v3 - with: - wranglerVersion: ${{ vars.WRANGLER_VERSION }} - apiToken: ${{ secrets.CF_API_TOKEN }} - accountId: ${{ secrets.CF_ACCOUNT_ID}} - workingDirectory: 'agents/hosting' - packageManager: npm - # The top level of wrangler.jsonc is the self-hosted default; - # cloudflarebase.com's own configuration lives in env.production, - # which pins the Worker name back to `hosting-agent`. This deploy - # needs the cfbase.dev zone, the cfbase-apps namespace, and the - # CF_ACCOUNT_ID/CF_HOSTING_API_TOKEN worker secrets to exist first - # (Phase B launch checklist). - environment: production diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 16685684..a988af9a 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -63,15 +63,7 @@ jobs: run: npm run test:unit working-directory: agents/storage - - name: Typecheck the hosting agent - run: npx tsc --noEmit - working-directory: agents/hosting - - - name: Unit-test the hosting agent - run: npm run test:unit - working-directory: agents/hosting - - # The CLI is a fifth npm project; the root install does not cover it. + # The CLI is a fourth npm project; the root install does not cover it. - name: Install CLI dependencies run: npm ci working-directory: cli diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 277f18e0..8d4f2cae 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,7 +13,7 @@ name: Release # for an approval click. # # Tags remain the manual escape hatch: pushing auth-v1.2.3, db-v1.2.3, -# storage-v1.2.3, hosting-v1.2.3, or cli-v1.2.3 publishes that one package, +# storage-v1.2.3, or cli-v1.2.3 publishes that one package, # refusing a tag whose version does not match the package.json it points at. # # Publishing uses npm trusted publishing (OIDC): npmjs.com is configured to @@ -38,7 +38,6 @@ on: - 'auth-v*.*.*' - 'db-v*.*.*' - 'storage-v*.*.*' - - 'hosting-v*.*.*' - 'cli-v*.*.*' permissions: @@ -69,7 +68,6 @@ jobs: { name: '@cloudflarebase/auth', dir: 'agents/auth', prefix: 'auth-v' }, { name: '@cloudflarebase/db', dir: 'agents/db', prefix: 'db-v' }, { name: '@cloudflarebase/storage', dir: 'agents/storage', prefix: 'storage-v' }, - { name: '@cloudflarebase/hosting', dir: 'agents/hosting', prefix: 'hosting-v' }, { name: '@cloudflarebase/cli', dir: 'cli', prefix: 'cli-v' }, ]; const refType = process.env.GITHUB_REF_TYPE; diff --git a/AGENTS.md b/AGENTS.md index 03325160..dd469dbb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ that only matter inside it. Read that one before changing an agent. ## Repository shape -Six separate npm projects, separate Wrangler configs, separate generated `Env` +Five separate npm projects, separate Wrangler configs, separate generated `Env` types. | Path | Worker | Durable Objects | @@ -18,7 +18,6 @@ types. | `agents/auth` | `auth-agent` | `AuthAgent` | | `agents/db` | `db-agent` | `DbAgent` `DbCollection` `DbTable` `DbGateway` `DbView` | | `agents/storage` | `storage-agent` | `StorageAgent` `StorageBucket` | -| `agents/hosting` | `hosting-agent` | `HostingAgent` (+ an outbound worker) | | `cli` | none | `@cloudflarebase/cli`, runs on a consumer's machine | **Never import runtime code or generated Worker types across those @@ -86,15 +85,15 @@ service binding the console already authorized. No agent ever sees the bearer. ## Commands ```bash -npm run dev # auth :8788, db :8789, hosting :8790, storage :8791, web :5173 +npm run dev # auth :8788, db :8789, storage :8791, web :5173 npm run check # svelte-check npm run lint # prettier + eslint npm test # full Playwright suite against real workerd ``` All three checks run in CI and all three must pass. The e2e suite boots a -production-mirroring stack — the built SvelteKit worker on `:8797` plus the four -agents on `:8798`–`:8801`, real service bindings, real DO SQLite. It is the +production-mirroring stack — the built SvelteKit worker on `:8797` plus the three +agents on `:8798`, `:8799` and `:8801`, real service bindings, real DO SQLite. It is the check that actually catches things. Each agent is its own TypeScript project with its own unit tests: diff --git a/README.md b/README.md index 5bf07c8f..f7be76ca 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,6 @@ isolation by architecture, not by a `WHERE` clause. flipped without shipping a release. - **Storage** — buckets of files on R2: public/auth/owner access modes, signed URLs, multipart uploads, a file browser in the console. -- **Hosting** — static sites and Workers at `.cfbase.dev`, deployed from - the CLI or on every git push. Use it hosted at [cloudflarebase.com](https://cloudflarebase.com), or run the whole stack on your own Cloudflare account. Same code either way. @@ -46,18 +44,16 @@ const baseUrl = 'https://cloudflarebase.com/api/projects/'; Add your app's origin under the project's **Settings** — that list is the CSRF allowlist, and an unlisted origin gets a 403. -To host the front end too: +For a server credential, from the CLI: ```bash npm install -g @cloudflarebase/cli cloudflarebase login -cloudflarebase init # links this directory to a project + app -cloudflarebase deploy # -> https://.cfbase.dev +cloudflarebase init # links this directory to a project +cloudflarebase key create server --env-file # writes a service key to .env.local ``` -Or connect the GitHub repo from the Hosting page and every push deploys. -Branches serve at `-.cfbase.dev`. Limits: 5 projects per org, -5 branches per project, 10 apps per project. +Limits: 5 projects per org, 5 branches per project. ## Self-host @@ -83,7 +79,6 @@ Everything else is optional and degrades cleanly when absent: - **Storage** needs R2 — create a bucket and add the `BUCKET` binding described in `agents/storage/wrangler.jsonc`. -- **Hosting** needs Workers for Platforms (paid); deploys 503 without it. - **Auth-event charts** need Analytics Engine, a free dashboard toggle; then add the two lines shown in `agents/auth/wrangler.jsonc`. - Google/GitHub sign-in, email, and Sentry are opt-in secrets. @@ -92,7 +87,6 @@ Prefer buttons? One per Worker, same order as `deploy:all`: [auth](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflarebase/cloudflarebase/tree/main/agents/auth) · [db](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflarebase/cloudflarebase/tree/main/agents/db) · [storage](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflarebase/cloudflarebase/tree/main/agents/storage) · -[hosting](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflarebase/cloudflarebase/tree/main/agents/hosting) · [dashboard](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflarebase/cloudflarebase) ## Add the agents to a Worker you already have diff --git a/agents/auth/wrangler.jsonc b/agents/auth/wrangler.jsonc index 0c9cd067..42d2ea10 100644 --- a/agents/auth/wrangler.jsonc +++ b/agents/auth/wrangler.jsonc @@ -158,7 +158,7 @@ // verification, the same configuration preview rehearses. It took // effect the moment this agent deployed, which is safe because the // ownership-aware web worker was already live (its OpenAPI serves - // /branches and /hosting/*) - a stranger who registers gets their + // /branches) - a stranger who registers gets their // own personal org and sees nothing else. Mail (the EMAIL binding // + EMAIL_FROM below) is what makes "open" effective; without it // the agent reports "claimed" and 503s sign-up rather than diff --git a/agents/hosting/.gitignore b/agents/hosting/.gitignore deleted file mode 100644 index 4138168d..00000000 --- a/agents/hosting/.gitignore +++ /dev/null @@ -1,167 +0,0 @@ -# Logs - -logs -_.log -npm-debug.log_ -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) - -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# Runtime data - -pids -_.pid -_.seed -\*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover - -lib-cov - -# Coverage directory used by tools like istanbul - -coverage -\*.lcov - -# nyc test coverage - -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) - -.grunt - -# Bower dependency directory (https://bower.io/) - -bower_components - -# node-waf configuration - -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) - -build/Release - -# Dependency directories - -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) - -web_modules/ - -# TypeScript cache - -\*.tsbuildinfo - -# Optional npm cache directory - -.npm - -# Optional eslint cache - -.eslintcache - -# Optional stylelint cache - -.stylelintcache - -# Microbundle cache - -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history - -.node_repl_history - -# Output of 'npm pack' - -\*.tgz - -# Yarn Integrity file - -.yarn-integrity - -# parcel-bundler cache (https://parceljs.org/) - -.cache -.parcel-cache - -# Next.js build output - -.next -out - -# Nuxt.js build / generate output - -.nuxt -dist - -# Gatsby files - -.cache/ - -# Comment in the public line in if your project uses Gatsby and not Next.js - -# https://nextjs.org/blog/next-9-1#public-directory-support - -# public - -# vuepress build output - -.vuepress/dist - -# vuepress v2.x temp and cache directory - -.temp -.cache - -# Docusaurus cache and generated files - -.docusaurus - -# Serverless directories - -.serverless/ - -# FuseBox cache - -.fusebox/ - -# DynamoDB Local files - -.dynamodb/ - -# TernJS port file - -.tern-port - -# Stores VSCode versions used for testing VSCode extensions - -.vscode-test - -# yarn v2 - -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.\* - -# wrangler project - -.dev.vars* -!.dev.vars.example -.env* -!.env.example -.wrangler/ diff --git a/agents/hosting/.prettierrc b/agents/hosting/.prettierrc deleted file mode 100644 index d910b4e4..00000000 --- a/agents/hosting/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "printWidth": 100, - "singleQuote": true, - "semi": true, - "useTabs": true -} \ No newline at end of file diff --git a/agents/hosting/AGENTS.md b/agents/hosting/AGENTS.md deleted file mode 100644 index 065c2095..00000000 --- a/agents/hosting/AGENTS.md +++ /dev/null @@ -1,135 +0,0 @@ -# @cloudflarebase/hosting - -Static sites and Workers on Workers for Platforms, served at -`.`. One `HostingAgent` per project. - -Read the root [AGENTS.md](../../AGENTS.md) first. - -## Two workers, and one of them has two roles - -`wrangler.jsonc` deploys `hosting-agent`; `wrangler.outbound.jsonc` deploys the -dispatch namespace's **outbound worker** separately. `deploy:production` runs -both, outbound first — deploy them together or the namespace loses its egress -hook. - -`hosting-agent` itself branches on **hostname**: - -- **Serving** — a request on the `*.` wildcard route takes the first - host label and does `env.DISPATCH.get(hostLabel)`. Zero lookup, because the - script name *is* the full subdomain; dispatch never parses it back into app - and branch. An unclaimed subdomain is simply no script, and gets the branded - 404. -- **Agent surface** on every other hostname — `/health`, `/internal/*`, and - `routeAgentRequest`. `/internal/*` is reachable only over the dashboard's - service binding: this Worker has no public route beyond the wildcard, and the - serve branch swallows every wildcard request before dispatch gets here. - -The serve path never touches `HostingAgent`. Control plane and data plane are -fully separate, same as storage. - -## What only holds here - -- **Subdomains are pushed by the console**, resolved from the control-plane - `app` claims table. The agent never derives or accepts one from a request — - that is what makes the deploy route safe on the operator surface: it can only - ever deploy to subdomains the console recorded for *this* project. -- **The cache shim is not optional.** Workers for Platforms runs namespaced - scripts in untrusted mode, which is what isolates tenants — and in that mode - `caches.default` throws and `request.cf` is absent. Frameworks call it - unconditionally (SvelteKit's adapter opens every request with - `caches.default.match(req)`), so an unmodified build 500s on every SSR route. - Each module deploy gets a generated entry that imports the shim **first** and - re-exports the customer's entry: ES modules evaluate imports depth-first in - source order, so a module-scope capture like `var s2 = caches.default` picks up - the neutralised methods. Generated names carry a `__cfbase` prefix so a - customer file can never be shadowed. The namespace-level `trusted_workers` - flag would fix it by turning tenant isolation off for every app at once — the - opposite trade. -- **Asset manifest hashes are salted with the project id** (SHA-256 over - `\0`, truncated to the API's 32 hex chars). Assets are - deduplicated *by hash* within a namespace, so unsalted hashes would let one - tenant probe another's content. -- **A tarball is attacker-controlled input.** `tar.ts` is bounded everywhere: the - gunzip reader checks the ceiling *as bytes arrive*, never after — waiting - until the end is what a decompression bomb wants. -- **Deploys are parsed in memory**, so the limits in `agent.ts` are sized against - the 128 MB isolate: 20 MB modules, 5000 assets, 40 MB asset total, 25 MB per - file. Cloudflare's own 10 MB-compressed script ceiling still applies at upload. -- **Hosting has no public routes at all.** Its whole manifest route table is - operator: `/overview`, `/apps/*`, `/deploys`. - -## App environments - -Three stores per app, three different trust shapes — never mix them up: - -- **Runtime vars** (`app_vars`): plaintext at rest on purpose — they upload as - `plain_text` bindings anyway, and DO storage is the trust boundary. Applied - on every deploy as platform > stored > the CLI's `meta.vars` (the console is - the canonical editor), and PATCHed onto the live script on edit. The patch - replaces the WHOLE `plain_text` set (that is how a deletion disappears), so - `apps.last_deploy_vars` snapshots the CLI's vars to rebuild it without the - CLI present. Store-first: an edit is never lost to a transient API failure. -- **Runtime secrets** (`app_secrets`): the value is write-through to - Cloudflare's script settings (that is the runtime binding; deploys carry - `keep_bindings: ['secret_text']` so it survives), AND stored AES-256-GCM - under `HOSTING_MASTER_KEY` when the install has one, so builds receive it — - the Pages model: frameworks inline env at build time. The AAD carries a - `runtime\0` prefix so this ciphertext and a build secret's are never - interchangeable. Null ciphertext (keyless install, or rows from before the - column existed) means runtime-only: the bundle skips it rather than failing - the build. The operator surface still answers names only. Delete uses the - per-script secrets endpoint (404-tolerant), then drops the row — a failed - CF delete keeps the row, which is the honest state. -- **Build env** (`build_vars` / `build_secrets`, ROOT project only — - connection-scoped): build-only OVERRIDES. The bundle the GitHub Actions - workflow fetches via its OIDC token is runtime vars+secrets merged under - these (`src/build-env.ts` pins build-wins-on-collision). Build secrets are - AES-256-GCM under `HOSTING_MASTER_KEY` (`src/crypto.ts`: `v1::` - versioned format, row-bound AAD `\0`). Decrypted values only - ever transit the service-binding-only `/internal/.../build-env` route; the - operator surface answers names only. No key ⇒ build-secret writes 503; - encrypted rows with no key ⇒ the bundle route fails loud rather than build - without secrets. - -`eraseApp` clears all three even without an `apps` row — claim-only apps can -be configured before their first deploy. - -## Analytics - -The serve path writes one Analytics Engine data point per dispatched request -(`index1` = subdomain, `blob1` = status, `double1` = duration ms) — the -subdomain IS the script name, so the DO joins it back to an app with zero -lookups. Unclaimed-subdomain 404s are deliberately not recorded. The read -(`GET /apps/:app/analytics`) mirrors the auth agent's ladder: `connected` -(CF_ACCOUNT_ID + CF_ANALYTICS_API_TOKEN + WAE_DATASET) → `local` (the -LOCAL_ANALYTICS D1 stand-in local dev and e2e read back) → `write-only` → -`error`, and a failed query answers 200 with zeroed series, never a 5xx. The -top-level wrangler.jsonc declares NO dataset — Analytics Engine is an -account-level opt-in and the binding fails a fresh clone's deploy (code -10089, the auth precedent). - -## Degradation - -Workers for Platforms is a paid product. Without `CF_ACCOUNT_ID` + -`CF_HOSTING_API_TOKEN` + a dispatch namespace, deploys answer 503 and the rest -of the stack is unaffected. `HOSTING_DOMAIN` empty disables the serving path. -`HOSTING_MASTER_KEY` unset disables only build-secret writes. - -Stub mode (`HOSTING_STUB=true`) stubs the Cloudflare API, never the logic: -deploys are recorded, secrets store their names (and answer ok), build-secret -encryption is REAL (local/test pin a dev master key as a var), and analytics -read the local D1 — which is what lets e2e exercise every contract on every -run. The two shapes only a live namespace proves — the settings PATCH on an -assets-only script and the per-script secrets DELETE — are gated by the -opt-in `RUN_HOSTING_E2E` spec; do not call those surfaces "verified" on a new -account until it has passed. - -## Commands - -```bash -npm run dev # wrangler dev --env local, :8790 -npm run typecheck -npm run test:unit # route-access, tar, cloudflare, crypto -npm run deploy:outbound # the outbound worker — needed before the agent -npm run migrations -``` diff --git a/agents/hosting/CLAUDE.md b/agents/hosting/CLAUDE.md deleted file mode 100644 index ff2b4a18..00000000 --- a/agents/hosting/CLAUDE.md +++ /dev/null @@ -1,4 +0,0 @@ -Guidance for this package lives in AGENTS.md, imported here so the two can -never drift. - -@AGENTS.md diff --git a/agents/hosting/LICENSE b/agents/hosting/LICENSE deleted file mode 100644 index dd8e76f4..00000000 --- a/agents/hosting/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2026 The Cloudflarebase Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/agents/hosting/NOTICE b/agents/hosting/NOTICE deleted file mode 100644 index 5e2a3b13..00000000 --- a/agents/hosting/NOTICE +++ /dev/null @@ -1,18 +0,0 @@ -Cloudflarebase -Copyright 2026 The Cloudflarebase Authors - -This product includes software developed by The Cloudflarebase Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - ------------------------------------------------------------------------------- - -Cloudflarebase is an independent open-source project. It is not affiliated with, -endorsed by, or sponsored by Cloudflare, Inc. - -"Cloudflare", "Workers", "Durable Objects", "D1", "R2", "KV", and related marks -are trademarks of Cloudflare, Inc. They are used here only to describe the -platform this software is built on, as permitted by nominative fair use. This -project claims no rights in them. diff --git a/agents/hosting/README.md b/agents/hosting/README.md deleted file mode 100644 index 673eb11a..00000000 --- a/agents/hosting/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# @cloudflarebase/hosting - -Apps and functions on Cloudflare Workers for Platforms - static assets and -server code in one deploy, served at your subdomain. The hosting primitive -behind [Cloudflarebase](https://github.com/cloudflarebase/cloudflarebase). - -One `HostingAgent` Durable Object per project orchestrates deploys (asset -upload sessions, script puts, per-script tags and limits) while the same -worker serves every app on a wildcard domain by dispatching the host label -straight into the namespace - the script name IS the subdomain, so serving -needs zero lookups. - -## Install - -```sh -npx @cloudflarebase/cli add hosting -``` - -The CLI installs this package, merges `template/wrangler-fragment.jsonc` into -your `wrangler.jsonc`, prepends the entrypoint re-export, and reruns -`wrangler types`. Workers for Platforms is a paid Cloudflare add-on; the -fragment's header documents the manual steps that turn recorded deploys into -served apps (dispatch namespace, API token, wildcard route). - -## What it serves - -- An "app" is one user Worker in the dispatch namespace: a static site, an - SSR frontend, backend functions, or all three in one artifact (modules plus - an asset manifest). -- Deploys go through the Cloudflarebase console (bare `cloudflarebase init`, - then `cloudflarebase deploy`), which resolves the subdomain claim in its - control plane and pushes it here - this agent owns no global state. -- Every script is uploaded with a `pid-` tag (project erase - deletes by tag), fixed CPU/subrequest limits applied at dispatch, and - injected `PROJECT_ID` / `CLOUDFLAREBASE_URL` vars so the SDK works out of - the box. - -## What your Worker serves - -Nothing, over HTTP. Every route this agent has - `/overview`, `/apps/*` -(deploys, secrets), `/deploys`, the state-sync socket, `/internal/*` - -deploys code, mints subdomains, or writes secrets, and none of them -authenticate a caller: they are meant to sit behind a console that already -has. Mounted on your own public Worker they answer 404. - -Serving deployed apps is unaffected - that runs on your wildcard hostname, -ahead of everything else. - -Drive deploys from your own code through the `HostingAgent` Durable Object -namespace binding, which no HTTP caller can reach. To serve the routes over -HTTP instead, put your own authentication in front and set -`"EXPOSE_OPERATOR_API": "true"` - only on a Worker with no public hostname of -its own. - -## License - -Apache-2.0. See NOTICE. diff --git a/agents/hosting/cloudflarebase.agent.json b/agents/hosting/cloudflarebase.agent.json deleted file mode 100644 index 301b1a46..00000000 --- a/agents/hosting/cloudflarebase.agent.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "manifestVersion": 1, - "name": "hosting", - "title": "Hosting", - "description": "Apps and functions on Workers for Platforms - static assets and server code in one deploy, served at your subdomain.", - "packageName": "@cloudflarebase/hosting", - "worker": "hosting-agent", - "durableObjects": [{ "class": "HostingAgent", "scope": "perProject" }], - "entrypoint": { "assertEnvType": "AssertHostingAgentEnv" }, - "erase": { "method": "DELETE", "path": "/internal/projects/:projectId" }, - "bindings": { - "analyticsEngine": [ - { "binding": "HOSTING_REQUESTS", "dataset": "cloudflarebase_hosting_requests" } - ] - }, - "secrets": { - "generated": [], - "optional": [ - "CF_ACCOUNT_ID", - "CF_HOSTING_API_TOKEN", - "HOSTING_MASTER_KEY", - "CF_ANALYTICS_API_TOKEN" - ] - }, - "vars": { - "SENTRY_DSN": { - "default": "", - "hint": "Error reporting; empty disables it. A var, not a secret - a DSN is public by design." - }, - "HOSTING_DOMAIN": { - "default": "", - "hint": "Apex domain apps serve under (e.g. cfbase.dev). Empty disables the serving path." - }, - "DISPATCH_NAMESPACE": { - "default": "", - "hint": "Workers for Platforms dispatch namespace name the DISPATCH binding targets." - }, - "WAE_DATASET": { - "default": "cloudflarebase_hosting_requests", - "hint": "Analytics Engine dataset for per-request analytics; reads need CF_ANALYTICS_API_TOKEN." - } - }, - "routes": [ - { "path": "/overview", "access": "operator" }, - { "path": "/apps/*", "access": "operator" }, - { "path": "/deploys", "access": "operator" } - ], - "proxy": { "apiPrefix": "hosting", "agentBasePath": "" }, - "permissions": [], - "console": { - "section": "Hosting", - "icon": "rocket", - "pages": [{ "path": "/hosting", "title": "Apps", "testId": "nav-hosting", "icon": "rocket" }] - } -} diff --git a/agents/hosting/drizzle.config.ts b/agents/hosting/drizzle.config.ts deleted file mode 100644 index 0e7c3343..00000000 --- a/agents/hosting/drizzle.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'drizzle-kit'; - -// Durable Object SQLite: drizzle-kit emits an importable migrations bundle -// (./drizzle/migrations.js) that the agent applies at runtime via -// drizzle-orm/durable-sqlite/migrator. -export default defineConfig({ - out: './drizzle', - schema: './src/db/schema.ts', - dialect: 'sqlite', - driver: 'durable-sqlite', -}); diff --git a/agents/hosting/drizzle/0000_red_la_nuit.sql b/agents/hosting/drizzle/0000_red_la_nuit.sql deleted file mode 100644 index fd1219f9..00000000 --- a/agents/hosting/drizzle/0000_red_la_nuit.sql +++ /dev/null @@ -1,19 +0,0 @@ -CREATE TABLE `apps` ( - `name` text PRIMARY KEY NOT NULL, - `subdomain` text NOT NULL, - `created_at` integer NOT NULL, - `last_deploy_at` integer, - `deploy_count` integer DEFAULT 0 NOT NULL -); ---> statement-breakpoint -CREATE TABLE `deploys` ( - `id` text PRIMARY KEY NOT NULL, - `app_name` text NOT NULL, - `subdomain` text NOT NULL, - `status` text NOT NULL, - `has_worker` integer NOT NULL, - `asset_count` integer NOT NULL, - `asset_bytes` integer NOT NULL, - `module_bytes` integer NOT NULL, - `created_at` integer NOT NULL -); diff --git a/agents/hosting/drizzle/0001_slippery_prism.sql b/agents/hosting/drizzle/0001_slippery_prism.sql deleted file mode 100644 index cce5281c..00000000 --- a/agents/hosting/drizzle/0001_slippery_prism.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE TABLE `app_secrets` ( - `app_name` text NOT NULL, - `name` text NOT NULL, - `created_at` integer NOT NULL, - `updated_at` integer NOT NULL, - PRIMARY KEY(`app_name`, `name`) -); ---> statement-breakpoint -CREATE TABLE `app_vars` ( - `app_name` text NOT NULL, - `name` text NOT NULL, - `value` text NOT NULL, - `created_at` integer NOT NULL, - `updated_at` integer NOT NULL, - PRIMARY KEY(`app_name`, `name`) -); ---> statement-breakpoint -CREATE TABLE `build_secrets` ( - `app_name` text NOT NULL, - `name` text NOT NULL, - `ciphertext` text NOT NULL, - `created_at` integer NOT NULL, - `updated_at` integer NOT NULL, - PRIMARY KEY(`app_name`, `name`) -); ---> statement-breakpoint -CREATE TABLE `build_vars` ( - `app_name` text NOT NULL, - `name` text NOT NULL, - `value` text NOT NULL, - `created_at` integer NOT NULL, - `updated_at` integer NOT NULL, - PRIMARY KEY(`app_name`, `name`) -); ---> statement-breakpoint -ALTER TABLE `apps` ADD `last_deploy_vars` text; \ No newline at end of file diff --git a/agents/hosting/drizzle/0002_amazing_kronos.sql b/agents/hosting/drizzle/0002_amazing_kronos.sql deleted file mode 100644 index 3c1abd3d..00000000 --- a/agents/hosting/drizzle/0002_amazing_kronos.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `app_secrets` ADD `ciphertext` text; \ No newline at end of file diff --git a/agents/hosting/drizzle/meta/0000_snapshot.json b/agents/hosting/drizzle/meta/0000_snapshot.json deleted file mode 100644 index 2a6caeaf..00000000 --- a/agents/hosting/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "d74ad2e0-6cb5-473f-a27d-6cfff48bdde7", - "prevId": "00000000-0000-0000-0000-000000000000", - "tables": { - "apps": { - "name": "apps", - "columns": { - "name": { - "name": "name", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "subdomain": { - "name": "subdomain", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_deploy_at": { - "name": "last_deploy_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "deploy_count": { - "name": "deploy_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "deploys": { - "name": "deploys", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subdomain": { - "name": "subdomain", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "has_worker": { - "name": "has_worker", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "asset_count": { - "name": "asset_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "asset_bytes": { - "name": "asset_bytes", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "module_bytes": { - "name": "module_bytes", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/agents/hosting/drizzle/meta/0001_snapshot.json b/agents/hosting/drizzle/meta/0001_snapshot.json deleted file mode 100644 index 188d22cc..00000000 --- a/agents/hosting/drizzle/meta/0001_snapshot.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "fd9d68a8-b16b-44c9-9c15-a258777a870e", - "prevId": "d74ad2e0-6cb5-473f-a27d-6cfff48bdde7", - "tables": { - "app_secrets": { - "name": "app_secrets", - "columns": { - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "app_secrets_app_name_name_pk": { - "columns": [ - "app_name", - "name" - ], - "name": "app_secrets_app_name_name_pk" - } - }, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "app_vars": { - "name": "app_vars", - "columns": { - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "app_vars_app_name_name_pk": { - "columns": [ - "app_name", - "name" - ], - "name": "app_vars_app_name_name_pk" - } - }, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "apps": { - "name": "apps", - "columns": { - "name": { - "name": "name", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "subdomain": { - "name": "subdomain", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_deploy_at": { - "name": "last_deploy_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "deploy_count": { - "name": "deploy_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "last_deploy_vars": { - "name": "last_deploy_vars", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "build_secrets": { - "name": "build_secrets", - "columns": { - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "ciphertext": { - "name": "ciphertext", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "build_secrets_app_name_name_pk": { - "columns": [ - "app_name", - "name" - ], - "name": "build_secrets_app_name_name_pk" - } - }, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "build_vars": { - "name": "build_vars", - "columns": { - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "build_vars_app_name_name_pk": { - "columns": [ - "app_name", - "name" - ], - "name": "build_vars_app_name_name_pk" - } - }, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "deploys": { - "name": "deploys", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subdomain": { - "name": "subdomain", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "has_worker": { - "name": "has_worker", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "asset_count": { - "name": "asset_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "asset_bytes": { - "name": "asset_bytes", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "module_bytes": { - "name": "module_bytes", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/agents/hosting/drizzle/meta/0002_snapshot.json b/agents/hosting/drizzle/meta/0002_snapshot.json deleted file mode 100644 index 69171256..00000000 --- a/agents/hosting/drizzle/meta/0002_snapshot.json +++ /dev/null @@ -1,356 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "3bd97b28-75fd-40aa-bf0f-cd89f32ee8d1", - "prevId": "fd9d68a8-b16b-44c9-9c15-a258777a870e", - "tables": { - "app_secrets": { - "name": "app_secrets", - "columns": { - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "ciphertext": { - "name": "ciphertext", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "app_secrets_app_name_name_pk": { - "columns": [ - "app_name", - "name" - ], - "name": "app_secrets_app_name_name_pk" - } - }, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "app_vars": { - "name": "app_vars", - "columns": { - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "app_vars_app_name_name_pk": { - "columns": [ - "app_name", - "name" - ], - "name": "app_vars_app_name_name_pk" - } - }, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "apps": { - "name": "apps", - "columns": { - "name": { - "name": "name", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "subdomain": { - "name": "subdomain", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_deploy_at": { - "name": "last_deploy_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "deploy_count": { - "name": "deploy_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": 0 - }, - "last_deploy_vars": { - "name": "last_deploy_vars", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "build_secrets": { - "name": "build_secrets", - "columns": { - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "ciphertext": { - "name": "ciphertext", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "build_secrets_app_name_name_pk": { - "columns": [ - "app_name", - "name" - ], - "name": "build_secrets_app_name_name_pk" - } - }, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "build_vars": { - "name": "build_vars", - "columns": { - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "updated_at": { - "name": "updated_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "build_vars_app_name_name_pk": { - "columns": [ - "app_name", - "name" - ], - "name": "build_vars_app_name_name_pk" - } - }, - "uniqueConstraints": {}, - "checkConstraints": {} - }, - "deploys": { - "name": "deploys", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "app_name": { - "name": "app_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "subdomain": { - "name": "subdomain", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "has_worker": { - "name": "has_worker", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "asset_count": { - "name": "asset_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "asset_bytes": { - "name": "asset_bytes", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "module_bytes": { - "name": "module_bytes", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "checkConstraints": {} - } - }, - "views": {}, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/agents/hosting/drizzle/meta/_journal.json b/agents/hosting/drizzle/meta/_journal.json deleted file mode 100644 index f22283c4..00000000 --- a/agents/hosting/drizzle/meta/_journal.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "version": "7", - "dialect": "sqlite", - "entries": [ - { - "idx": 0, - "version": "6", - "when": 1786556374566, - "tag": "0000_red_la_nuit", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1787197325964, - "tag": "0001_slippery_prism", - "breakpoints": true - }, - { - "idx": 2, - "version": "6", - "when": 1787253423135, - "tag": "0002_amazing_kronos", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/agents/hosting/drizzle/migrations.js b/agents/hosting/drizzle/migrations.js deleted file mode 100644 index e14960b1..00000000 --- a/agents/hosting/drizzle/migrations.js +++ /dev/null @@ -1,14 +0,0 @@ -import journal from './meta/_journal.json'; -import m0000 from './0000_red_la_nuit.sql'; -import m0001 from './0001_slippery_prism.sql'; -import m0002 from './0002_amazing_kronos.sql'; - - export default { - journal, - migrations: { - m0000, -m0001, -m0002 - } - } - \ No newline at end of file diff --git a/agents/hosting/package-lock.json b/agents/hosting/package-lock.json deleted file mode 100644 index bc2f21d6..00000000 --- a/agents/hosting/package-lock.json +++ /dev/null @@ -1,5912 +0,0 @@ -{ - "name": "@cloudflarebase/hosting", - "version": "0.4.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@cloudflarebase/hosting", - "version": "0.4.0", - "license": "Apache-2.0", - "dependencies": { - "@sentry/cloudflare": "^10.66.0", - "agents": "^0.17.4", - "drizzle-orm": "^0.45.2", - "zod": "^4.4.3" - }, - "devDependencies": { - "@types/node": "^26.1.1", - "drizzle-kit": "^0.31.10", - "tsx": "^4.20.0", - "typescript": "^5.5.2", - "wrangler": "^4.118.0" - } - }, - "node_modules/@apm-js-collab/code-transformer": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.18.1.tgz", - "integrity": "sha512-u1Hb6bHjWtkSpiprwVP6YaHC1DTN4RAU3zYkUDUe7WMnJwdyU1pwTL9dFKiSJB9IiLue/EQovmyx6xhU7FFtAQ==", - "license": "Apache-2.0", - "dependencies": { - "@types/estree": "^1.0.8", - "astring": "^1.9.0", - "esquery": "^1.7.0", - "meriyah": "^6.1.4", - "semifies": "^1.0.0", - "source-map": "^0.6.0" - }, - "bin": { - "code-transformer": "cli.js" - } - }, - "node_modules/@apm-js-collab/code-transformer-bundler-plugins": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.4.tgz", - "integrity": "sha512-nAfOeZPSUAQvJa1iFT/5oCrTm5YQhMMrfCNthNnaXHZiOQhu1KGuLoIx7HtbAi3wfwaBYLaICPIeenIaEwcXIg==", - "license": "MIT", - "dependencies": { - "@apm-js-collab/code-transformer": "^0.18.1", - "es-module-lexer": "^2.1.0", - "magic-string": "^0.30.21", - "module-details-from-path": "^1.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@apm-js-collab/tracing-hooks": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.13.0.tgz", - "integrity": "sha512-mTvWz9rnQwx1U3h0XPTHaX7bgfkpipLLTQyjlC2cdhQpQEuoLT0AGzoydeoq2NxfEVv6fWOOETcSbb2nptleyw==", - "license": "Apache-2.0", - "dependencies": { - "@apm-js-collab/code-transformer": "^0.18.0", - "debug": "^4.4.1", - "module-details-from-path": "^1.0.4" - } - }, - "node_modules/@babel/code-frame": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz", - "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^8.0.0", - "js-tokens": "^10.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0.tgz", - "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==", - "license": "MIT", - "peer": true, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/core": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.1.tgz", - "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^8.0.0", - "@babel/generator": "^8.0.0", - "@babel/helper-compilation-targets": "^8.0.0", - "@babel/helpers": "^8.0.0", - "@babel/parser": "^8.0.0", - "@babel/template": "^8.0.0", - "@babel/traverse": "^8.0.0", - "@babel/types": "^8.0.0", - "@types/gensync": "^1.0.5", - "convert-source-map": "^2.0.0", - "empathic": "^2.0.1", - "gensync": "^1.0.0-beta.2", - "import-meta-resolve": "^4.2.0", - "json5": "^2.2.3", - "obug": "^2.1.1", - "semver": "^7.7.3" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", - "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^8.0.0", - "@babel/types": "^8.0.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "@types/jsesc": "^2.5.0", - "jsesc": "^3.0.2" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz", - "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz", - "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/compat-data": "^8.0.0", - "@babel/helper-validator-option": "^8.0.0", - "browserslist": "^4.24.0", - "lru-cache": "^11.0.0", - "semver": "^7.7.3" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.1.tgz", - "integrity": "sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^8.0.0", - "@babel/helper-member-expression-to-functions": "^8.0.0", - "@babel/helper-optimise-call-expression": "^8.0.0", - "@babel/helper-replace-supers": "^8.0.1", - "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", - "@babel/traverse": "^8.0.0", - "semver": "^7.7.3" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - }, - "peerDependencies": { - "@babel/core": "^8.0.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz", - "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.0.tgz", - "integrity": "sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^8.0.0", - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz", - "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz", - "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - }, - "peerDependencies": { - "@babel/core": "^8.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz", - "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^8.0.0", - "@babel/helper-optimise-call-expression": "^8.0.0", - "@babel/traverse": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - }, - "peerDependencies": { - "@babel/core": "^8.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz", - "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^8.0.0", - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", - "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", - "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", - "license": "MIT", - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", - "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/helpers": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0.tgz", - "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/template": "^8.0.0", - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/parser": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", - "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", - "license": "MIT", - "dependencies": { - "@babel/types": "^8.0.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz", - "integrity": "sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^8.0.1", - "@babel/helper-plugin-utils": "^8.0.1", - "@babel/plugin-syntax-decorators": "^8.0.1" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - }, - "peerDependencies": { - "@babel/core": "^8.0.0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz", - "integrity": "sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^8.0.1" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - }, - "peerDependencies": { - "@babel/core": "^8.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.7.tgz", - "integrity": "sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==", - "license": "MIT", - "dependencies": { - "core-js-pure": "^3.48.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", - "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^8.0.0", - "@babel/parser": "^8.0.0", - "@babel/types": "^8.0.0" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/traverse": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.4.tgz", - "integrity": "sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^8.0.0", - "@babel/generator": "^8.0.0", - "@babel/helper-globals": "^8.0.0", - "@babel/parser": "^8.0.4", - "@babel/template": "^8.0.0", - "@babel/types": "^8.0.4", - "obug": "^2.1.1" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@babel/types": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", - "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^8.0.0", - "@babel/helper-validator-identifier": "^8.0.4" - }, - "engines": { - "node": "^22.18.0 || >=24.11.0" - } - }, - "node_modules/@cfworker/json-schema": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", - "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", - "license": "MIT" - }, - "node_modules/@cloudflare/codemode": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@cloudflare/codemode/-/codemode-0.4.4.tgz", - "integrity": "sha512-GL0IF7NiHwAC222riEYGX75NHcnhTornAPfRI0Mj3Ig7C1y9H89FL0xs+pdflIF74eecNkzynsA+JdCIkGvKeA==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.15", - "acorn": "^8.17.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.0", - "@tanstack/ai": ">=0.8.0 <1.0.0", - "ai": "^6.0.0", - "zod": "^4.0.0" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - }, - "@tanstack/ai": { - "optional": true - }, - "ai": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", - "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", - "devOptional": true, - "license": "MIT OR Apache-2.0", - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@cloudflare/unenv-preset": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz", - "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==", - "devOptional": true, - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "unenv": "2.0.0-rc.24", - "workerd": ">1.20260305.0 <2.0.0-0" - }, - "peerDependenciesMeta": { - "workerd": { - "optional": true - } - } - }, - "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20260811.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260811.1.tgz", - "integrity": "sha512-i5jqz+ywtOefr0AJbiAc8qxBLfSim/B0WJG7aW3B+pWnoVfMJdUQvi+BWcFKZJ0MoCci3KadTx6g31VfuEEqpQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20260811.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260811.1.tgz", - "integrity": "sha512-NoOUM/nvaDdm2Onlnz33FikWjtatzulNtvwvy4xs0IrHaTCHwC0c8NwIt6s+AI13FkDs02/vm2I3GTPLCT9+hQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20260811.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260811.1.tgz", - "integrity": "sha512-sdYq2jL1AD1supa3fsi5O4zTB28wSjvTHj7Migh6/ts8EROPdvrSwv+rdGHhv8HJNAz/wbIAY3wZsi1Rw4uUIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20260811.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260811.1.tgz", - "integrity": "sha512-RIRv4shbu1kg05sD+DHTpSFCNnb5Dl2SkPDMUykqZa508tkPqe7VVw7gO0Q5msTBGyL0FfFrLuRxwwfA8u5Sow==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20260811.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260811.1.tgz", - "integrity": "sha512-g6VquwjASlYAibcNW/0E6Zszht4qLkmnXOGwIjjRHl2A0Qz48kVeMcGvyH6eA0G9U3OzZojjYFpP+YeyQmmdjw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workers-types": { - "version": "5.20260812.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-5.20260812.1.tgz", - "integrity": "sha512-eG2aQdUWH8RYEubpBjUE98L2G57JYzGEcI8PgROTjt9YjunbEnPBHP4371Usj8v8pTWcXtbOmI+LRCXF4KhJgQ==", - "license": "MIT OR Apache-2.0", - "peer": true - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@drizzle-team/brocli": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", - "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", - "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild-kit/core-utils": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", - "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", - "deprecated": "Merged into tsx: https://tsx.hirok.io", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.18.20", - "source-map-support": "^0.5.21" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/@esbuild-kit/esm-loader": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", - "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", - "deprecated": "Merged into tsx: https://tsx.hirok.io", - "dev": true, - "license": "MIT", - "dependencies": { - "@esbuild-kit/core-utils": "^3.3.2", - "get-tsconfig": "^4.7.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.17", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz", - "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@img/colour": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", - "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz", - "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.1" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz", - "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.1" - } - }, - "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz", - "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "dependencies": { - "@img/sharp-wasm32": "0.35.2" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz", - "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz", - "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz", - "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz", - "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz", - "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz", - "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz", - "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz", - "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz", - "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz", - "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz", - "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.1" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz", - "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.1" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz", - "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.1" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz", - "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.1" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz", - "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.1" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz", - "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.1" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz", - "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz", - "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.1" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz", - "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==", - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.11.1" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz", - "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/sharp-wasm32": "0.35.2" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz", - "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz", - "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz", - "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.144.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", - "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@poppinss/colors": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", - "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "kleur": "^4.1.5" - } - }, - "node_modules/@poppinss/dumper": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", - "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@poppinss/colors": "^4.1.5", - "@sindresorhus/is": "^7.0.2", - "supports-color": "^10.0.0" - } - }, - "node_modules/@poppinss/exception": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", - "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", - "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", - "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", - "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", - "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", - "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", - "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", - "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", - "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", - "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", - "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", - "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", - "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", - "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/plugin-babel": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.3.tgz", - "integrity": "sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==", - "license": "MIT", - "dependencies": { - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=22.12.0 || ^24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.29.0 || ^8.0.0-rc.1", - "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1", - "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1", - "rolldown": "^1.0.0-rc.5", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@babel/plugin-transform-runtime": { - "optional": true - }, - "@babel/runtime": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "license": "MIT", - "peer": true - }, - "node_modules/@sentry/cloudflare": { - "version": "10.70.0", - "resolved": "https://registry.npmjs.org/@sentry/cloudflare/-/cloudflare-10.70.0.tgz", - "integrity": "sha512-jlr9txw3lQrbc6KQn9FzlpgC1KcB36u7aIGSlUhXAZ2MB93DgwjuTnHF20qQHxmHbZylg7CtHoMq3rGiTXAgpg==", - "license": "MIT", - "dependencies": { - "@opentelemetry/api": "^1.9.1", - "@sentry/core": "10.70.0", - "@sentry/server-utils": "10.70.0", - "magic-string": "~0.30.21" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.x || ^5.x", - "wrangler": "^4.x" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - }, - "wrangler": { - "optional": true - } - } - }, - "node_modules/@sentry/conventions": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", - "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@sentry/core": { - "version": "10.70.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.70.0.tgz", - "integrity": "sha512-ozhCTDqg89oB4XmWfAwuHshABpvT7AkRpaPnogopPfMAaI61G1t8EKCJ4W7aum8JSBonlfyjPCyW5oYZFm0KvA==", - "license": "MIT", - "dependencies": { - "@sentry/conventions": "^0.16.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@sentry/server-utils": { - "version": "10.70.0", - "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.70.0.tgz", - "integrity": "sha512-rzegZjMFFgCp3o+N8+XU13rfSvz4B+f8rU0ijBGrQcHdMNyfsFDTu1UTm262JofmrV2u+s+D0u0vFTnqtOGkbA==", - "license": "MIT", - "dependencies": { - "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.3", - "@apm-js-collab/tracing-hooks": "^0.13.0", - "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.70.0", - "meriyah": "^6.1.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@sindresorhus/is": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", - "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@speed-highlight/core": { - "version": "1.2.24", - "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", - "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", - "devOptional": true, - "license": "CC0-1.0" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "license": "MIT" - }, - "node_modules/@types/gensync": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz", - "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==", - "license": "MIT", - "peer": true - }, - "node_modules/@types/jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", - "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agents": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/agents/-/agents-0.17.4.tgz", - "integrity": "sha512-K6YRbpD3VcwdTOPBlDgI4dILAwkhXo5cdxTlVF0IvUwQEKfMPawmH8E/QMXTN8CPGHqVYgYFACxTyk6nKlK+vg==", - "license": "MIT", - "dependencies": { - "@babel/plugin-proposal-decorators": "^8.0.2", - "@cfworker/json-schema": "^4.1.1", - "@cloudflare/codemode": "^0.4.3", - "@modelcontextprotocol/sdk": "1.29.0", - "@rolldown/plugin-babel": "^0.2.3", - "cron-schedule": "^6.0.0", - "esbuild": "^0.28.1", - "mimetext": "^3.0.28", - "nanoid": "^5.1.16", - "partyserver": "^0.5.8", - "partysocket": "1.3.0", - "yaml": "^2.9.0", - "yargs": "^18.0.0" - }, - "bin": { - "agents": "dist/cli/index.js" - }, - "peerDependencies": { - "@ai-sdk/react": "^3.0.204", - "@tanstack/ai": ">=0.10.2 <1.0.0", - "@x402/core": "^2.0.0", - "@x402/evm": "^2.0.0", - "ai": "^6.0.0", - "chat": "^4.29.0", - "just-bash": "^3.0.0", - "react": "^19.0.0", - "vite": ">=6.0.0 <9.0.0", - "zod": "^4.0.0" - }, - "peerDependenciesMeta": { - "@ai-sdk/react": { - "optional": true - }, - "@tanstack/ai": { - "optional": true - }, - "@x402/core": { - "optional": true - }, - "@x402/evm": { - "optional": true - }, - "ai": { - "optional": true - }, - "chat": { - "optional": true - }, - "just-bash": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "license": "MIT", - "bin": { - "astring": "bin/astring" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", - "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/browserslist": { - "version": "4.28.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", - "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.11.12", - "caniuse-lite": "^1.0.30001809", - "electron-to-chromium": "^1.5.402", - "node-releases": "^2.0.53", - "update-browserslist-db": "^1.3.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001809", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", - "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0", - "peer": true - }, - "node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT", - "peer": true - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/core-js-pure": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.50.0.tgz", - "integrity": "sha512-6GP3Pxz4IKyWjAfa747vIu/jilB5z29JWROLqH/b+pXVcpgh6tM06ZIBwSuglgVqzDYURhOK6oEzTrG0bCHitA==", - "hasInstallScript": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cron-schedule": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-6.0.0.tgz", - "integrity": "sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/drizzle-kit": { - "version": "0.31.10", - "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", - "integrity": "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@drizzle-team/brocli": "^0.10.2", - "@esbuild-kit/esm-loader": "^2.5.5", - "esbuild": "^0.25.4", - "tsx": "^4.21.0" - }, - "bin": { - "drizzle-kit": "bin.cjs" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/drizzle-orm": { - "version": "0.45.2", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", - "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==", - "license": "Apache-2.0", - "peerDependencies": { - "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=4", - "@electric-sql/pglite": ">=0.2.0", - "@libsql/client": ">=0.10.0", - "@libsql/client-wasm": ">=0.10.0", - "@neondatabase/serverless": ">=0.10.0", - "@op-engineering/op-sqlite": ">=2", - "@opentelemetry/api": "^1.4.1", - "@planetscale/database": ">=1.13", - "@prisma/client": "*", - "@tidbcloud/serverless": "*", - "@types/better-sqlite3": "*", - "@types/pg": "*", - "@types/sql.js": "*", - "@upstash/redis": ">=1.34.7", - "@vercel/postgres": ">=0.8.0", - "@xata.io/client": "*", - "better-sqlite3": ">=7", - "bun-types": "*", - "expo-sqlite": ">=14.0.0", - "gel": ">=2", - "knex": "*", - "kysely": "*", - "mysql2": ">=2", - "pg": ">=8", - "postgres": ">=3", - "sql.js": ">=1", - "sqlite3": ">=5" - }, - "peerDependenciesMeta": { - "@aws-sdk/client-rds-data": { - "optional": true - }, - "@cloudflare/workers-types": { - "optional": true - }, - "@electric-sql/pglite": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@libsql/client-wasm": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@op-engineering/op-sqlite": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@tidbcloud/serverless": { - "optional": true - }, - "@types/better-sqlite3": { - "optional": true - }, - "@types/pg": { - "optional": true - }, - "@types/sql.js": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "bun-types": { - "optional": true - }, - "expo-sqlite": { - "optional": true - }, - "gel": { - "optional": true - }, - "knex": { - "optional": true - }, - "kysely": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "postgres": { - "optional": true - }, - "prisma": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - } - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.405", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.405.tgz", - "integrity": "sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew==", - "license": "ISC", - "peer": true - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/empathic": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", - "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/error-stack-parser-es": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", - "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", - "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-polyfill": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz", - "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==", - "license": "MIT" - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", - "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz", - "integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-tsconfig": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.2.tgz", - "integrity": "sha512-XpwZALwwl/BaKTAyC6+c5T8y6kCg2jk+XGqOVrKIQmW49pNypYLMRjCUXqa28tQgJlhS2RlzP7sc+Rx7W6qsfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hono": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", - "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", - "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jose": { - "version": "6.2.8", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.8.tgz", - "integrity": "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-base64": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.2.tgz", - "integrity": "sha512-6zayE8QlUdiweYI6cETD/XBSqFcoCUlufn/29PJR99r82x1yDnIprRca0YvAYpAW+ez0GuQkVBC6xG5QkD7OjA==", - "license": "BSD-3-Clause" - }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "peer": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "license": "BlueOak-1.0.0", - "peer": true, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meriyah": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz", - "integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==", - "license": "ISC", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimetext": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/mimetext/-/mimetext-3.0.28.tgz", - "integrity": "sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@babel/runtime-corejs3": "^7.26.0", - "js-base64": "^3.7.7", - "mime-types": "^2.1.35" - }, - "funding": { - "type": "patreon", - "url": "https://patreon.com/muratgozel" - } - }, - "node_modules/mimetext/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimetext/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/miniflare": { - "version": "5.20260811.0-alpha", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260811.0-alpha.tgz", - "integrity": "sha512-sypXsD5fjY88fZNedPqnwrwR1dwfnfbfW7MfvMyIfPJdtRiCCOpUnjWGeFVYYZ+0fQVICye6Juu+vZgzTEx8XA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "sharp": "0.35.2", - "undici": "7.29.0", - "workerd": "1.20260811.1", - "ws": "8.21.0", - "youch": "4.1.0-beta.10" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "5.1.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", - "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-releases": { - "version": "2.0.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", - "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT", - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/partyserver": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.5.10.tgz", - "integrity": "sha512-t2B3mhTL1IOxCsj8rZgn4TxTuub7oLhypjc1/fGPNsbgnn9OsHms6uR3QEd5bFJ/7xrFo771j3DdUlll8cxgBg==", - "license": "ISC", - "dependencies": { - "nanoid": "^5.1.9" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20260424.1 || ^5.20260703.1" - } - }, - "node_modules/partysocket": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.3.0.tgz", - "integrity": "sha512-1zToNyolZFK/7nuAw/K2bZrNzFqaZyRoCEkS+9vG6WSC5ikrN6qWRe96q6ImU51uptz2r+dAwSkwhJVdQi4LiA==", - "license": "MIT", - "dependencies": { - "event-target-polyfill": "^0.0.4" - }, - "peerDependencies": { - "react": ">=17" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC", - "peer": true - }, - "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/rolldown": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", - "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@oxc-project/types": "=0.144.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.2.4", - "@rolldown/binding-darwin-arm64": "1.2.4", - "@rolldown/binding-darwin-x64": "1.2.4", - "@rolldown/binding-freebsd-x64": "1.2.4", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", - "@rolldown/binding-linux-arm64-gnu": "1.2.4", - "@rolldown/binding-linux-arm64-musl": "1.2.4", - "@rolldown/binding-linux-ppc64-gnu": "1.2.4", - "@rolldown/binding-linux-s390x-gnu": "1.2.4", - "@rolldown/binding-linux-x64-gnu": "1.2.4", - "@rolldown/binding-linux-x64-musl": "1.2.4", - "@rolldown/binding-openharmony-arm64": "1.2.4", - "@rolldown/binding-win32-arm64-msvc": "1.2.4", - "@rolldown/binding-win32-x64-msvc": "1.2.4" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semifies": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semifies/-/semifies-1.0.0.tgz", - "integrity": "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==", - "license": "Apache-2.0" - }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/sharp": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", - "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@img/colour": "^1.1.0", - "detect-libc": "^2.1.2", - "semver": "^7.8.4" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.2", - "@img/sharp-darwin-x64": "0.35.2", - "@img/sharp-freebsd-wasm32": "0.35.2", - "@img/sharp-libvips-darwin-arm64": "1.3.1", - "@img/sharp-libvips-darwin-x64": "1.3.1", - "@img/sharp-libvips-linux-arm": "1.3.1", - "@img/sharp-libvips-linux-arm64": "1.3.1", - "@img/sharp-libvips-linux-ppc64": "1.3.1", - "@img/sharp-libvips-linux-riscv64": "1.3.1", - "@img/sharp-libvips-linux-s390x": "1.3.1", - "@img/sharp-libvips-linux-x64": "1.3.1", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", - "@img/sharp-libvips-linuxmusl-x64": "1.3.1", - "@img/sharp-linux-arm": "0.35.2", - "@img/sharp-linux-arm64": "0.35.2", - "@img/sharp-linux-ppc64": "0.35.2", - "@img/sharp-linux-riscv64": "0.35.2", - "@img/sharp-linux-s390x": "0.35.2", - "@img/sharp-linux-x64": "0.35.2", - "@img/sharp-linuxmusl-arm64": "0.35.2", - "@img/sharp-linuxmusl-x64": "0.35.2", - "@img/sharp-webcontainers-wasm32": "0.35.2", - "@img/sharp-win32-arm64": "0.35.2", - "@img/sharp-win32-ia32": "0.35.2", - "@img/sharp-win32-x64": "0.35.2" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-width": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", - "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/tsx": { - "version": "4.23.12", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", - "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unenv": { - "version": "2.0.0-rc.24", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", - "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", - "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerd": { - "version": "1.20260811.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260811.1.tgz", - "integrity": "sha512-kh+FFm55JQ4ssxhHZV9VPdMQq3D1nHxNJgwxMtWGD4dGppJvLySdguTRDKgeNTvgq6heSz+6TTXyPSDGj8Yllw==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20260811.1", - "@cloudflare/workerd-darwin-arm64": "1.20260811.1", - "@cloudflare/workerd-linux-64": "1.20260811.1", - "@cloudflare/workerd-linux-arm64": "1.20260811.1", - "@cloudflare/workerd-windows-64": "1.20260811.1" - } - }, - "node_modules/wrangler": { - "version": "4.122.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.122.0.tgz", - "integrity": "sha512-qkskzgQ76Y1qvVe5JARgvc3RISq6BC2rPoxQhFoKH1dKIwQc3GDFttQ/7m2OfeQ+tmQRzynv2dy/DXnxFCj2Lw==", - "devOptional": true, - "license": "MIT OR Apache-2.0", - "dependencies": { - "@cloudflare/kv-asset-handler": "0.5.0", - "@cloudflare/unenv-preset": "2.16.1", - "blake3-wasm": "2.1.5", - "esbuild": "0.28.1", - "miniflare": "5.20260811.0-alpha", - "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.24", - "workerd": "1.20260811.1" - }, - "bin": { - "cf-wrangler": "bin/cf-wrangler.js", - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=22.0.0" - }, - "optionalDependencies": { - "fsevents": "2.3.3" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^5.20260811.1" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - } - } - }, - "node_modules/wrangler/node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/wrangler/node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "devOptional": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, - "node_modules/wrangler/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", - "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^8.2.1", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/youch": { - "version": "4.1.0-beta.10", - "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", - "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@poppinss/colors": "^4.1.5", - "@poppinss/dumper": "^0.6.4", - "@speed-highlight/core": "^1.2.7", - "cookie": "^1.0.2", - "youch-core": "^0.3.3" - } - }, - "node_modules/youch-core": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", - "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@poppinss/exception": "^1.2.2", - "error-stack-parser-es": "^1.0.5" - } - }, - "node_modules/youch/node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } -} diff --git a/agents/hosting/package.json b/agents/hosting/package.json deleted file mode 100644 index 91fdab0e..00000000 --- a/agents/hosting/package.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "name": "@cloudflarebase/hosting", - "version": "0.7.0", - "description": "Apps and functions on Cloudflare Workers for Platforms - static assets and server code in one deploy, one Durable Object orchestrator per project - the hosting primitive behind Cloudflarebase.", - "license": "Apache-2.0", - "type": "module", - "repository": { - "type": "git", - "url": "git+https://github.com/cloudflarebase/cloudflarebase.git", - "directory": "agents/hosting" - }, - "homepage": "https://github.com/cloudflarebase/cloudflarebase/tree/main/agents/hosting#readme", - "bugs": "https://github.com/cloudflarebase/cloudflarebase/issues", - "keywords": [ - "cloudflare", - "workers", - "workers-for-platforms", - "durable-objects", - "hosting", - "static-assets", - "agents", - "cloudflarebase" - ], - "publishConfig": { - "access": "public" - }, - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "./wrangler-fragment.jsonc": "./template/wrangler-fragment.jsonc", - "./cloudflarebase.agent.json": "./cloudflarebase.agent.json", - "./package.json": "./package.json" - }, - "files": [ - "dist", - "template", - "NOTICE", - "cloudflarebase.agent.json" - ], - "scripts": { - "build": "tsc -p tsconfig.build.json", - "typecheck": "tsc --noEmit", - "test:unit": "node --import tsx --test src/route-access.unit.test.ts src/tar.unit.test.ts src/cloudflare.unit.test.ts src/crypto.unit.test.ts src/build-env.unit.test.ts", - "prepublishOnly": "npm run build", - "deploy": "wrangler deploy", - "deploy:outbound": "wrangler deploy --config wrangler.outbound.jsonc", - "deploy:outbound:preview": "wrangler deploy --config wrangler.outbound.jsonc --env preview", - "deploy:preview": "npm run deploy:outbound:preview && wrangler deploy --env preview", - "dev": "wrangler dev --env local --persist-to=../../.wrangler/state/ --ip 0.0.0.0 --port 8790", - "start": "wrangler dev", - "cf-typegen": "wrangler types", - "dev:test": "wrangler dev --env test --ip 127.0.0.1 --port 8800 --persist-to ../../.wrangler/test-state/hosting-agent", - "deploy:production": "npm run deploy:outbound && wrangler deploy --env production", - "migrations": "drizzle-kit generate && node scripts/generate-migrations.mjs" - }, - "devDependencies": { - "@types/node": "^26.1.1", - "drizzle-kit": "^0.31.10", - "tsx": "^4.20.0", - "typescript": "^5.5.2", - "wrangler": "^4.118.0" - }, - "dependencies": { - "@sentry/cloudflare": "^10.66.0", - "agents": "^0.17.4", - "drizzle-orm": "^0.45.2", - "zod": "^4.4.3" - } -} diff --git a/agents/hosting/scripts/generate-migrations.mjs b/agents/hosting/scripts/generate-migrations.mjs deleted file mode 100644 index 93ae36be..00000000 --- a/agents/hosting/scripts/generate-migrations.mjs +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Inlines the generated Drizzle migrations into a plain TypeScript module. - * - * drizzle-kit emits `drizzle/migrations.js`, which imports each `.sql` file as - * a module. That only works because Wrangler is configured to bundle `.sql` as - * a Text module - a `rules` entry that every consumer of this agent would have - * to replicate in their own wrangler config for the package to build at all. - * - * Emitting the SQL as ordinary string literals removes that requirement: the - * agent becomes an npm dependency you can import with no bundler - * configuration, and this repository loses a gotcha it had to document. - * - * Run after `drizzle-kit generate`; `npm run migrations` does both. - */ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const here = path.dirname(fileURLToPath(import.meta.url)); -const drizzleDir = path.join(here, '..', 'drizzle'); -const outFile = path.join(here, '..', 'src', 'migrations.ts'); - -const journalPath = path.join(drizzleDir, 'meta', '_journal.json'); -if (!fs.existsSync(journalPath)) { - console.error(`No journal at ${journalPath} - run \`npx drizzle-kit generate\` first.`); - process.exit(1); -} - -const journal = JSON.parse(fs.readFileSync(journalPath, 'utf8')); -const entries = journal.entries ?? []; - -/** Backtick-safe: migrations are SQL, but never assume about generated text. */ -function toTemplateLiteral(sql) { - return '`' + sql.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${') + '`'; -} - -const parts = []; -for (const entry of entries) { - const file = path.join(drizzleDir, `${entry.tag}.sql`); - if (!fs.existsSync(file)) { - console.error(`Journal lists ${entry.tag} but ${file} is missing.`); - process.exit(1); - } - const key = `m${String(entry.idx).padStart(4, '0')}`; - parts.push({ key, sql: fs.readFileSync(file, 'utf8') }); -} - -const banner = `// Generated by scripts/generate-migrations.mjs - do not edit. -// -// The SQL is inlined rather than imported from .sql files so this agent can be -// consumed as a plain npm dependency, with no Wrangler Text-module rule needed -// in the consumer's config. Regenerate with \`npm run migrations\`. -`; - -// No `as const`: drizzle's MigrationConfig takes a mutable entries array, and a -// readonly tuple is not assignable to it. -const body = ` -const journal = ${JSON.stringify(journal, null, '\t')}; - -${parts.map((p) => `const ${p.key} = ${toTemplateLiteral(p.sql)};`).join('\n\n')} - -export default { - journal, - migrations: { -${parts.map((p) => `\t\t${p.key}`).join(',\n')} - } -}; -`; - -fs.writeFileSync(outFile, `${banner}${body}`, 'utf8'); -console.log( - `Inlined ${parts.length} migration${parts.length === 1 ? '' : 's'} into src/migrations.ts`, -); diff --git a/agents/hosting/src/agent.ts b/agents/hosting/src/agent.ts deleted file mode 100644 index bc2649c3..00000000 --- a/agents/hosting/src/agent.ts +++ /dev/null @@ -1,1433 +0,0 @@ -import * as Sentry from '@sentry/cloudflare'; -import { Agent, type AgentContext } from 'agents'; -import { and, count, desc, eq, gt, lt, or, sql } from 'drizzle-orm'; -import { drizzle, type DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite'; -import { migrate } from 'drizzle-orm/durable-sqlite/migrator'; -import { - deleteScript, - deleteScriptSecret, - deployVars, - deleteScriptsByTag, - patchScriptSecret, - patchScriptVars, - putScript, - uploadAssets, - type AssetFile, - type CfApi, - type ModuleFile, -} from './cloudflare'; -import * as schema from './db/schema'; -import { - apps, - appSecrets, - appVars, - buildSecrets, - buildVars, - deploys, - type AppRecord, - type DeployRecord, -} from './db/schema'; -import { mergeBuildEnv } from './build-env'; -import { decryptValue, encryptValue, importMasterKey } from './crypto'; -import migrations from './migrations'; -import { - DEMO_PROJECT_PATTERN, - analyticsApiResponseSchema, - appNameSchema, - buildSecretBodySchema, - deployCursorSchema, - deployMetaSchema, - projectIdSchema, - secretBodySchema, - storedVarsSchema, - subdomainSchema, - varNameSchema, - varsBodySchema, - type DeployMeta, -} from './schemas'; -import { gunzip, parseTar, toAssetPaths } from './tar'; - -/** - * HostingAgent - one Durable Object per project: the app registry for that - * project, deploy history, and the orchestration of the Cloudflare API. - * Control plane, not data plane: code and assets live in the dispatch - * namespace, and the SERVE path (src/index.ts) never touches this object. - * - * Subdomains are pushed by the console after it resolves the claim in the - * control-plane `app` table - the agent never derives or accepts one from a - * request, which is what makes the deploy route safe to expose on the - * operator surface: it can only ever deploy to subdomains the console - * recorded for THIS project. - */ - -const MAX_APPS = 10; -const MAX_DEPLOYS_PER_DAY = 50; -const MAX_VARS_PER_APP = 64; -const MAX_SECRETS_PER_APP = 64; -const MAX_BUILD_SECRETS_PER_APP = 32; -const ANALYTICS_CACHE_MS = 5_000; -// Sized for framework output, not just hand-rolled Workers: an OpenNext -// worker bundle routinely passes 10 MB uncompressed, and a Next site blows -// 1000 files on `_next/static` alone. The DO parses deploys in memory, so -// the asset total stays well under the 128 MB isolate limit; Cloudflare's -// own 10 MB-compressed script ceiling still applies at upload. -const MAX_MODULE_BYTES = 20 * 1024 * 1024; -const MAX_ASSET_COUNT = 5000; -const MAX_ASSET_TOTAL_BYTES = 40 * 1024 * 1024; -const MAX_ASSET_FILE_BYTES = 25 * 1024 * 1024; -const RECENT_DEPLOYS = 10; -/** Decompressed ceiling for a direct deploy's tarball. Above the 40 MB asset - * cap because the archive also carries source we filter out - the deploy - * itself is still bounded by the asset caps in publish(). */ -const MAX_ARCHIVE_BYTES = 96 * 1024 * 1024; -const MAX_ARCHIVE_FILES = 20_000; - -/** Cloudflare's Pages/Workers convention files: deploy CONFIGURATION, never - * content. `_worker.js` doubly so - for any framework whose assets directory - * is also its build output (SvelteKit, Astro SSR), it is the customer's - * server bundle, and publishing it would hand out their server source. The - * CLI filters these too (via .assetsignore); this is the backstop for older - * CLIs and direct tarball deploys. */ -const RESERVED_ROOT_ASSETS = new Set(['/_worker.js', '/_routes.json', '/_headers', '/_redirects']); - -export interface HostingAppSummary { - name: string; - subdomain: string; - url: string | null; - deployCount: number; - lastDeployAt: string | null; - createdAt: string; -} - -export interface HostingDeploySummary { - id: string; - appName: string; - subdomain: string; - url: string | null; - status: 'live' | 'stub'; - hasWorker: boolean; - assetCount: number; - assetBytes: number; - moduleBytes: number; - createdAt: string; -} - -export interface HostingAgentState { - projectId: string; - provisionedAt: string | null; - apps: HostingAppSummary[]; - recentDeploys: HostingDeploySummary[]; - totalDeploys: number; - /** Bumped on any reported change; dashboards refetch when it moves. */ - rev: number; -} - -const EXTENSION_TYPES: Record = { - html: 'text/html', - css: 'text/css', - js: 'text/javascript', - mjs: 'text/javascript', - json: 'application/json', - svg: 'image/svg+xml', - png: 'image/png', - jpg: 'image/jpeg', - jpeg: 'image/jpeg', - gif: 'image/gif', - webp: 'image/webp', - avif: 'image/avif', - ico: 'image/x-icon', - txt: 'text/plain', - xml: 'application/xml', - pdf: 'application/pdf', - wasm: 'application/wasm', - woff: 'font/woff', - woff2: 'font/woff2', - map: 'application/json', - webmanifest: 'application/manifest+json', -}; - -function contentTypeFor(path: string, provided: string): string { - if (provided && provided !== 'application/octet-stream') return provided; - const extension = path.slice(path.lastIndexOf('.') + 1).toLowerCase(); - return EXTENSION_TYPES[extension] ?? 'application/octet-stream'; -} - -/** Answering a body-bearing request without consuming the body wedges the - * worker; every route that skips the body drains it before answering. */ -async function drainUnusedBody(request: Request): Promise { - try { - if (request.body && !request.bodyUsed) await request.body.cancel(); - } catch { - // draining is belt-and-braces, never a failure - } -} - -export class HostingAgent extends Agent { - initialState: HostingAgentState = { - projectId: '', - provisionedAt: null, - apps: [], - recentDeploys: [], - totalDeploys: 0, - rev: 0, - }; - - db: DrizzleSqliteDODatabase; - - constructor(ctx: AgentContext, env: Env) { - super(ctx, env); - this.db = drizzle(ctx.storage, { schema }); - } - - async onStart(): Promise { - // Idempotent - drizzle tracks applied migrations in its own table. - await migrate(this.db, migrations); - if (!this.state.projectId) { - this.setState({ - ...this.state, - projectId: this.name, - provisionedAt: new Date().toISOString(), - }); - } - } - - private get stub(): boolean { - return this.env.HOSTING_STUB === 'true'; - } - - /** The REST driver's config, or null while any piece is missing. */ - private cfApi(): CfApi | null { - const { CF_ACCOUNT_ID, CF_HOSTING_API_TOKEN, DISPATCH_NAMESPACE } = this.env; - if (!CF_ACCOUNT_ID || !CF_HOSTING_API_TOKEN || !DISPATCH_NAMESPACE) return null; - return { - accountId: CF_ACCOUNT_ID, - apiToken: CF_HOSTING_API_TOKEN, - namespace: DISPATCH_NAMESPACE, - }; - } - - private appUrl(subdomain: string): string | null { - return this.env.HOSTING_DOMAIN ? `https://${subdomain}.${this.env.HOSTING_DOMAIN}` : null; - } - - private toAppSummary(row: AppRecord): HostingAppSummary { - return { - name: row.name, - subdomain: row.subdomain, - url: this.appUrl(row.subdomain), - deployCount: row.deployCount, - lastDeployAt: row.lastDeployAt?.toISOString() ?? null, - createdAt: row.createdAt.toISOString(), - }; - } - - private toDeploySummary(row: DeployRecord): HostingDeploySummary { - return { - id: row.id, - appName: row.appName, - subdomain: row.subdomain, - url: this.appUrl(row.subdomain), - status: row.status, - hasWorker: row.hasWorker, - assetCount: row.assetCount, - assetBytes: row.assetBytes, - moduleBytes: row.moduleBytes, - createdAt: row.createdAt.toISOString(), - }; - } - - private async syncState(): Promise { - const appRows = await this.db.select().from(apps).orderBy(apps.createdAt); - const recent = await this.db - .select() - .from(deploys) - .orderBy(desc(deploys.createdAt), desc(deploys.id)) - .limit(RECENT_DEPLOYS); - const [total] = await this.db.select({ value: count() }).from(deploys); - this.setState({ - ...this.state, - apps: appRows.map((row) => this.toAppSummary(row)), - recentDeploys: recent.map((row) => this.toDeploySummary(row)), - totalDeploys: total?.value ?? 0, - rev: this.state.rev + 1, - }); - } - - /** - * RPC from the worker's service-binding-only `/internal` route: the console - * pushes the claim it resolved (control plane owns the global namespace). - * Idempotent per app; re-pushing the same claim is a no-op. - */ - async registerApp(appName: string, subdomain: string): Promise<{ ok: true } | { error: string }> { - if (!appNameSchema.safeParse(appName).success) return { error: 'invalid app name' }; - if (!subdomainSchema.safeParse(subdomain).success) return { error: 'invalid subdomain' }; - - const [existing] = await this.db.select().from(apps).where(eq(apps.name, appName)).limit(1); - if (existing) { - if (existing.subdomain !== subdomain) { - // The claim table is authoritative; a changed claim (release + - // re-claim) updates the record. - await this.db.update(apps).set({ subdomain }).where(eq(apps.name, appName)); - await this.syncState(); - } - return { ok: true }; - } - const [{ value: appCount }] = await this.db.select({ value: count() }).from(apps); - if (appCount >= MAX_APPS) return { error: `projects are limited to ${MAX_APPS} apps` }; - await this.db.insert(apps).values({ - name: appName, - subdomain, - createdAt: new Date(), - deployCount: 0, - }); - await this.syncState(); - return { ok: true }; - } - - /** - * RPC from the worker's service-binding-only route: deletes ONE app - its - * namespace script, its deploy history, and its row. The console releases - * the subdomain claim only after this succeeds, so a failure here never - * frees a name whose script is still serving. Idempotent: an app the DO - * does not know (claimed but never deployed - the DO row is only pushed - * with the first deploy) still answers ok, because there is nothing here - * to remove. - */ - async eraseApp(appName: string): Promise<{ ok: true } | { error: string }> { - if (!appNameSchema.safeParse(appName).success) return { error: 'invalid app name' }; - - // Environment rows exist without an `apps` row (a claim-only app can be - // configured before its first deploy), so they are cleared regardless. - await this.db.delete(appVars).where(eq(appVars.appName, appName)); - await this.db.delete(appSecrets).where(eq(appSecrets.appName, appName)); - await this.db.delete(buildVars).where(eq(buildVars.appName, appName)); - await this.db.delete(buildSecrets).where(eq(buildSecrets.appName, appName)); - - const [app] = await this.db.select().from(apps).where(eq(apps.name, appName)).limit(1); - if (!app) return { ok: true }; - - if (!this.stub) { - const api = this.cfApi(); - // Configured installs must actually remove the script - answering ok - // while user code keeps serving would be a lie with a subdomain - // attached. deleteScript itself tolerates 404 (never deployed). - if (this.env.DISPATCH && api) { - await deleteScript(api, app.subdomain); - } - } - - await this.db.delete(deploys).where(eq(deploys.appName, appName)); - await this.db.delete(apps).where(eq(apps.name, appName)); - await this.syncState(); - return { ok: true }; - } - - /** Erase fan-in target. Deletes this project's namespace scripts by tag - * first - nothing that deletes a project may leave user code running. */ - async destroy(): Promise { - const api = this.cfApi(); - if (api && !this.stub) { - // Tag deletion catches every script this project ever uploaded, even - // ones a lost DO row no longer names. - await deleteScriptsByTag(api, `pid-${this.name}`); - } - await this.ctx.storage.deleteAll(); - // deleteAll leaves the Durable Object's alarm armed; an orphaned alarm - // would wake the erased object where the SDK's handler dies reading its - // dropped cf_agents_schedules table. - await this.ctx.storage.deleteAlarm(); - setTimeout(() => this.ctx.abort(), 0); - } - - async onRequest(request: Request): Promise { - try { - const response = await this.routeRequest(request); - await drainUnusedBody(request); - return response; - } catch (error) { - // The Agents SDK's own _tryCatch converts handler exceptions into a - // bare 500 before Sentry's DO instrumentation sees them - capture the - // real stack first, then let the SDK answer. - Sentry.captureException(error); - throw error; - } - } - - private async routeRequest(request: Request): Promise { - const url = new URL(request.url); - if (!projectIdSchema.safeParse(this.name).success) { - return Response.json({ error: 'invalid project id' }, { status: 400 }); - } - const subPath = url.pathname.match(/\/agents\/[^/]+\/[^/]+(\/.*)?$/)?.[1] ?? '/'; - - if (subPath === '/overview' && request.method === 'GET') { - return Response.json(await this.getOverview()); - } - if (subPath === '/deploys' && request.method === 'GET') { - return this.listDeploys(url); - } - const deploy = subPath.match(/^\/apps\/([^/]+)\/deploys$/); - if (deploy && request.method === 'POST') { - return this.deployApp(decodeURIComponent(deploy[1]), request); - } - const vars = subPath.match(/^\/apps\/([^/]+)\/vars$/); - if (vars && request.method === 'GET') { - return this.listVars(decodeURIComponent(vars[1])); - } - if (vars && request.method === 'PUT') { - return this.putVars(decodeURIComponent(vars[1]), request); - } - const secret = subPath.match(/^\/apps\/([^/]+)\/secrets$/); - if (secret && request.method === 'GET') { - return this.listSecrets(decodeURIComponent(secret[1])); - } - if (secret && request.method === 'POST') { - return this.setSecret(decodeURIComponent(secret[1]), request); - } - const secretName = subPath.match(/^\/apps\/([^/]+)\/secrets\/([^/]+)$/); - if (secretName && request.method === 'DELETE') { - return this.deleteSecret( - decodeURIComponent(secretName[1]), - decodeURIComponent(secretName[2]), - ); - } - const analytics = subPath.match(/^\/apps\/([^/]+)\/analytics$/); - if (analytics && request.method === 'GET') { - return this.getAppAnalytics(decodeURIComponent(analytics[1]), url); - } - const buildEnv = subPath.match(/^\/apps\/([^/]+)\/build-env$/); - if (buildEnv && request.method === 'GET') { - return this.getBuildEnv(decodeURIComponent(buildEnv[1])); - } - const buildVarsPath = subPath.match(/^\/apps\/([^/]+)\/build-vars$/); - if (buildVarsPath && request.method === 'PUT') { - return this.putBuildVars(decodeURIComponent(buildVarsPath[1]), request); - } - const buildSecret = subPath.match(/^\/apps\/([^/]+)\/build-secrets\/([^/]+)$/); - if (buildSecret && request.method === 'PUT') { - return this.putBuildSecret( - decodeURIComponent(buildSecret[1]), - decodeURIComponent(buildSecret[2]), - request, - ); - } - if (buildSecret && request.method === 'DELETE') { - return this.deleteBuildSecret( - decodeURIComponent(buildSecret[1]), - decodeURIComponent(buildSecret[2]), - ); - } - - return Response.json({ error: 'not found' }, { status: 404 }); - } - - async getOverview(): Promise> { - const appRows = await this.db.select().from(apps).orderBy(apps.createdAt); - const recent = await this.db - .select() - .from(deploys) - .orderBy(desc(deploys.createdAt), desc(deploys.id)) - .limit(RECENT_DEPLOYS); - const [total] = await this.db.select({ value: count() }).from(deploys); - return { - projectId: this.name, - provisionedAt: this.state.provisionedAt, - apps: appRows.map((row) => this.toAppSummary(row)), - recentDeploys: recent.map((row) => this.toDeploySummary(row)), - totalDeploys: total?.value ?? 0, - // Honest config report, so the dashboard can explain instead of 502. - configured: this.stub || (!!this.env.DISPATCH && this.cfApi() !== null), - stub: this.stub, - }; - } - - /** Keyset-paged deploy history (no offsets: rows land mid-scan). */ - private async listDeploys(url: URL): Promise { - const appName = url.searchParams.get('app') ?? undefined; - const cursor = deployCursorSchema.parse(url.searchParams.get('cursor') ?? undefined); - const limit = Math.min(Math.max(Number(url.searchParams.get('limit')) || 20, 1), 100); - - const appFilter = appName ? eq(deploys.appName, appName) : undefined; - let cursorFilter; - if (cursor) { - const [ms, id] = cursor.split(':'); - const at = new Date(Number(ms)); - cursorFilter = or( - lt(deploys.createdAt, at), - and(eq(deploys.createdAt, at), lt(deploys.id, id)), - ); - } - const rows = await this.db - .select() - .from(deploys) - .where(and(appFilter, cursorFilter)) - .orderBy(desc(deploys.createdAt), desc(deploys.id)) - .limit(limit + 1); - const page = rows.slice(0, limit); - const next = rows.length > limit ? page[page.length - 1] : null; - const [total] = await this.db - .select({ value: count() }) - .from(deploys) - .where(appFilter ?? sql`1 = 1`); - return Response.json({ - deploys: page.map((row) => this.toDeploySummary(row)), - total: total?.value ?? 0, - cursor: next ? `${next.createdAt.getTime()}:${next.id}` : null, - }); - } - - /** - * The checks every deploy passes before its body is even read: no demo - * hosting, the app must carry a console-pushed claim, and the daily - * ceiling. Returns the app row, or the Response to send instead. - */ - private async gateDeploy(appName: string): Promise { - if (DEMO_PROJECT_PATTERN.test(this.name)) { - // Belt to the console's braces: no demo hosting, ever. - return Response.json({ error: 'demo projects cannot deploy apps' }, { status: 403 }); - } - const [app] = await this.db.select().from(apps).where(eq(apps.name, appName)).limit(1); - if (!app) { - // The console pushes the claim before forwarding a deploy; reaching - // this without one means the agent surface was dialled directly. - return Response.json( - { error: 'app is not linked - deploy through the console or the CLI' }, - { status: 409 }, - ); - } - - const since = new Date(Date.now() - 24 * 60 * 60 * 1000); - const [recent] = await this.db - .select({ value: count() }) - .from(deploys) - .where(gt(deploys.createdAt, since)); - if ((recent?.value ?? 0) >= MAX_DEPLOYS_PER_DAY) { - return Response.json( - { error: `projects are limited to ${MAX_DEPLOYS_PER_DAY} deploys per day` }, - { status: 429 }, - ); - } - return app; - } - - private async deployApp(appName: string, request: Request): Promise { - const gate = await this.gateDeploy(appName); - if (gate instanceof Response) return gate; - const app = gate; - - let form: FormData; - try { - form = await request.formData(); - } catch { - return Response.json({ error: 'expected a multipart deploy body' }, { status: 400 }); - } - - const metaRaw = form.get('meta'); - const meta = deployMetaSchema.safeParse( - typeof metaRaw === 'string' ? JSON.parse(metaRaw || 'null') : null, - ); - if (!meta.success) { - return Response.json({ error: 'invalid deploy meta' }, { status: 400 }); - } - - const modules: ModuleFile[] = []; - const assets: AssetFile[] = []; - for (const [key, value] of form.entries()) { - if (typeof value === 'string') continue; - const bytes = new Uint8Array(await value.arrayBuffer()); - if (key.startsWith('module:')) { - const name = key.slice('module:'.length); - if (!/^[A-Za-z0-9._-]+\.(?:js|mjs)$/.test(name)) { - return Response.json({ error: `invalid module name "${name}"` }, { status: 400 }); - } - modules.push({ name, bytes }); - } else if (key.startsWith('asset:')) { - const path = key.slice('asset:'.length); - if (!path.startsWith('/') || path.includes('..') || path.length > 512) { - return Response.json({ error: `invalid asset path "${path}"` }, { status: 400 }); - } - assets.push({ path, bytes, contentType: contentTypeFor(path, value.type) }); - } - } - - // Size ceilings are enforced in publish(), so both deploy paths meet - // exactly the same limits. - return this.publish(appName, app, { - modules, - assets, - meta: meta.data, - origin: new URL(request.url).origin, - }); - } - - /** - * Uploads a prepared deploy and records it. Shared by the multipart route - * (the CLI's path) and the git route (a push webhook), so the caps and the - * bookkeeping can never diverge between them - the two differ only in - * where the bytes came from. - */ - private async publish( - appName: string, - app: AppRecord, - input: { modules: ModuleFile[]; assets: AssetFile[]; meta: DeployMeta; origin: string }, - ): Promise { - const { modules, meta } = input; - // Root-level convention files never publish, whatever path they arrived - // by. A deploy REDUCED to nothing by this (a lone _worker.js) falls - // through to the honest "nothing to deploy" below. - const assets = input.assets.filter((asset) => !RESERVED_ROOT_ASSETS.has(asset.path)); - const moduleBytes = modules.reduce((total, module) => total + module.bytes.length, 0); - const assetBytes = assets.reduce((total, asset) => total + asset.bytes.length, 0); - - if (moduleBytes > MAX_MODULE_BYTES) { - return Response.json( - { error: `the Worker bundle exceeds ${MAX_MODULE_BYTES / 1024 / 1024} MB` }, - { status: 400 }, - ); - } - if (assets.length > MAX_ASSET_COUNT) { - return Response.json( - { error: `deploys are limited to ${MAX_ASSET_COUNT} assets` }, - { status: 400 }, - ); - } - if (assetBytes > MAX_ASSET_TOTAL_BYTES) { - return Response.json( - { error: `assets exceed ${MAX_ASSET_TOTAL_BYTES / 1024 / 1024} MB` }, - { status: 400 }, - ); - } - if (assets.some((asset) => asset.bytes.length > MAX_ASSET_FILE_BYTES)) { - return Response.json( - { error: `an asset exceeds ${MAX_ASSET_FILE_BYTES / 1024 / 1024} MB` }, - { status: 400 }, - ); - } - if (!modules.length && !assets.length) { - return Response.json({ error: 'nothing to deploy' }, { status: 400 }); - } - if (modules.length && !meta.mainModule) { - return Response.json({ error: 'deploys with modules need meta.mainModule' }, { status: 400 }); - } - if (meta.mainModule && !modules.some((module) => module.name === meta.mainModule)) { - return Response.json({ error: 'meta.mainModule is not among the modules' }, { status: 400 }); - } - - // Stored console vars sit between the CLI's meta.vars and the platform's: - // the console is the canonical editor, so its values win over a stale - // cloudflarebase.json, and the platform's facts stay uncontestable on top - // (deployVars applies them last). - const storedRows = await this.db.select().from(appVars).where(eq(appVars.appName, appName)); - const storedVars = Object.fromEntries(storedRows.map((row) => [row.name, row.value])); - - if (!this.stub) { - const api = this.cfApi(); - if (!this.env.DISPATCH || !api) { - return Response.json( - { - error: - 'hosting is not configured - this install needs a Workers for Platforms dispatch namespace (DISPATCH binding, DISPATCH_NAMESPACE, CF_ACCOUNT_ID, CF_HOSTING_API_TOKEN)', - }, - { status: 503 }, - ); - } - try { - const assetsJwt = await uploadAssets(api, app.subdomain, this.name, assets); - await putScript(api, app.subdomain, { - projectId: this.name, - appName, - mainModule: meta.mainModule, - modules, - compatibilityDate: meta.compatibilityDate ?? '2026-07-10', - compatibilityFlags: meta.compatibilityFlags ?? [], - assetsJwt, - notFoundHandling: meta.notFoundHandling, - vars: deployVars({ ...(meta.vars ?? {}), ...storedVars }, this.name, input.origin), - }); - } catch (cause) { - Sentry.captureException(cause, { - tags: { operation: 'hosting-deploy', projectId: this.name }, - }); - const message = cause instanceof Error ? cause.message : 'upload failed'; - return Response.json({ error: `deploy failed - ${message}` }, { status: 502 }); - } - } - - const record: DeployRecord = { - id: crypto.randomUUID(), - appName, - subdomain: app.subdomain, - status: this.stub ? 'stub' : 'live', - hasWorker: modules.length > 0, - assetCount: assets.length, - assetBytes, - moduleBytes, - createdAt: new Date(), - }; - await this.db.insert(deploys).values(record); - await this.db - .update(apps) - .set({ - deployCount: app.deployCount + 1, - lastDeployAt: record.createdAt, - // Snapshot of what the CLI declared, so a later console var edit can - // rebuild the script's full plain_text set without the CLI present. - lastDeployVars: JSON.stringify(meta.vars ?? {}), - }) - .where(eq(apps.name, appName)); - await this.syncState(); - - return Response.json( - { - deploy: this.toDeploySummary(record), - subdomain: app.subdomain, - url: this.appUrl(app.subdomain), - }, - { status: 201 }, - ); - } - - /** - * Deploys a repository tarball - the no-runner path (Phase B, direct - * deploys). The console resolved the claim and the download URL, so this - * receives a plain URL and never holds a GitHub credential. - * - * Only static assets: a repository that needs a build is a build-mode - * connection and arrives through the ordinary multipart route instead. - */ - async gitDeploy(input: { - appName: string; - tarballUrl: string; - assetsDir: string; - origin: string; - }): Promise<{ status: number; json: string }> { - const gate = await this.gateDeploy(input.appName); - if (gate instanceof Response) { - return { status: gate.status, json: await gate.text() }; - } - - let assets: AssetFile[]; - try { - const response = await fetch(input.tarballUrl); - if (!response.ok || !response.body) { - return { - status: 502, - json: JSON.stringify({ error: 'could not download the repository archive' }), - }; - } - const buffer = await gunzip(response.body, MAX_ARCHIVE_BYTES); - const entries = parseTar(buffer, MAX_ARCHIVE_FILES); - assets = toAssetPaths(entries, input.assetsDir).map(({ path, bytes }) => ({ - path, - bytes, - contentType: contentTypeFor(path, ''), - })); - } catch (cause) { - Sentry.captureException(cause, { - tags: { operation: 'hosting-git-deploy', projectId: this.name }, - }); - const message = cause instanceof Error ? cause.message : 'could not read the archive'; - return { status: 400, json: JSON.stringify({ error: message }) }; - } - - if (!assets.length) { - return { - status: 400, - json: JSON.stringify({ - error: input.assetsDir - ? `nothing to deploy - "${input.assetsDir}" is empty or missing in this commit` - : 'nothing to deploy - the repository has no publishable files', - }), - }; - } - - const response = await this.publish(input.appName, gate, { - modules: [], - assets, - meta: { notFoundHandling: 'single-page-application' }, - origin: input.origin, - }); - return { status: response.status, json: await response.text() }; - } - - private async setSecret(appName: string, request: Request): Promise { - const [app] = await this.db.select().from(apps).where(eq(apps.name, appName)).limit(1); - if (!app) return Response.json({ error: 'no such app' }, { status: 404 }); - const body = secretBodySchema.safeParse(await request.json().catch(() => null)); - if (!body.success) { - return Response.json({ error: 'invalid secret body' }, { status: 400 }); - } - const name = body.data.name; - - const [existing] = await this.db - .select() - .from(appSecrets) - .where(and(eq(appSecrets.appName, appName), eq(appSecrets.name, name))) - .limit(1); - if (!existing) { - const [{ value: secretCount }] = await this.db - .select({ value: count() }) - .from(appSecrets) - .where(eq(appSecrets.appName, appName)); - if (secretCount >= MAX_SECRETS_PER_APP) { - return Response.json( - { error: `apps are limited to ${MAX_SECRETS_PER_APP} secrets` }, - { status: 400 }, - ); - } - } - - if (!this.stub) { - // Write-through: the value goes to Cloudflare's script settings and is - // never at rest here. Stub mode skips the call (there is no script) but - // still records the name, so local dev and e2e exercise the whole - // list/delete contract. - const api = this.cfApi(); - if (!api) { - return Response.json({ error: 'hosting is not configured' }, { status: 503 }); - } - try { - await patchScriptSecret(api, app.subdomain, name, body.data.value); - } catch (cause) { - Sentry.captureException(cause, { - tags: { operation: 'hosting-secret', projectId: this.name }, - }); - return Response.json({ error: 'setting the secret failed' }, { status: 502 }); - } - } - - // Also at rest, encrypted, when the install can: builds receive runtime - // secrets too (frameworks inline env at build time), and the write-through - // value above is unrecoverable by design. Keyless installs store null - - // the secret works at runtime and simply stays out of builds. The AAD's - // `runtime` prefix keeps this ciphertext and a build secret's from ever - // being interchangeable, even for the same app and name. - const master = this.env.HOSTING_MASTER_KEY; - const ciphertext = master - ? await encryptValue( - await importMasterKey(master), - body.data.value, - `runtime\0${appName}\0${name}`, - ) - : null; - const now = new Date(); - await this.db - .insert(appSecrets) - .values({ appName, name, ciphertext, createdAt: now, updatedAt: now }) - .onConflictDoUpdate({ - target: [appSecrets.appName, appSecrets.name], - set: { ciphertext, updatedAt: now }, - }); - return Response.json({ ok: true }); - } - - /** Secret NAMES and timestamps - the values live at Cloudflare, not here. */ - private async listSecrets(appName: string): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - const rows = await this.db - .select() - .from(appSecrets) - .where(eq(appSecrets.appName, appName)) - .orderBy(appSecrets.name); - return Response.json({ - secrets: rows.map((row) => ({ - name: row.name, - createdAt: row.createdAt.toISOString(), - updatedAt: row.updatedAt.toISOString(), - })), - }); - } - - /** Idempotent: the name row goes regardless; the script binding is removed - * when there is a script to remove it from (404-tolerant at the API). */ - private async deleteSecret(appName: string, name: string): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - if (!varNameSchema.safeParse(name).success) { - return Response.json({ error: 'invalid secret name' }, { status: 400 }); - } - if (!this.stub) { - const [app] = await this.db.select().from(apps).where(eq(apps.name, appName)).limit(1); - const api = this.cfApi(); - if (app && api) { - try { - await deleteScriptSecret(api, app.subdomain, name); - } catch (cause) { - Sentry.captureException(cause, { - tags: { operation: 'hosting-secret-delete', projectId: this.name }, - }); - // The row stays - the console keeps showing a secret that is - // still set, which is the honest state. - return Response.json({ error: 'deleting the secret failed' }, { status: 502 }); - } - } - } - await this.db - .delete(appSecrets) - .where(and(eq(appSecrets.appName, appName), eq(appSecrets.name, name))); - return Response.json({ ok: true }); - } - - /** Stored runtime vars. No `apps`-row requirement: a claim-only app can be - * configured before its first deploy, which then picks the vars up. */ - private async listVars(appName: string): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - return Response.json({ vars: await this.varSummaries(appName) }); - } - - private async varSummaries( - appName: string, - ): Promise<{ name: string; value: string; createdAt: string; updatedAt: string }[]> { - const rows = await this.db - .select() - .from(appVars) - .where(eq(appVars.appName, appName)) - .orderBy(appVars.name); - return rows.map((row) => ({ - name: row.name, - value: row.value, - createdAt: row.createdAt.toISOString(), - updatedAt: row.updatedAt.toISOString(), - })); - } - - /** - * Replace-the-set: the console form submits the whole table, so absent - * names are deletions. The store always succeeds first; patching the live - * script is best-effort and reported (`patched` / `warning`), never a way - * to lose an edit to a transient API failure. - */ - private async putVars(appName: string, request: Request): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - const body = varsBodySchema.safeParse(await request.json().catch(() => null)); - if (!body.success) { - return Response.json({ error: 'invalid vars body' }, { status: 400 }); - } - const wanted = body.data.vars; - if (Object.keys(wanted).length > MAX_VARS_PER_APP) { - return Response.json( - { error: `apps are limited to ${MAX_VARS_PER_APP} variables` }, - { status: 400 }, - ); - } - - const now = new Date(); - const existing = await this.db.select().from(appVars).where(eq(appVars.appName, appName)); - const current = new Map(existing.map((row) => [row.name, row])); - for (const row of existing) { - if (!(row.name in wanted)) { - await this.db - .delete(appVars) - .where(and(eq(appVars.appName, appName), eq(appVars.name, row.name))); - } - } - for (const [name, value] of Object.entries(wanted)) { - const row = current.get(name); - if (!row) { - await this.db - .insert(appVars) - .values({ appName, name, value, createdAt: now, updatedAt: now }); - } else if (row.value !== value) { - await this.db - .update(appVars) - .set({ value, updatedAt: now }) - .where(and(eq(appVars.appName, appName), eq(appVars.name, name))); - } - } - - let patched = false; - let warning: string | undefined; - const [app] = await this.db.select().from(apps).where(eq(apps.name, appName)).limit(1); - const api = this.cfApi(); - if (!this.stub && app?.lastDeployAt && api) { - // The last CLI-declared vars ride the stored snapshot; the platform's - // come out on top, same order as a deploy. - let cliVars: Record = {}; - try { - cliVars = storedVarsSchema.parse(JSON.parse(app.lastDeployVars ?? '{}')); - } catch { - // An unreadable snapshot degrades to "no CLI vars". - } - try { - await patchScriptVars( - api, - app.subdomain, - deployVars({ ...cliVars, ...wanted }, this.name, new URL(request.url).origin), - ); - patched = true; - } catch (cause) { - Sentry.captureException(cause, { - tags: { operation: 'hosting-vars', projectId: this.name }, - }); - warning = 'saved, but updating the live script failed - the next deploy applies them'; - } - } - - return Response.json({ - vars: await this.varSummaries(appName), - patched, - ...(warning ? { warning } : {}), - }); - } - - /** - * Operator read of the build-time environment: vars with values, secret - * NAMES only. Decrypted values never cross the operator surface - the - * runner's copy travels the service-binding-only /internal route instead. - */ - private async getBuildEnv(appName: string): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - const varRows = await this.db - .select() - .from(buildVars) - .where(eq(buildVars.appName, appName)) - .orderBy(buildVars.name); - const secretRows = await this.db - .select() - .from(buildSecrets) - .where(eq(buildSecrets.appName, appName)) - .orderBy(buildSecrets.name); - return Response.json({ - vars: varRows.map((row) => ({ - name: row.name, - value: row.value, - createdAt: row.createdAt.toISOString(), - updatedAt: row.updatedAt.toISOString(), - })), - secrets: secretRows.map((row) => ({ - name: row.name, - createdAt: row.createdAt.toISOString(), - updatedAt: row.updatedAt.toISOString(), - })), - encryptionConfigured: !!this.env.HOSTING_MASTER_KEY, - }); - } - - /** Replace-the-set for build vars, exactly like runtime vars - minus the - * live patch, because build vars only exist at build time. */ - private async putBuildVars(appName: string, request: Request): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - const body = varsBodySchema.safeParse(await request.json().catch(() => null)); - if (!body.success) { - return Response.json({ error: 'invalid vars body' }, { status: 400 }); - } - const wanted = body.data.vars; - if (Object.keys(wanted).length > MAX_VARS_PER_APP) { - return Response.json( - { error: `apps are limited to ${MAX_VARS_PER_APP} build variables` }, - { status: 400 }, - ); - } - - const now = new Date(); - const existing = await this.db.select().from(buildVars).where(eq(buildVars.appName, appName)); - const current = new Map(existing.map((row) => [row.name, row])); - for (const row of existing) { - if (!(row.name in wanted)) { - await this.db - .delete(buildVars) - .where(and(eq(buildVars.appName, appName), eq(buildVars.name, row.name))); - } - } - for (const [name, value] of Object.entries(wanted)) { - const row = current.get(name); - if (!row) { - await this.db - .insert(buildVars) - .values({ appName, name, value, createdAt: now, updatedAt: now }); - } else if (row.value !== value) { - await this.db - .update(buildVars) - .set({ value, updatedAt: now }) - .where(and(eq(buildVars.appName, appName), eq(buildVars.name, name))); - } - } - - const rows = await this.db - .select() - .from(buildVars) - .where(eq(buildVars.appName, appName)) - .orderBy(buildVars.name); - return Response.json({ - vars: rows.map((row) => ({ - name: row.name, - value: row.value, - createdAt: row.createdAt.toISOString(), - updatedAt: row.updatedAt.toISOString(), - })), - }); - } - - /** Encrypts one build secret at rest. The 503 without a master key is the - * degradation contract: everything else on the install keeps working. */ - private async putBuildSecret(appName: string, name: string, request: Request): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - if (!varNameSchema.safeParse(name).success) { - return Response.json({ error: 'invalid secret name' }, { status: 400 }); - } - const body = buildSecretBodySchema.safeParse(await request.json().catch(() => null)); - if (!body.success) { - return Response.json({ error: 'invalid secret body' }, { status: 400 }); - } - const master = this.env.HOSTING_MASTER_KEY; - if (!master) { - return Response.json( - { - error: - 'build secrets are not configured on this install - set the HOSTING_MASTER_KEY secret on the hosting agent', - }, - { status: 503 }, - ); - } - - const [existing] = await this.db - .select() - .from(buildSecrets) - .where(and(eq(buildSecrets.appName, appName), eq(buildSecrets.name, name))) - .limit(1); - if (!existing) { - const [{ value: secretCount }] = await this.db - .select({ value: count() }) - .from(buildSecrets) - .where(eq(buildSecrets.appName, appName)); - if (secretCount >= MAX_BUILD_SECRETS_PER_APP) { - return Response.json( - { error: `apps are limited to ${MAX_BUILD_SECRETS_PER_APP} build secrets` }, - { status: 400 }, - ); - } - } - - const key = await importMasterKey(master); - // The AAD binds the ciphertext to its row: copied to another name or - // app, it fails authentication instead of decrypting. - const ciphertext = await encryptValue(key, body.data.value, `${appName}\0${name}`); - const now = new Date(); - await this.db - .insert(buildSecrets) - .values({ appName, name, ciphertext, createdAt: now, updatedAt: now }) - .onConflictDoUpdate({ - target: [buildSecrets.appName, buildSecrets.name], - set: { ciphertext, updatedAt: now }, - }); - return Response.json({ ok: true }); - } - - private async deleteBuildSecret(appName: string, name: string): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - if (!varNameSchema.safeParse(name).success) { - return Response.json({ error: 'invalid secret name' }, { status: 400 }); - } - await this.db - .delete(buildSecrets) - .where(and(eq(buildSecrets.appName, appName), eq(buildSecrets.name, name))); - return Response.json({ ok: true }); - } - - /** - * RPC from the worker's service-binding-only build-env route: the DECRYPTED - * bundle a GitHub Actions runner exports before its build step. The console - * verified the OIDC grant before dialling this; decrypted values only ever - * transit the dashboard's service binding, never the operator HTTP surface. - */ - async buildEnvBundle( - appName: string, - ): Promise< - | { vars: Record; secrets: Record } - | { error: string; status: number } - > { - if (!appNameSchema.safeParse(appName).success) { - return { error: 'invalid app name', status: 400 }; - } - // Runtime env rides into the build too - frameworks inline env at build - // time, so a PUBLIC_* var that exists only as a runtime binding never - // reaches the client bundle. Build-specific values win on collision - // (mergeBuildEnv pins the rule). - const appVarRows = await this.db.select().from(appVars).where(eq(appVars.appName, appName)); - const buildVarRows = await this.db - .select() - .from(buildVars) - .where(eq(buildVars.appName, appName)); - const appSecretRows = await this.db - .select() - .from(appSecrets) - .where(eq(appSecrets.appName, appName)); - const buildSecretRows = await this.db - .select() - .from(buildSecrets) - .where(eq(buildSecrets.appName, appName)); - - // Rows without ciphertext are runtime-only: written before values were - // stored at rest, or on an install with no master key. They were never - // promised to builds, so their absence is not an error - unlike a row - // that HAS ciphertext nobody can decrypt. - const encryptedAppSecrets = appSecretRows.filter( - (row): row is typeof row & { ciphertext: string } => row.ciphertext !== null, - ); - const master = this.env.HOSTING_MASTER_KEY; - if ((buildSecretRows.length || encryptedAppSecrets.length) && !master) { - // Fail loud: a build that silently runs without its secrets is worse - // than one that fails attributed to configuration. - return { - error: 'stored secrets exist but HOSTING_MASTER_KEY is not set on this install', - status: 503, - }; - } - - const runtimeSecrets: Record = {}; - const buildOnlySecrets: Record = {}; - if (master) { - const key = await importMasterKey(master); - const decrypt = async ( - into: Record, - row: { appName: string; name: string; ciphertext: string }, - aad: string, - ) => { - try { - into[row.name] = await decryptValue(key, row.ciphertext, aad); - return null; - } catch (cause) { - Sentry.captureException(cause, { - tags: { operation: 'hosting-build-env', projectId: this.name }, - }); - return { - error: `secret ${row.name} cannot be decrypted - was the master key rotated?`, - status: 503, - }; - } - }; - for (const row of encryptedAppSecrets) { - const failed = await decrypt(runtimeSecrets, row, `runtime\0${row.appName}\0${row.name}`); - if (failed) return failed; - } - for (const row of buildSecretRows) { - const failed = await decrypt(buildOnlySecrets, row, `${row.appName}\0${row.name}`); - if (failed) return failed; - } - } - - return mergeBuildEnv( - { - vars: Object.fromEntries(appVarRows.map((row) => [row.name, row.value])), - secrets: runtimeSecrets, - }, - { - vars: Object.fromEntries(buildVarRows.map((row) => [row.name, row.value])), - secrets: buildOnlySecrets, - }, - ); - } - - // --- Analytics ------------------------------------------------------------ - - private analyticsCache: { - key: string; - expiresAt: number; - data: Record; - } | null = null; - - private get waeConfig(): { accountId: string; token: string; dataset: string } | null { - const accountId = this.env.CF_ACCOUNT_ID; - const token = this.env.CF_ANALYTICS_API_TOKEN; - const dataset = this.env.WAE_DATASET; - return accountId && token && dataset ? { accountId, token, dataset } : null; - } - - private async analyticsSql(query: string): Promise { - const config = this.waeConfig; - if (!config) return []; - const response = await fetch( - `https://api.cloudflare.com/client/v4/accounts/${config.accountId}/analytics_engine/sql`, - { - method: 'POST', - headers: { authorization: `Bearer ${config.token}` }, - body: `${query} FORMAT JSON`, - }, - ); - if (!response.ok) { - throw new Error(`Analytics Engine query failed (${response.status})`); - } - const result = analyticsApiResponseSchema.parse(await response.json()); - return (result.data ?? []) as T[]; - } - - /** - * Per-app requests/errors: daily buckets plus totals, from Analytics Engine - * (or the local D1 stand-in). Errors never 5xx the route - a broken - * analytics token would otherwise read as "the app is down" forever; the - * chart answers zeroed with `engine.status: 'error'` instead (the auth - * agent's rule). - */ - private async getAppAnalytics(appName: string, url: URL): Promise { - if (!appNameSchema.safeParse(appName).success) { - return Response.json({ error: 'invalid app name' }, { status: 400 }); - } - const wanted = Number(url.searchParams.get('days')); - const days = wanted === 30 || wanted === 90 ? wanted : 7; - const [app] = await this.db.select().from(apps).where(eq(apps.name, appName)).limit(1); - if (!app) return Response.json({ error: 'no such app' }, { status: 404 }); - - const cacheKey = `${appName}:${days}`; - if ( - this.analyticsCache && - this.analyticsCache.key === cacheKey && - this.analyticsCache.expiresAt > Date.now() - ) { - return Response.json(this.analyticsCache.data); - } - - let analyticsError: string | undefined; - let byDay: { day: string; requests: number; errors: number }[] = []; - let totals = { requests: 0, errors: 0, avgDurationMs: 0 }; - const config = this.waeConfig; - try { - if (config) { - const series = await this.queryWaeSeries(config, app.subdomain, days); - byDay = series.byDay; - totals = series.totals; - } else if (this.env.LOCAL_ANALYTICS) { - const series = await this.queryLocalSeries(app.subdomain, days); - byDay = series.byDay; - totals = series.totals; - } - } catch (cause) { - analyticsError = cause instanceof Error ? cause.message : 'Analytics Engine query failed'; - Sentry.captureException(cause, { - tags: { operation: 'hosting-analytics', projectId: this.name }, - }); - byDay = []; - totals = { requests: 0, errors: 0, avgDurationMs: 0 }; - } - - const data = { - appName, - subdomain: app.subdomain, - days, - totals, - byDay, - engine: { - dataset: this.env.WAE_DATASET ?? 'cloudflarebase_hosting_requests', - enabled: config !== null || !!this.env.LOCAL_ANALYTICS, - status: analyticsError - ? ('error' as const) - : config - ? ('connected' as const) - : this.env.LOCAL_ANALYTICS - ? ('local' as const) - : ('write-only' as const), - ...(analyticsError ? { error: analyticsError } : {}), - }, - }; - this.analyticsCache = { key: cacheKey, expiresAt: Date.now() + ANALYTICS_CACHE_MS, data }; - return Response.json(data); - } - - private async queryWaeSeries( - config: { accountId: string; token: string; dataset: string }, - subdomain: string, - days: number, - ): Promise<{ - byDay: { day: string; requests: number; errors: number }[]; - totals: { requests: number; errors: number; avgDurationMs: number }; - }> { - if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(config.dataset)) { - throw new Error('WAE_DATASET must be a valid Analytics Engine identifier'); - } - const sub = subdomain.replaceAll("'", "''"); - const from = `FROM ${config.dataset} WHERE index1 = '${sub}' AND timestamp > NOW() - INTERVAL '${days}' DAY`; - // Status rides blob1 as a 3-digit string, so 5xx is a lexicographic range. - const [requests, errors, sums] = await Promise.all([ - this.analyticsSql<{ day: string; n: number | string }>( - `SELECT formatDateTime(timestamp, '%Y-%m-%d') AS day, SUM(_sample_interval) AS n ${from} GROUP BY day ORDER BY day`, - ), - this.analyticsSql<{ day: string; n: number | string }>( - `SELECT formatDateTime(timestamp, '%Y-%m-%d') AS day, SUM(_sample_interval) AS n ${from} AND blob1 >= '500' AND blob1 <= '599' GROUP BY day ORDER BY day`, - ), - this.analyticsSql<{ n: number | string; duration: number | string }>( - `SELECT SUM(_sample_interval) AS n, SUM(double1 * _sample_interval) AS duration ${from}`, - ), - ]); - const dayMap = new Map(); - const entry = (day: string) => { - let value = dayMap.get(day); - if (!value) { - value = { day, requests: 0, errors: 0 }; - dayMap.set(day, value); - } - return value; - }; - for (const row of requests) entry(row.day.slice(0, 10)).requests += Number(row.n); - for (const row of errors) entry(row.day.slice(0, 10)).errors += Number(row.n); - const byDay = [...dayMap.values()].sort((a, b) => a.day.localeCompare(b.day)); - const totalRequests = Number(sums[0]?.n ?? 0); - const totalDuration = Number(sums[0]?.duration ?? 0); - return { - byDay, - totals: { - requests: totalRequests, - errors: byDay.reduce((sum, row) => sum + row.errors, 0), - avgDurationMs: totalRequests ? totalDuration / totalRequests : 0, - }, - }; - } - - private async queryLocalSeries( - subdomain: string, - days: number, - ): Promise<{ - byDay: { day: string; requests: number; errors: number }[]; - totals: { requests: number; errors: number; avgDurationMs: number }; - }> { - const db = this.env.LOCAL_ANALYTICS!; - const since = Date.now() - days * 86_400_000; - // The serve path (src/index.ts) creates the table on first write; the - // CREATE here covers reading an app nothing has ever requested. - const [, , rows, sums] = await db.batch([ - db.prepare( - `CREATE TABLE IF NOT EXISTS hosting_requests (id INTEGER PRIMARY KEY AUTOINCREMENT, subdomain TEXT NOT NULL, timestamp INTEGER NOT NULL, status INTEGER NOT NULL, duration_ms REAL NOT NULL)`, - ), - db.prepare( - `CREATE INDEX IF NOT EXISTS hosting_requests_sub_time ON hosting_requests(subdomain, timestamp)`, - ), - db - .prepare( - `SELECT strftime('%Y-%m-%d', timestamp / 1000, 'unixepoch') AS day, COUNT(*) AS requests, SUM(CASE WHEN status BETWEEN 500 AND 599 THEN 1 ELSE 0 END) AS errors FROM hosting_requests WHERE subdomain = ? AND timestamp > ? GROUP BY day ORDER BY day`, - ) - .bind(subdomain, since), - db - .prepare( - `SELECT COUNT(*) AS requests, SUM(CASE WHEN status BETWEEN 500 AND 599 THEN 1 ELSE 0 END) AS errors, AVG(duration_ms) AS avg FROM hosting_requests WHERE subdomain = ? AND timestamp > ?`, - ) - .bind(subdomain, since), - ]); - const byDay = (rows.results as { day: string; requests: number; errors: number | null }[]).map( - (row) => ({ day: row.day, requests: Number(row.requests), errors: Number(row.errors ?? 0) }), - ); - const total = ( - sums.results as { requests: number; errors: number | null; avg: number | null }[] - )[0]; - return { - byDay, - totals: { - requests: Number(total?.requests ?? 0), - errors: Number(total?.errors ?? 0), - avgDurationMs: Number(total?.avg ?? 0), - }, - }; - } -} diff --git a/agents/hosting/src/bindings.test-d.ts b/agents/hosting/src/bindings.test-d.ts deleted file mode 100644 index 899f5de5..00000000 --- a/agents/hosting/src/bindings.test-d.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Compile-time contract test, executed by `tsc --noEmit` (excluded from the - * publish build). The @ts-expect-error negatives fail the typecheck in BOTH - * directions: weakening the contract makes a suppression unused, which - * TypeScript reports. - */ -import type { AssertHostingAgentEnv } from './bindings'; - -// This repository's own generated Env satisfies the contract. -type _SelfCheck = AssertHostingAgentEnv; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type AnyNamespace = DurableObjectNamespace; - -// The minimal correct deployment: just the Durable Object namespace. -type _Minimal = AssertHostingAgentEnv<{ HostingAgent: AnyNamespace }>; - -// A fully configured deployment. -type _Full = AssertHostingAgentEnv<{ - HostingAgent: AnyNamespace; - DISPATCH: DispatchNamespace; - CF_ACCOUNT_ID: string; - CF_HOSTING_API_TOKEN: string; - DISPATCH_NAMESPACE: string; - HOSTING_DOMAIN: string; - DISPATCH_OUTBOUND: 'true'; - HOSTING_APEX_REDIRECT: string; - HOSTING_STUB: 'true'; - SENTRY_DSN: string; - SENTRY_ENV: string; -}>; - -// @ts-expect-error - the Durable Object namespace is required. -type _MissingAgent = AssertHostingAgentEnv<{ DISPATCH: DispatchNamespace }>; - -// @ts-expect-error - DISPATCH must be a dispatch namespace, not a Fetcher. -type _WrongDispatch = AssertHostingAgentEnv<{ HostingAgent: AnyNamespace; DISPATCH: Fetcher }>; - -// @ts-expect-error - vars are strings. -type _WrongOptional = AssertHostingAgentEnv<{ HostingAgent: AnyNamespace; HOSTING_DOMAIN: number }>; - -export type {}; diff --git a/agents/hosting/src/bindings.ts b/agents/hosting/src/bindings.ts deleted file mode 100644 index f064c097..00000000 --- a/agents/hosting/src/bindings.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * The binding contract for a Worker hosting `@cloudflarebase/hosting`. - * - * `AssertHostingAgentEnv` is an identity type whose CONSTRAINT does the - * checking: the consumer's entrypoint template instantiates it with their - * generated `Env`, so a missing or mistyped binding fails their typecheck - * with the field named, before anything deploys. Required vs optional here - * describes what a correct deployment provides, not what the runtime happens - * to tolerate. - */ - -// The namespace type is effectively invariant, so `never` and `unknown` each -// fail one direction; `any` is what accepts every concrete class. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type AnyDurableObjectNamespace = DurableObjectNamespace; - -export interface HostingAgentBindings { - /** The orchestrator Durable Object, one instance per project. */ - HostingAgent: AnyDurableObjectNamespace; - /** Workers for Platforms dispatch namespace. Optional on purpose: WfP is a - * paid add-on, and without it the agent answers hosting requests 503 - * instead of failing the whole install's deploy. */ - DISPATCH?: DispatchNamespace; - /** Cloudflare account that owns the dispatch namespace (REST uploads). */ - CF_ACCOUNT_ID?: string; - /** API token with Workers Scripts edit, scoped to namespace operations. */ - CF_HOSTING_API_TOKEN?: string; - /** Namespace NAME for the REST API - the binding does not reveal it. */ - DISPATCH_NAMESPACE?: string; - /** Apex domain apps serve under; empty disables the serving path. */ - HOSTING_DOMAIN?: string; - /** Pass outbound parameters at dispatch - set only beside an `outbound` - * block in the namespace binding, because they throw without one. */ - DISPATCH_OUTBOUND?: 'true'; - /** Where the bare apex redirects; without it the apex answers 404. */ - HOSTING_APEX_REDIRECT?: string; - /** Record deploys and serve a stub page instead of calling Cloudflare - - * local dev and e2e, where dynamic upload has no simulator. */ - HOSTING_STUB?: 'true'; - /** Master key for build secrets (AES-GCM at rest, src/crypto.ts). Optional: - * without it build-secret writes answer 503 and everything else works. */ - HOSTING_MASTER_KEY?: string; - /** Per-request analytics writes from the serve path. Optional: Analytics - * Engine is an account-level opt-in (see wrangler.jsonc); unset, writes - * are skipped and nothing else changes. */ - HOSTING_REQUESTS?: AnalyticsEngineDataset; - /** Dataset NAME for the SQL read API - the binding does not reveal it. */ - WAE_DATASET?: string; - /** Account Analytics Read token; with CF_ACCOUNT_ID it unlocks the - * analytics tab's reads. Optional - without it the tab reports write-only. */ - CF_ANALYTICS_API_TOKEN?: string; - /** Local-dev stand-in: with no read credentials, served requests land in - * this D1 and the analytics endpoint reads them back. */ - LOCAL_ANALYTICS?: D1Database; - SENTRY_DSN?: string; - SENTRY_ENV?: string; - - /** - * Serves the agent's own routes (`/overview`, `/apps/*`, `/deploys`, the - * state-sync socket, `/internal/*`) over HTTP. Every one of them deploys - * code, mints subdomains, or writes secrets, and none authenticate a - * caller - set this only on a Worker with no public hostname, fronted by - * something that authenticates operators. - * - * Unset (the default, and what `template/wrangler-fragment.jsonc` ships) - * they 404. Serving deployed apps on the wildcard hostname is unaffected, - * and your own code reaches the agent through the Durable Object namespace - * either way. - */ - EXPOSE_OPERATOR_API?: 'true'; -} - -export type AssertHostingAgentEnv = E; diff --git a/agents/hosting/src/build-env.ts b/agents/hosting/src/build-env.ts deleted file mode 100644 index ccd5ba80..00000000 --- a/agents/hosting/src/build-env.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * The build bundle's merge rule, extracted pure so a unit test can pin it: - * the decrypted-bundle route is reachable only through a GitHub Actions OIDC - * grant, which e2e cannot mint. - * - * Runtime env rides into builds because frameworks inline env at build time - - * a SvelteKit `PUBLIC_*` value that exists only as a runtime binding never - * reaches the client bundle. The build-specific stores stay separate and WIN - * on a name collision: they exist precisely to say "at build time, this - * instead". - */ -export function mergeBuildEnv( - runtime: { vars: Record; secrets: Record }, - build: { vars: Record; secrets: Record }, -): { vars: Record; secrets: Record } { - return { - vars: { ...runtime.vars, ...build.vars }, - secrets: { ...runtime.secrets, ...build.secrets }, - }; -} diff --git a/agents/hosting/src/build-env.unit.test.ts b/agents/hosting/src/build-env.unit.test.ts deleted file mode 100644 index c20e90f7..00000000 --- a/agents/hosting/src/build-env.unit.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import assert from 'node:assert/strict'; -import { test } from 'node:test'; -import { mergeBuildEnv } from './build-env'; - -test('runtime env reaches the build, and build-specific values win', () => { - const merged = mergeBuildEnv( - { - vars: { PUBLIC_API_URL: 'https://api.example', REGION: 'weur' }, - secrets: { DATABASE_URL: 'postgres://runtime' }, - }, - { - vars: { PUBLIC_API_URL: 'https://staging-api.example' }, - secrets: { NPM_TOKEN: 'npm_x' }, - }, - ); - // The build-specific override wins; everything else flows through. - assert.deepEqual(merged.vars, { - PUBLIC_API_URL: 'https://staging-api.example', - REGION: 'weur', - }); - assert.deepEqual(merged.secrets, { - DATABASE_URL: 'postgres://runtime', - NPM_TOKEN: 'npm_x', - }); -}); - -test('empty stores merge to empty, not to errors', () => { - assert.deepEqual(mergeBuildEnv({ vars: {}, secrets: {} }, { vars: {}, secrets: {} }), { - vars: {}, - secrets: {}, - }); -}); diff --git a/agents/hosting/src/cloudflare.ts b/agents/hosting/src/cloudflare.ts deleted file mode 100644 index db5c79a3..00000000 --- a/agents/hosting/src/cloudflare.ts +++ /dev/null @@ -1,447 +0,0 @@ -/** - * Cloudflare REST driver for Workers for Platforms uploads. Kept apart from - * the agent so the orchestration reads as the three documented steps: asset - * upload session -> wanted buckets -> script put. - * - * Asset manifest hashes are salted with the project id (SHA-256 over - * `\0`, truncated to the API's 32 hex chars): identical - * assets are deduplicated within a namespace BY HASH, so unsalted hashes - * would let one tenant probe another's content. The salt scopes dedup to the - * project, which is the correctness/privacy trade the design doc picks. - */ - -export interface CfApi { - accountId: string; - apiToken: string; - namespace: string; -} - -export interface AssetFile { - /** URL path the asset serves at, always starting with `/`. */ - path: string; - bytes: Uint8Array; - contentType: string; -} - -export interface ModuleFile { - /** Module file name (`index.js`); also its part name in the upload. */ - name: string; - bytes: Uint8Array; -} - -export interface PutScriptOptions { - projectId: string; - appName: string; - /** Entry module name; undefined = assets-only Worker. */ - mainModule?: string; - modules: ModuleFile[]; - compatibilityDate: string; - compatibilityFlags: string[]; - /** Completion token from the asset session; undefined = no assets. */ - assetsJwt?: string; - notFoundHandling?: 'single-page-application' | '404-page' | 'none'; - /** Injected beside the user's own vars so the SDK works out of the box. */ - vars: Record; -} - -const API_BASE = 'https://api.cloudflare.com/client/v4'; - -/** - * Workers for Platforms runs namespaced scripts in UNTRUSTED mode, which is - * what isolates one tenant from the next - and in that mode `caches.default` - * is disabled: touching it throws `This Worker is not permitted to access the - * default cache` (`request.cf` is absent for the same reason). Frameworks call - * it unconditionally - SvelteKit's Cloudflare adapter opens EVERY request with - * `caches.default.match(req)` - so an unmodified framework build answers 500 - * on every path the asset layer does not serve, which is every SSR route. - * - * The namespace-level `trusted_workers` flag would lift it by turning tenant - * isolation off for every app at once, which is the opposite trade a managed - * platform wants. So each module deploy gets a generated entry instead: it - * imports the shim FIRST and re-exports the customer's entry. ES modules - * evaluate imports depth-first in source order, so the shim runs before the - * customer bundle's own body - which is what makes a module-scope capture - * (`var s2 = caches.default`, exactly what the SvelteKit adapter emits) pick - * up the neutralised methods rather than the forbidden ones. - * - * Named caches (`caches.open(...)`) are per-Worker in untrusted mode and keep - * working; only the shared default cache is patched. - */ -const CACHE_SHIM_SOURCE = `// Injected by Cloudflarebase: Workers for Platforms disables the default -// cache for namespaced scripts, and frameworks call it unconditionally. -try { - const cache = globalThis.caches?.default; - if (cache) { - cache.match = async () => undefined; - cache.put = async () => undefined; - cache.delete = async () => false; - } -} catch { - // A runtime that exposes no Cache API needs no shim. -} -`; - -/** - * Returns the modules to upload and the entry to declare, with the cache shim - * wrapped around the customer's entry. Assets-only deploys (no main module) - * are returned untouched - there is no code to shim. - * - * Generated names carry a `__cfbase` prefix and a counter, so a customer file - * of the same name can never be shadowed by ours. - */ -export function wrapEntry( - modules: ModuleFile[], - mainModule: string | undefined, -): { modules: ModuleFile[]; mainModule: string | undefined } { - if (!mainModule) return { modules, mainModule }; - - const taken = new Set(modules.map((module) => module.name)); - const free = (base: string) => { - let name = `__cfbase_${base}.js`; - for (let suffix = 2; taken.has(name); suffix += 1) name = `__cfbase_${base}_${suffix}.js`; - taken.add(name); - return name; - }; - const shimName = free('runtime'); - const entryName = free('entry'); - - // JSON.stringify the specifiers: module names arrive from a CLI upload or a - // repository tarball, so they are never assumed to be quote-free. - const target = JSON.stringify(`./${mainModule}`); - const source = `import ${JSON.stringify(`./${shimName}`)}; -import * as entry from ${target}; -export * from ${target}; -export default entry.default; -`; - - const encoder = new TextEncoder(); - return { - modules: [ - ...modules, - { name: shimName, bytes: encoder.encode(CACHE_SHIM_SOURCE) }, - { name: entryName, bytes: encoder.encode(source) }, - ], - mainModule: entryName, - }; -} - -interface CfEnvelope { - success: boolean; - errors?: { code?: number; message?: string }[]; - result?: T; -} - -async function cfFetch(api: CfApi, path: string, init: RequestInit = {}): Promise { - const response = await fetch(`${API_BASE}/accounts/${api.accountId}${path}`, { - ...init, - headers: { - authorization: `Bearer ${api.apiToken}`, - ...(init.headers ?? {}), - }, - }); - const body = (await response.json().catch(() => null)) as CfEnvelope | null; - if (!response.ok || !body?.success) { - const detail = - body?.errors?.map((error) => `${error.code ?? '?'}: ${error.message ?? '?'}`).join('; ') ?? - `HTTP ${response.status}`; - throw new Error(`Cloudflare API ${path} failed - ${detail}`); - } - return body.result as T; -} - -async function saltedHash(projectId: string, bytes: Uint8Array): Promise { - const salt = new TextEncoder().encode(`${projectId}\0`); - const input = new Uint8Array(salt.length + bytes.length); - input.set(salt, 0); - input.set(bytes, salt.length); - const digest = await crypto.subtle.digest('SHA-256', input); - return [...new Uint8Array(digest)] - .map((byte) => byte.toString(16).padStart(2, '0')) - .join('') - .slice(0, 32); -} - -function toBase64(bytes: Uint8Array): string { - let binary = ''; - const chunk = 0x8000; - for (let i = 0; i < bytes.length; i += chunk) { - binary += String.fromCharCode(...bytes.subarray(i, i + chunk)); - } - return btoa(binary); -} - -/** - * Runs the asset upload session for a script: manifest -> wanted buckets -> - * base64 uploads -> completion token. Returns undefined when there are no - * assets at all. When every asset is already known to the namespace the - * session's own jwt IS the completion token. - */ -export async function uploadAssets( - api: CfApi, - scriptName: string, - projectId: string, - files: AssetFile[], -): Promise { - if (!files.length) return undefined; - - const byHash = new Map(); - const manifest: Record = {}; - for (const file of files) { - const hash = await saltedHash(projectId, file.bytes); - byHash.set(hash, file); - manifest[file.path] = { hash, size: file.bytes.length }; - } - - const session = await cfFetch<{ jwt: string; buckets?: string[][] }>( - api, - `/workers/dispatch/namespaces/${api.namespace}/scripts/${scriptName}/assets-upload-session`, - { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ manifest }), - }, - ); - - const buckets = session.buckets ?? []; - if (!buckets.length) return session.jwt; - - let completion: string | undefined; - for (const bucket of buckets) { - const form = new FormData(); - for (const hash of bucket) { - const file = byHash.get(hash); - if (!file) throw new Error(`Cloudflare asked for an unknown asset hash ${hash}`); - form.append(hash, new Blob([toBase64(file.bytes)], { type: file.contentType }), hash); - } - const response = await fetch( - `${API_BASE}/accounts/${api.accountId}/workers/assets/upload?base64=true`, - { - method: 'POST', - headers: { authorization: `Bearer ${session.jwt}` }, - body: form, - }, - ); - const body = (await response.json().catch(() => null)) as CfEnvelope<{ - jwt?: string; - }> | null; - if (!response.ok || !body?.success) { - throw new Error(`asset upload failed - HTTP ${response.status}`); - } - if (body.result?.jwt) completion = body.result.jwt; - } - - if (!completion) throw new Error('asset upload finished without a completion token'); - return completion; -} - -/** Uploads the script to the dispatch namespace. The script name IS the - * subdomain; the `pid-` tag is what erase deletes by. */ -export async function putScript( - api: CfApi, - scriptName: string, - options: PutScriptOptions, -): Promise { - const bindings: Record[] = Object.entries(options.vars).map(([name, text]) => ({ - type: 'plain_text', - name, - text, - })); - if (options.assetsJwt && options.mainModule) { - bindings.push({ type: 'assets', name: 'ASSETS' }); - } - - // Every code deploy is entered through the generated shim entry. - const wrapped = wrapEntry(options.modules, options.mainModule); - - const metadata: Record = { - compatibility_date: options.compatibilityDate, - compatibility_flags: options.compatibilityFlags, - bindings, - // `pid-` rather than `project:` - the scripts-by-tag filter grammar is - // `?tags=:yes`, so a colon inside a tag collides with it. - tags: [`pid-${options.projectId}`, `app-${options.appName}`], - // Secrets are PATCHed separately; redeploys must never drop them. - keep_bindings: ['secret_text'], - }; - if (wrapped.mainModule) metadata.main_module = wrapped.mainModule; - if (options.assetsJwt) { - metadata.assets = { - jwt: options.assetsJwt, - config: { - html_handling: 'auto-trailing-slash', - not_found_handling: - options.notFoundHandling ?? (options.mainModule ? 'none' : 'single-page-application'), - }, - }; - } - - const form = new FormData(); - form.append( - 'metadata', - new Blob([JSON.stringify(metadata)], { type: 'application/json' }), - 'metadata', - ); - for (const module of wrapped.modules) { - form.append( - module.name, - new Blob([module.bytes], { type: 'application/javascript+module' }), - module.name, - ); - } - - await cfFetch(api, `/workers/dispatch/namespaces/${api.namespace}/scripts/${scriptName}`, { - method: 'PUT', - body: form, - }); -} - -/** Deletes one script from the namespace. 404-tolerant: an app whose only - * deploys were stubbed (or that never deployed) has no script to remove. */ -export async function deleteScript(api: CfApi, scriptName: string): Promise { - const response = await fetch( - `${API_BASE}/accounts/${api.accountId}/workers/dispatch/namespaces/${api.namespace}/scripts/${scriptName}`, - { - method: 'DELETE', - headers: { authorization: `Bearer ${api.apiToken}` }, - }, - ); - if (!response.ok && response.status !== 404) { - throw new Error(`deleting script ${scriptName} failed - HTTP ${response.status}`); - } -} - -/** Bulk-delete every script carrying the project tag (the erase fan-in). */ -export async function deleteScriptsByTag(api: CfApi, tag: string): Promise { - const response = await fetch( - `${API_BASE}/accounts/${api.accountId}/workers/dispatch/namespaces/${api.namespace}/scripts?tags=${encodeURIComponent(`${tag}:yes`)}`, - { - method: 'DELETE', - headers: { authorization: `Bearer ${api.apiToken}` }, - }, - ); - // 404 means nothing carried the tag - erase is idempotent. - if (!response.ok && response.status !== 404) { - throw new Error(`deleting scripts by tag failed - HTTP ${response.status}`); - } -} - -/** Sets one secret on a deployed script without touching anything else. */ -export async function patchScriptSecret( - api: CfApi, - scriptName: string, - name: string, - value: string, -): Promise { - const form = new FormData(); - form.append( - 'settings', - new Blob( - [ - JSON.stringify({ - bindings: [{ type: 'secret_text', name, text: value }], - // Keep everything the deploy put there - this call only adds. - keep_bindings: ['plain_text', 'secret_text', 'assets'], - keep_assets: true, - }), - ], - { type: 'application/json' }, - ), - 'settings', - ); - await cfFetch( - api, - `/workers/dispatch/namespaces/${api.namespace}/scripts/${scriptName}/settings`, - { - method: 'PATCH', - body: form, - }, - ); -} - -/** - * Replaces the script's WHOLE `plain_text` set in one settings PATCH; secrets - * and assets stay. Callers pass the FULL effective set (platform over stored - * over the last deploy's CLI vars) - replacement, not addition, is how a - * deleted var actually disappears from the live script. - */ -export async function patchScriptVars( - api: CfApi, - scriptName: string, - vars: Record, -): Promise { - const form = new FormData(); - form.append( - 'settings', - new Blob( - [ - JSON.stringify({ - bindings: Object.entries(vars).map(([name, text]) => ({ - type: 'plain_text', - name, - text, - })), - // Deliberately NOT 'plain_text': this call replaces that set. - keep_bindings: ['secret_text', 'assets'], - keep_assets: true, - }), - ], - { type: 'application/json' }, - ), - 'settings', - ); - await cfFetch( - api, - `/workers/dispatch/namespaces/${api.namespace}/scripts/${scriptName}/settings`, - { - method: 'PATCH', - body: form, - }, - ); -} - -/** Deletes one secret from a namespaced script. 404-tolerant: a secret set - * before name-tracking existed, or already gone, is not an error. */ -export async function deleteScriptSecret( - api: CfApi, - scriptName: string, - name: string, -): Promise { - const response = await fetch( - `${API_BASE}/accounts/${api.accountId}/workers/dispatch/namespaces/${api.namespace}/scripts/${scriptName}/secrets/${encodeURIComponent(name)}`, - { - method: 'DELETE', - headers: { authorization: `Bearer ${api.apiToken}` }, - }, - ); - if (!response.ok && response.status !== 404) { - throw new Error(`deleting secret ${name} failed - HTTP ${response.status}`); - } -} - -/** - * The plain-text vars a deployed app is born with. - * - * The customer's declared vars come first and the platform's are applied over - * them, deliberately: which project an app belongs to is the platform's fact, - * not a value the app gets to disagree with. An app that could point - * `CLOUDFLAREBASE_PROJECT` at another project would be reaching into another - * tenant's data plane by editing a config file. - * - * `CLOUDFLAREBASE_PROJECT` + `CLOUDFLAREBASE_URL` are exactly the names the - * admin SDKs resolve from, so `createDbAdmin()` with no arguments works inside - * a hosted Worker and `db.remoteConfig()` needs no setup. `PROJECT_ID` predates - * them and is kept because apps already deployed against it would otherwise - * break for nothing. - */ -export function deployVars( - declared: Record | undefined, - projectId: string, - consoleOrigin: string, -): Record { - return { - ...(declared ?? {}), - CLOUDFLAREBASE_PROJECT: projectId, - PROJECT_ID: projectId, - CLOUDFLAREBASE_URL: consoleOrigin, - }; -} diff --git a/agents/hosting/src/cloudflare.unit.test.ts b/agents/hosting/src/cloudflare.unit.test.ts deleted file mode 100644 index 98be1aca..00000000 --- a/agents/hosting/src/cloudflare.unit.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { deleteScriptSecret, deployVars, patchScriptVars, wrapEntry } from './cloudflare'; - -/** - * The shim entry only matters against a REAL dispatch namespace (untrusted - * mode is where `caches.default` is forbidden), and the e2e stack runs on the - * stub, which uploads nothing. So the shape of what we upload is pinned here. - */ - -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); -const module = (name: string, source = 'export default {};') => ({ - name, - bytes: encoder.encode(source), -}); -const sourceOf = (modules: { name: string; bytes: Uint8Array }[], name: string) => - decoder.decode(modules.find((entry) => entry.name === name)!.bytes); - -test('an assets-only deploy is left alone', () => { - const result = wrapEntry([], undefined); - assert.deepEqual(result.modules, []); - assert.equal(result.mainModule, undefined); -}); - -test('the customer entry is entered through the shim', () => { - const result = wrapEntry([module('_worker.js')], '_worker.js'); - - assert.equal(result.mainModule, '__cfbase_entry.js'); - assert.deepEqual( - result.modules.map((entry) => entry.name), - ['_worker.js', '__cfbase_runtime.js', '__cfbase_entry.js'], - ); - - const entry = sourceOf(result.modules, '__cfbase_entry.js'); - // Import order is the whole mechanism: the shim must be evaluated before - // the customer bundle's module body captures `caches.default`. - assert.ok(entry.indexOf('__cfbase_runtime.js') < entry.indexOf('_worker.js')); - // Named exports (Durable Object classes) survive the wrap. - assert.match(entry, /export \* from "\.\/_worker\.js"/); - assert.match(entry, /export default entry\.default/); - - const shim = sourceOf(result.modules, '__cfbase_runtime.js'); - assert.match(shim, /caches\?\.default/); - assert.match(shim, /cache\.match = async \(\) => undefined/); -}); - -test('a nested entry keeps resolving from the generated root entry', () => { - const result = wrapEntry([module('dist/index.js'), module('dist/chunk.js')], 'dist/index.js'); - assert.match(sourceOf(result.modules, '__cfbase_entry.js'), /from "\.\/dist\/index\.js"/); -}); - -test('customer modules of the generated names are never shadowed', () => { - const result = wrapEntry( - [module('_worker.js'), module('__cfbase_runtime.js'), module('__cfbase_entry.js')], - '_worker.js', - ); - - assert.equal(result.mainModule, '__cfbase_entry_2.js'); - // The customer's own files are uploaded untouched. - assert.equal(sourceOf(result.modules, '__cfbase_runtime.js'), 'export default {};'); - assert.match(sourceOf(result.modules, '__cfbase_entry_2.js'), /__cfbase_runtime_2\.js/); -}); - -test('deployVars: the platform is authoritative about which project an app is', () => { - const vars = deployVars({ API_BASE: 'https://api.example' }, 'acme', 'https://console.example'); - // The customer's own vars survive untouched. - assert.equal(vars.API_BASE, 'https://api.example'); - // And the app is born knowing its project: these are exactly the names - // createDbAdmin()/createStorageAdmin() resolve from, so a hosted Worker - // self-configures with no setup step. - assert.equal(vars.CLOUDFLAREBASE_PROJECT, 'acme'); - assert.equal(vars.CLOUDFLAREBASE_URL, 'https://console.example'); - // Kept for apps already deployed against it. - assert.equal(vars.PROJECT_ID, 'acme'); -}); - -test('deployVars: an app cannot point itself at another project', () => { - // Which project an app belongs to is the platform's fact, not a value the - // app gets to disagree with - otherwise editing a config file would reach - // into another tenant's data plane. - const vars = deployVars( - { CLOUDFLAREBASE_PROJECT: 'someone-else', CLOUDFLAREBASE_URL: 'https://evil.example' }, - 'acme', - 'https://console.example', - ); - assert.equal(vars.CLOUDFLAREBASE_PROJECT, 'acme'); - assert.equal(vars.CLOUDFLAREBASE_URL, 'https://console.example'); -}); - -/** - * The settings PATCH shapes are pinned here for the same reason as the shim: - * they only run against a REAL dispatch namespace, which the e2e stub never - * dials. A captured fetch is the whole harness. - */ - -const API = { accountId: 'acct', apiToken: 'tok', namespace: 'ns' }; - -async function withFetchCapture( - response: Response, - run: () => Promise, -): Promise<{ url: string; init: RequestInit }> { - const original = globalThis.fetch; - let captured: { url: string; init: RequestInit } | undefined; - globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { - captured = { url: String(input), init: init ?? {} }; - return response; - }) as typeof fetch; - try { - await run(); - } finally { - globalThis.fetch = original; - } - assert.ok(captured, 'fetch was never called'); - return captured!; -} - -const ok = () => new Response(JSON.stringify({ success: true, result: {} }), { status: 200 }); - -test('patchScriptVars replaces the plain_text set and keeps everything else', async () => { - const { url, init } = await withFetchCapture(ok(), () => - patchScriptVars(API, 'site', { A: '1', B: '2' }), - ); - assert.equal( - url, - 'https://api.cloudflare.com/client/v4/accounts/acct/workers/dispatch/namespaces/ns/scripts/site/settings', - ); - assert.equal(init.method, 'PATCH'); - const part = (init.body as FormData).get('settings') as Blob; - const settings = JSON.parse(await part.text()) as { - bindings: { type: string; name: string; text: string }[]; - keep_bindings: string[]; - keep_assets: boolean; - }; - assert.deepEqual(settings.bindings, [ - { type: 'plain_text', name: 'A', text: '1' }, - { type: 'plain_text', name: 'B', text: '2' }, - ]); - // NOT 'plain_text': replacing the whole set is how a deleted var actually - // disappears from the live script. - assert.deepEqual(settings.keep_bindings, ['secret_text', 'assets']); - assert.equal(settings.keep_assets, true); -}); - -test('deleteScriptSecret dials the per-script secret and tolerates 404', async () => { - const { url, init } = await withFetchCapture(ok(), () => - deleteScriptSecret(API, 'site', 'API_KEY'), - ); - assert.equal( - url, - 'https://api.cloudflare.com/client/v4/accounts/acct/workers/dispatch/namespaces/ns/scripts/site/secrets/API_KEY', - ); - assert.equal(init.method, 'DELETE'); - - // Already gone is not an error - the row deletion must proceed. - await withFetchCapture(new Response('', { status: 404 }), () => - deleteScriptSecret(API, 'site', 'API_KEY'), - ); - - // Anything else is. - const original = globalThis.fetch; - globalThis.fetch = (async () => new Response('', { status: 500 })) as typeof fetch; - try { - await assert.rejects(deleteScriptSecret(API, 'site', 'API_KEY'), /HTTP 500/); - } finally { - globalThis.fetch = original; - } -}); diff --git a/agents/hosting/src/crypto.ts b/agents/hosting/src/crypto.ts deleted file mode 100644 index 58bba58e..00000000 --- a/agents/hosting/src/crypto.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Encryption at rest for build secrets - the one place in Cloudflarebase where - * a secret VALUE must be stored and later recovered (the GitHub Actions - * runner fetches it at build time), so hashing and write-through both fall - * short. Everything else stays hashed or write-through; reach for this only - * when a value genuinely has to come back out. - * - * AES-256-GCM under a master key derived from the `HOSTING_MASTER_KEY` - * wrangler secret. The stored format is versioned (`v1::`) - * so a key or algorithm rotation can introduce `v2` while still reading old - * rows. Ciphertexts are bound to their row via GCM's additional data - * (`\0`): a value copied between rows fails authentication - * instead of decrypting under the wrong name. - */ - -const VERSION = 'v1'; - -function toBase64Url(bytes: Uint8Array): string { - let binary = ''; - for (const byte of bytes) binary += String.fromCharCode(byte); - return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, ''); -} - -function fromBase64Url(value: string): Uint8Array { - const padded = value.replaceAll('-', '+').replaceAll('_', '/'); - const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4)); - return Uint8Array.from(binary, (char) => char.charCodeAt(0)); -} - -/** - * SHA-256 of the secret string as the raw AES-256 key. The env secret is - * high-entropy operator input, not a password, so a KDF with salt/stretching - * would add parameters without adding security. - */ -export async function importMasterKey(secret: string): Promise { - const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(secret)); - return crypto.subtle.importKey('raw', digest, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']); -} - -/** Encrypts to `v1::`, fresh random - * 12-byte IV per call - two encryptions of the same value never match. */ -export async function encryptValue( - key: CryptoKey, - plaintext: string, - aad: string, -): Promise { - const iv = crypto.getRandomValues(new Uint8Array(12)); - const ciphertext = await crypto.subtle.encrypt( - { name: 'AES-GCM', iv, additionalData: new TextEncoder().encode(aad) }, - key, - new TextEncoder().encode(plaintext), - ); - return `${VERSION}:${toBase64Url(iv)}:${toBase64Url(new Uint8Array(ciphertext))}`; -} - -/** Throws on an unknown version, malformed input, a wrong key, a wrong AAD, - * or any bit of tampering - GCM authenticates before it decrypts. */ -export async function decryptValue(key: CryptoKey, stored: string, aad: string): Promise { - const [version, iv, ciphertext, ...rest] = stored.split(':'); - if (version !== VERSION || !iv || !ciphertext || rest.length) { - throw new Error(`unsupported ciphertext format "${stored.slice(0, 8)}"`); - } - const plaintext = await crypto.subtle.decrypt( - { - name: 'AES-GCM', - iv: fromBase64Url(iv), - additionalData: new TextEncoder().encode(aad), - }, - key, - fromBase64Url(ciphertext), - ); - return new TextDecoder().decode(plaintext); -} diff --git a/agents/hosting/src/crypto.unit.test.ts b/agents/hosting/src/crypto.unit.test.ts deleted file mode 100644 index 2dbe43ab..00000000 --- a/agents/hosting/src/crypto.unit.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { decryptValue, encryptValue, importMasterKey } from './crypto'; - -/** - * Build secrets are the repo's only encryption at rest, so the contract is - * pinned end to end: round-trips work, everything else - tampering, a wrong - * key, a value copied to another row, an unknown format - throws. - */ - -const AAD = 'site\0API_KEY'; - -test('round-trips a value', async () => { - const key = await importMasterKey('a master key of respectable length!!'); - const stored = await encryptValue(key, 'hunter2', AAD); - assert.match(stored, /^v1:[A-Za-z0-9_-]+:[A-Za-z0-9_-]+$/); - assert.equal(await decryptValue(key, stored, AAD), 'hunter2'); -}); - -test('the same plaintext never encrypts to the same ciphertext', async () => { - const key = await importMasterKey('a master key of respectable length!!'); - const first = await encryptValue(key, 'hunter2', AAD); - const second = await encryptValue(key, 'hunter2', AAD); - assert.notEqual(first, second); // fresh IV per call - assert.equal(await decryptValue(key, second, AAD), 'hunter2'); -}); - -test('round-trips edge-case values', async () => { - const key = await importMasterKey('a master key of respectable length!!'); - for (const value of ['', 'x'.repeat(5000), 'newline\\n literal and unïcøde ✨']) { - const stored = await encryptValue(key, value, AAD); - assert.equal(await decryptValue(key, stored, AAD), value); - } -}); - -test('a flipped ciphertext bit fails authentication', async () => { - const key = await importMasterKey('a master key of respectable length!!'); - const stored = await encryptValue(key, 'hunter2', AAD); - const tampered = stored.slice(0, -1) + (stored.endsWith('A') ? 'B' : 'A'); - await assert.rejects(decryptValue(key, tampered, AAD)); -}); - -test('a ciphertext copied to another row fails authentication', async () => { - const key = await importMasterKey('a master key of respectable length!!'); - const stored = await encryptValue(key, 'hunter2', AAD); - await assert.rejects(decryptValue(key, stored, 'site\0OTHER_NAME')); - await assert.rejects(decryptValue(key, stored, 'other-app\0API_KEY')); -}); - -test('the wrong key fails authentication', async () => { - const key = await importMasterKey('a master key of respectable length!!'); - const other = await importMasterKey('a different master key entirely!!!!!'); - const stored = await encryptValue(key, 'hunter2', AAD); - await assert.rejects(decryptValue(other, stored, AAD)); -}); - -test('unknown versions and malformed inputs are refused, not guessed at', async () => { - const key = await importMasterKey('a master key of respectable length!!'); - const stored = await encryptValue(key, 'hunter2', AAD); - const [, iv, ciphertext] = stored.split(':'); - for (const bad of [ - `v9:${iv}:${ciphertext}`, // future version - `${stored}:extra`, // trailing segment - 'v1:only-two', - 'v1', - '', - 'plaintext-that-was-never-encrypted', - ]) { - await assert.rejects(decryptValue(key, bad, AAD), /unsupported ciphertext|error/i); - } -}); diff --git a/agents/hosting/src/db/schema.ts b/agents/hosting/src/db/schema.ts deleted file mode 100644 index 2a958bec..00000000 --- a/agents/hosting/src/db/schema.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core'; - -/** - * HostingAgent storage: the per-project app registry, deploy history, and - * per-app environment. Control plane, not data plane - the code and assets - * live in the dispatch namespace; these rows are what the dashboard lists and - * what erase walks. - */ - -/** Apps this project has deployed. `subdomain` is pushed by the console after - * it resolves the claim (the control plane owns the global namespace); the - * agent never derives one. */ -export const apps = sqliteTable('apps', { - name: text('name').primaryKey(), - subdomain: text('subdomain').notNull(), - createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), - lastDeployAt: integer('last_deploy_at', { mode: 'timestamp_ms' }), - deployCount: integer('deploy_count').notNull().default(0), - /** JSON snapshot of the CLI's `meta.vars` from the most recent deploy. - * Editing a var in the console replaces the script's WHOLE plain_text set, - * so the patch must reconstitute platform > stored > CLI without the CLI - * present - without this snapshot a console edit would silently drop every - * CLI-declared var from the live script. */ - lastDeployVars: text('last_deploy_vars'), -}); - -export type AppRecord = typeof apps.$inferSelect; - -export const deploys = sqliteTable('deploys', { - id: text('id').primaryKey(), - appName: text('app_name').notNull(), - subdomain: text('subdomain').notNull(), - /** `live` = uploaded to the namespace; `stub` = recorded under HOSTING_STUB. */ - status: text('status').$type<'live' | 'stub'>().notNull(), - /** Whether the deploy carried a Worker module (vs assets-only). */ - hasWorker: integer('has_worker', { mode: 'boolean' }).notNull(), - assetCount: integer('asset_count').notNull(), - assetBytes: integer('asset_bytes').notNull(), - moduleBytes: integer('module_bytes').notNull(), - createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), -}); - -export type DeployRecord = typeof deploys.$inferSelect; - -/** Runtime plain-text vars, stored per app: applied as `plain_text` bindings - * on every deploy and patched onto the live script when edited. Plaintext at - * rest on purpose - they upload as plain_text bindings anyway, and DO storage - * is the trust boundary (the storage agent's signing secret precedent). */ -export const appVars = sqliteTable( - 'app_vars', - { - appName: text('app_name').notNull(), - name: text('name').notNull(), - value: text('value').notNull(), - createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), - updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(), - }, - (table) => [primaryKey({ columns: [table.appName, table.name] })], -); - -export type AppVarRecord = typeof appVars.$inferSelect; - -/** Runtime secrets. The VALUE is write-through to Cloudflare's script - * settings (that is what binds it at runtime); `ciphertext` additionally - * holds it AES-GCM under `HOSTING_MASTER_KEY` so builds can receive it too - - * frameworks inline env at build time, and a secret that exists only at - * runtime never reaches them. Null on installs without the master key (and - * on rows written before this column existed): the secret still works at - * runtime, it just cannot ride into builds. */ -export const appSecrets = sqliteTable( - 'app_secrets', - { - appName: text('app_name').notNull(), - name: text('name').notNull(), - ciphertext: text('ciphertext'), - createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), - updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(), - }, - (table) => [primaryKey({ columns: [table.appName, table.name] })], -); - -export type AppSecretRecord = typeof appSecrets.$inferSelect; - -/** Build-time vars, fetched by the GitHub Actions workflow before the build - * step. Connection-scoped: they live in the ROOT project's agent only, and - * every branch build of the connection reads the same set. */ -export const buildVars = sqliteTable( - 'build_vars', - { - appName: text('app_name').notNull(), - name: text('name').notNull(), - value: text('value').notNull(), - createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), - updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(), - }, - (table) => [primaryKey({ columns: [table.appName, table.name] })], -); - -export type BuildVarRecord = typeof buildVars.$inferSelect; - -/** Build-time secrets - the one value Cloudflarebase stores AND must recover - * (the runner fetches it at build time), so it is AES-GCM ciphertext under - * `HOSTING_MASTER_KEY` (src/crypto.ts), never plaintext at rest. */ -export const buildSecrets = sqliteTable( - 'build_secrets', - { - appName: text('app_name').notNull(), - name: text('name').notNull(), - ciphertext: text('ciphertext').notNull(), - createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), - updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(), - }, - (table) => [primaryKey({ columns: [table.appName, table.name] })], -); - -export type BuildSecretRecord = typeof buildSecrets.$inferSelect; diff --git a/agents/hosting/src/env.d.ts b/agents/hosting/src/env.d.ts deleted file mode 100644 index 16e9c0bf..00000000 --- a/agents/hosting/src/env.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Optional secrets set via `wrangler secret put` - not present in wrangler.jsonc -// vars, so they are augmented here instead of hand-editing the generated -// worker-configuration.d.ts (the auth agent's precedent). -interface Env { - /** Account Analytics Read token: with CF_ACCOUNT_ID it unlocks the - * analytics tab's SQL reads. Writes need no credentials. */ - CF_ANALYTICS_API_TOKEN?: string; -} diff --git a/agents/hosting/src/index.ts b/agents/hosting/src/index.ts deleted file mode 100644 index 3d5a2b68..00000000 --- a/agents/hosting/src/index.ts +++ /dev/null @@ -1,424 +0,0 @@ -import * as Sentry from '@sentry/cloudflare'; -import { getAgentByName, routeAgentRequest } from 'agents'; -import { WorkerEntrypoint } from 'cloudflare:workers'; -import { HostingAgent as HostingAgentBase } from './agent'; -import { gateOperatorRoutes } from './route-access'; -import { appNameSchema, projectIdSchema, subdomainSchema } from './schemas'; - -/** - * hosting-agent worker - two roles, decided by hostname (the LiveShard - * precedent): - * - * - **Serving**: requests on the `*.cfbase.dev` wildcard route take the first - * host label and `env.DISPATCH.get(hostLabel)` - zero lookup, because the - * script name IS the full subdomain. Dispatch never parses the subdomain - * into app and branch; the control plane's claims table resolved that at - * deploy time. Unclaimed subdomain = no script = branded 404. - * - **Agent surface** on every other hostname: `/health`, the - * service-binding-only `/internal/*` routes (erase fan-in and the console's - * claim push - outside /agents/* on purpose: this Worker has no public - * route beyond the wildcard, and the serve branch swallows every wildcard - * request before dispatch gets here, so /internal is reachable only over - * the dashboard's service binding), and `routeAgentRequest` for the - * operator surface. Never with `cors: true`. - */ - -const sentryOptions = (env: Env) => ({ - dsn: env.SENTRY_DSN, - environment: env.SENTRY_ENV, - tracesSampleRate: 0.1, - enableRpcTracePropagation: true, -}); - -const DO_RESET_PATTERN = /abort\(\) to reset|durable object reset/i; -function isDurableObjectReset(error: unknown): boolean { - return DO_RESET_PATTERN.test(error instanceof Error ? error.message : String(error)); -} - -/** - * Local-dev stand-in for Analytics Engine, ensured once per isolate. Keyed on - * the binding (the control-plane getDb precedent) so tests that swap - * databases still bootstrap the new one; a failure clears the cache so the - * next request retries instead of wedging analytics for the isolate. - */ -const localAnalyticsReady = new WeakMap>(); -function ensureHostingRequestsTable(db: D1Database): Promise { - let pending = localAnalyticsReady.get(db); - if (!pending) { - pending = db - .batch([ - db.prepare( - `CREATE TABLE IF NOT EXISTS hosting_requests (id INTEGER PRIMARY KEY AUTOINCREMENT, subdomain TEXT NOT NULL, timestamp INTEGER NOT NULL, status INTEGER NOT NULL, duration_ms REAL NOT NULL)`, - ), - db.prepare( - `CREATE INDEX IF NOT EXISTS hosting_requests_sub_time ON hosting_requests(subdomain, timestamp)`, - ), - ]) - .then(() => undefined) - .catch((cause: unknown) => { - localAnalyticsReady.delete(db); - throw cause; - }); - localAnalyticsReady.set(db, pending); - } - return pending; -} - -async function drainUnusedBody(request: Request): Promise { - try { - if (request.body && !request.bodyUsed) await request.body.cancel(); - } catch { - // draining is belt-and-braces, never a failure - } -} - -function brandedNotFound(subdomain: string, domain: string): Response { - return new Response( - ` - - - - - Nothing here yet - - - -
-

Nothing is deployed at ${subdomain}.${domain}

-

Claim it with cloudflarebase init and ship with cloudflarebase deploy.

-

cloudflarebase.com

-
- -`, - { status: 404, headers: { 'content-type': 'text/html; charset=utf-8' } }, - ); -} - -/** What the dispatch path serves under HOSTING_STUB - local dev and e2e have - * no namespace, so the contract test asserts this page instead. */ -function stubPage(subdomain: string, domain: string): Response { - return new Response( - ` - - ${subdomain}.${domain} (stub) - -

cfbase.dev stub

-

This deployment records apps without a dispatch namespace; ${subdomain}.${domain} would serve your deploy in production.

- -`, - { status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } }, - ); -} - -class HostingService extends WorkerEntrypoint { - override async fetch(request: Request): Promise { - const response = await this.dispatch(request); - await drainUnusedBody(request); - return response; - } - - private async dispatch(request: Request): Promise { - const url = new URL(request.url); - - // Serving first: on a wildcard host EVERY path belongs to the user's - // app - our own surfaces exist only on non-serving hostnames. - const served = await this.serveIfAppHost(request, url); - if (served) { - // Reported as the DEPLOYED APP failing, never as this worker failing: - // a customer script that throws is their bug, and titling it - // "Hosting agent returned HTTP 500" sends every one of them to us - // wearing our own name. Kept (rather than dropped) because it is the - // only signal a whole class of apps is broken - which is how the - // Workers for Platforms default-cache restriction was found - and - // kept under ONE title so a hundred broken apps stay one issue with a - // subdomain tag, not a hundred issues. - await this.reportDispatchError(request, url, served); - return served; - } - - if (url.pathname === '/health') { - return Response.json({ service: 'hosting-agent', status: 'ok' }); - } - - // Past the serving path, every surface this worker has deploys code, - // mints subdomains, or writes secrets, and none of them authenticate a - // caller: the console guard is the gate, and the package cannot assume - // one exists - the documented consumer install mounts this handler on - // their own PUBLIC Worker. Closed unless the deployment says otherwise - // (src/route-access.ts); a no-op wherever EXPOSE_OPERATOR_API is set. - const gated = gateOperatorRoutes(url, this.env); - if (gated) return gated; - - const erase = url.pathname.match(/^\/internal\/projects\/([^/]+)$/); - if (erase && request.method === 'DELETE') { - const projectId = decodeURIComponent(erase[1]); - if (!projectIdSchema.safeParse(projectId).success) { - return Response.json({ error: 'invalid project id' }, { status: 400 }); - } - const agent = await getAgentByName(this.env.HostingAgent, projectId); - try { - await agent.destroy(); - } catch (error) { - // The agent's own deferred abort can outrace the RPC reply in - // production; scripts and storage are already gone by then. - if (!isDurableObjectReset(error)) throw error; - } - return Response.json({ erased: true }); - } - - // The console's claim push: it resolved the subdomain in the control - // plane and records it here so the deploy route can never be steered to - // a subdomain this project does not own. Service-binding-only by - // topology, like the erase route. - const link = url.pathname.match(/^\/internal\/projects\/([^/]+)\/apps\/([^/]+)$/); - if (link && (request.method === 'PUT' || request.method === 'DELETE')) { - const projectId = decodeURIComponent(link[1]); - const appName = decodeURIComponent(link[2]); - if ( - !projectIdSchema.safeParse(projectId).success || - !appNameSchema.safeParse(appName).success - ) { - return Response.json({ error: 'invalid claim push' }, { status: 400 }); - } - const agent = await getAgentByName(this.env.HostingAgent, projectId); - // DELETE is the app erase: script, deploy history, and row - the - // console releases the subdomain claim only after this answers ok. - if (request.method === 'DELETE') { - const result = await agent.eraseApp(appName); - if ('error' in result) return Response.json(result, { status: 400 }); - return Response.json(result); - } - const body = (await request.json().catch(() => null)) as { subdomain?: string } | null; - if (!subdomainSchema.safeParse(body?.subdomain).success) { - return Response.json({ error: 'invalid claim push' }, { status: 400 }); - } - const result = await agent.registerApp(appName, body!.subdomain!); - if ('error' in result) return Response.json(result, { status: 409 }); - return Response.json(result); - } - - // The build-env fetch: the console verified a GitHub Actions OIDC grant - // and relays the DECRYPTED bundle to the runner. Values only ever - // transit the dashboard's service binding - the operator HTTP surface - // answers names only. Service-binding-only, like the routes above. - const buildEnv = url.pathname.match( - /^\/internal\/projects\/([^/]+)\/apps\/([^/]+)\/build-env$/, - ); - if (buildEnv && request.method === 'GET') { - const projectId = decodeURIComponent(buildEnv[1]); - const appName = decodeURIComponent(buildEnv[2]); - if ( - !projectIdSchema.safeParse(projectId).success || - !appNameSchema.safeParse(appName).success - ) { - return Response.json({ error: 'invalid build-env request' }, { status: 400 }); - } - const agent = await getAgentByName(this.env.HostingAgent, projectId); - const result = await agent.buildEnvBundle(appName); - if ('error' in result) { - return Response.json({ error: result.error }, { status: result.status }); - } - return Response.json(result); - } - - // Direct deploys (Phase B): a push webhook the console verified, whose - // repository needs no build. The console resolved the claim and the - // download URL first, so the agent receives a plain URL and never holds - // a GitHub credential. Service-binding-only, like the routes above. - const gitDeploy = url.pathname.match( - /^\/internal\/projects\/([^/]+)\/apps\/([^/]+)\/git-deploy$/, - ); - if (gitDeploy && request.method === 'POST') { - const projectId = decodeURIComponent(gitDeploy[1]); - const appName = decodeURIComponent(gitDeploy[2]); - const body = (await request.json().catch(() => null)) as { - tarballUrl?: string; - assetsDir?: string; - } | null; - if ( - !projectIdSchema.safeParse(projectId).success || - !appNameSchema.safeParse(appName).success || - typeof body?.tarballUrl !== 'string' || - // Only GitHub's own download hosts - this URL is fetched server side. - !/^https:\/\/(codeload|api)\.github\.com\//.test(body.tarballUrl) - ) { - return Response.json({ error: 'invalid git deploy' }, { status: 400 }); - } - const agent = await getAgentByName(this.env.HostingAgent, projectId); - const result = await agent.gitDeploy({ - appName, - tarballUrl: body.tarballUrl, - assetsDir: typeof body.assetsDir === 'string' ? body.assetsDir : '', - origin: url.origin, - }); - return new Response(result.json, { - status: result.status, - headers: { 'content-type': 'application/json' }, - }); - } - - const response = - (await routeAgentRequest(request, this.env)) ?? - Response.json({ error: 'not found' }, { status: 404 }); - - await this.reportServerError(request, url, response); - return response; - } - - /** - * The dispatch path. Null when the hostname is not a serving host - agent - * traffic (service binding, dev ports) falls through to the surfaces - * above. Under HOSTING_STUB an `x-cfbase-host` header stands in for the - * Host header, because local workerd is dialled by port, not subdomain; - * the header is ignored everywhere else. - */ - private async serveIfAppHost(request: Request, url: URL): Promise { - const domain = this.env.HOSTING_DOMAIN; - if (!domain) return null; - - let host = url.hostname; - if (this.env.HOSTING_STUB === 'true') { - const override = request.headers.get('x-cfbase-host'); - if (override) host = override.split(':')[0]; - } - - if (host === domain) { - const apex = this.env.HOSTING_APEX_REDIRECT; - return apex ? Response.redirect(apex, 302) : brandedNotFound('www', domain); - } - if (!host.endsWith(`.${domain}`)) return null; - - const label = host.slice(0, -(domain.length + 1)); - // Universal SSL covers exactly one wildcard level; anything deeper (or - // charset-invalid) can never be a claimed subdomain. - if (label.includes('.') || !subdomainSchema.safeParse(label).success) { - return brandedNotFound(label.replaceAll('.', '-'), domain); - } - - if (this.env.HOSTING_STUB === 'true') { - // The stub records too - that is what makes the analytics tab real in - // local dev and e2e. - this.recordRequest(label, 200, 0); - return stubPage(label, domain); - } - if (!this.env.DISPATCH) { - return brandedNotFound(label, domain); - } - - const options: DynamicDispatchOptions = { - // Fixed v1 caps, applied at dispatch (plan-driven in Phase C - which - // then changes THIS call, never the deployed scripts). - limits: { cpuMs: 50, subRequests: 50 }, - }; - if (this.env.DISPATCH_OUTBOUND === 'true') { - // The subdomain rides the outbound parameters - joinable to a project - // offline via the claims table, so the serve path stays zero-lookup. - options.outbound = { subdomain: label }; - } - - const startedAt = Date.now(); - try { - const worker = this.env.DISPATCH.get(label, {}, options); - const response = await worker.fetch(request); - this.recordRequest(label, response.status, Date.now() - startedAt); - return response; - } catch (error) { - if (/worker not found/i.test(error instanceof Error ? error.message : String(error))) { - // Unclaimed subdomains are deliberately NOT recorded - a scanner - // walking names would otherwise write a data point per probe. - return brandedNotFound(label, domain); - } - throw error; - } - } - - /** - * One data point per served request: subdomain (the index - it IS the - * script name, so the DO joins it back to an app for free), status, and - * duration. Never allowed to affect the response; with no Analytics Engine - * read credentials the local D1 stand-in records instead, which is what - * the local/e2e stacks read back. - */ - private recordRequest(subdomain: string, status: number, durationMs: number): void { - try { - this.env.HOSTING_REQUESTS?.writeDataPoint({ - indexes: [subdomain], - blobs: [String(status)], - doubles: [durationMs], - }); - } catch { - // metrics failure must never affect the serve path - } - const readable = - this.env.CF_ACCOUNT_ID && this.env.CF_ANALYTICS_API_TOKEN && this.env.WAE_DATASET; - const local = this.env.LOCAL_ANALYTICS; - if (!readable && local) { - this.ctx.waitUntil( - ensureHostingRequestsTable(local) - .then(() => - local - .prepare( - `INSERT INTO hosting_requests (subdomain, timestamp, status, duration_ms) VALUES (?, ?, ?, ?)`, - ) - .bind(subdomain, Date.now(), status, durationMs) - .run(), - ) - .then(() => undefined) - .catch(() => undefined), - ); - } - } - - /** Records a 5xx a DEPLOYED APP answered with. Never replaces the response. */ - private async reportDispatchError(request: Request, url: URL, response: Response): Promise { - if (response.status < 500) return; - try { - const body = (await response.clone().text()).slice(0, 512); - Sentry.captureMessage('Deployed app returned HTTP 5xx', { - level: 'warning', - tags: { - 'http.method': request.method, - 'http.status_code': response.status, - subdomain: url.hostname.split('.')[0], - }, - contexts: { response: { body } }, - extra: { pathname: url.pathname }, - }); - } catch { - // reporting must never replace the response - } - } - - /** Records any 5xx leaving this worker. Never replaces the response. */ - private async reportServerError(request: Request, url: URL, response: Response): Promise { - if (response.status < 500) return; - try { - const body = (await response.clone().text()).slice(0, 2048); - Sentry.captureMessage(`Hosting agent returned HTTP ${response.status}`, { - level: 'error', - tags: { 'http.method': request.method, 'http.status_code': response.status }, - contexts: { - response: { body, contentType: response.headers.get('content-type') ?? '' }, - }, - extra: { pathname: url.pathname }, - }); - } catch { - // reporting must never replace the response - } - } -} - -export const HostingAgent = Sentry.instrumentDurableObjectWithSentry( - sentryOptions, - HostingAgentBase, -); - -export type { HostingAgentState, HostingAppSummary, HostingDeploySummary } from './agent'; -export type { AssertHostingAgentEnv, HostingAgentBindings } from './bindings'; - -export default Sentry.withSentry(sentryOptions, HostingService); diff --git a/agents/hosting/src/migrations.ts b/agents/hosting/src/migrations.ts deleted file mode 100644 index 067676ca..00000000 --- a/agents/hosting/src/migrations.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Generated by scripts/generate-migrations.mjs - do not edit. -// -// The SQL is inlined rather than imported from .sql files so this agent can be -// consumed as a plain npm dependency, with no Wrangler Text-module rule needed -// in the consumer's config. Regenerate with `npm run migrations`. - -const journal = { - version: '7', - dialect: 'sqlite', - entries: [ - { - idx: 0, - version: '6', - when: 1786556374566, - tag: '0000_red_la_nuit', - breakpoints: true, - }, - { - idx: 1, - version: '6', - when: 1787197325964, - tag: '0001_slippery_prism', - breakpoints: true, - }, - { - idx: 2, - version: '6', - when: 1787253423135, - tag: '0002_amazing_kronos', - breakpoints: true, - }, - ], -}; - -const m0000 = `CREATE TABLE \`apps\` ( - \`name\` text PRIMARY KEY NOT NULL, - \`subdomain\` text NOT NULL, - \`created_at\` integer NOT NULL, - \`last_deploy_at\` integer, - \`deploy_count\` integer DEFAULT 0 NOT NULL -); ---> statement-breakpoint -CREATE TABLE \`deploys\` ( - \`id\` text PRIMARY KEY NOT NULL, - \`app_name\` text NOT NULL, - \`subdomain\` text NOT NULL, - \`status\` text NOT NULL, - \`has_worker\` integer NOT NULL, - \`asset_count\` integer NOT NULL, - \`asset_bytes\` integer NOT NULL, - \`module_bytes\` integer NOT NULL, - \`created_at\` integer NOT NULL -); -`; - -const m0001 = `CREATE TABLE \`app_secrets\` ( - \`app_name\` text NOT NULL, - \`name\` text NOT NULL, - \`created_at\` integer NOT NULL, - \`updated_at\` integer NOT NULL, - PRIMARY KEY(\`app_name\`, \`name\`) -); ---> statement-breakpoint -CREATE TABLE \`app_vars\` ( - \`app_name\` text NOT NULL, - \`name\` text NOT NULL, - \`value\` text NOT NULL, - \`created_at\` integer NOT NULL, - \`updated_at\` integer NOT NULL, - PRIMARY KEY(\`app_name\`, \`name\`) -); ---> statement-breakpoint -CREATE TABLE \`build_secrets\` ( - \`app_name\` text NOT NULL, - \`name\` text NOT NULL, - \`ciphertext\` text NOT NULL, - \`created_at\` integer NOT NULL, - \`updated_at\` integer NOT NULL, - PRIMARY KEY(\`app_name\`, \`name\`) -); ---> statement-breakpoint -CREATE TABLE \`build_vars\` ( - \`app_name\` text NOT NULL, - \`name\` text NOT NULL, - \`value\` text NOT NULL, - \`created_at\` integer NOT NULL, - \`updated_at\` integer NOT NULL, - PRIMARY KEY(\`app_name\`, \`name\`) -); ---> statement-breakpoint -ALTER TABLE \`apps\` ADD \`last_deploy_vars\` text;`; - -const m0002 = `ALTER TABLE \`app_secrets\` ADD \`ciphertext\` text;`; - -export default { - journal, - migrations: { - m0000, - m0001, - m0002, - }, -}; diff --git a/agents/hosting/src/outbound.ts b/agents/hosting/src/outbound.ts deleted file mode 100644 index 1524b0d7..00000000 --- a/agents/hosting/src/outbound.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Outbound Worker for the dispatch namespaces (wrangler.outbound.jsonc). - * - * Every `fetch()` a hosted app makes is re-dispatched here with the - * parameters the serve path passed (`subdomain` - joinable to a project via - * the control plane's claims table, so the serve path stays zero-lookup). - * v1 forwards untouched; Phase C hooks egress metering and policy blocking - * into exactly this spot without touching user scripts. - */ - -interface OutboundEnv { - /** Dispatch parameter: the subdomain (= script name) that made the fetch. */ - subdomain?: string; -} - -export default { - async fetch(request: Request, _env: OutboundEnv): Promise { - return fetch(request); - }, -} satisfies ExportedHandler; diff --git a/agents/hosting/src/route-access.ts b/agents/hosting/src/route-access.ts deleted file mode 100644 index e6aaf869..00000000 --- a/agents/hosting/src/route-access.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Who may call which route - enforced by the agent itself, not only by - * whatever sits in front of it. - * - * On cloudflarebase.com the console guard IS the gate, and that is sound - * there: this worker claims a wildcard route that serves deployed APPS and - * nothing of ours, `workers_dev` and `preview_urls` are false, so our own - * surfaces are reachable only over the dashboard's service binding - where - * every request has already been classified against the manifest's route - * table and checked for an operator session or a deploy token. - * - * A consumer of the published package deploys the opposite shape. The - * documented install re-exports the default handler from THEIR Worker - the - * public one their app already runs on - and there the manifest is a file - * nobody reads at request time. This agent has NO public routes at all: - * every surface it serves deploys code, mints subdomains, writes secrets, or - * reports what is deployed. - * - * So the whole surface is CLOSED unless the deployment declares its Worker - * control-plane-only. Two properties matter: - * - * - Default deny. An undeclared route is operator, which is the same default - * the agent contract states for the console guard. Drift between - * this table and the manifest can therefore only ever close something that - * should be open - a loud, testable failure - never open something that - * should be closed. - * - The refusal is the ordinary 404, byte for byte, so a closed surface is - * not enumerable either. - * - * Serving a deployed app is unaffected: `serveIfAppHost` answers on the - * wildcard hostname before any of this runs. - * - * `ROUTES` mirrors `cloudflarebase.agent.json` and - * `route-access.unit.test.ts` fails if the two disagree, so the manifest - * stays the single declaration. It is a copy rather than an import because - * the JSON sits outside the build's `rootDir`, and the manifest has to stay - * at the package root where the console and the CLI read it. - */ - -export type RouteAccess = 'public' | 'operator'; - -export interface RouteRule { - path: string; - access: RouteAccess; -} - -/** Mirrors the `routes` block of `cloudflarebase.agent.json`. */ -export const ROUTES: readonly RouteRule[] = [ - { path: '/overview', access: 'operator' }, - { path: '/apps/*', access: 'operator' }, - { path: '/deploys', access: 'operator' }, -]; - -/** `/agents//` plus the sub-path the agent dispatches on. */ -const AGENT_PATH = /^\/agents\/[^/]+\/[^/]+(\/.*)?$/; - -/** - * The declared access for one agent sub-path. Undeclared is `operator`: the - * SDK's state-sync socket (sub-path `/`) lands here too, and broadcasting - * agent state to an anonymous caller is exactly the leak this prevents. - */ -export function routeAccess(subPath: string): RouteAccess { - for (const rule of ROUTES) { - if (rule.path.endsWith('/*')) { - const prefix = rule.path.slice(0, -2); - if (subPath === prefix || subPath.startsWith(`${prefix}/`)) return rule.access; - } else if (subPath === rule.path) { - return rule.access; - } - } - return 'operator'; -} - -/** - * Set only on a Worker that serves no public hostname of its own, where a - * control plane in front has already authorized the caller. - */ -export function operatorApiExposed(env: { EXPOSE_OPERATOR_API?: string }): boolean { - return env.EXPOSE_OPERATOR_API === 'true'; -} - -/** - * The 404 to answer with, or null to carry on. Everything that is not a - * declared-public agent route is refused, `/internal/*` included - that one - * is service-binding-only by deployment shape alone, which is a property of - * our topology and not of the package. - */ -export function gateOperatorRoutes( - url: URL, - env: { EXPOSE_OPERATOR_API?: string }, -): Response | null { - if (operatorApiExposed(env)) return null; - // `new URL` resolves real dot segments before anything reads the path, but - // it leaves `%2e` alone - and classifying an encoded dot segment means - // betting on whether something downstream decodes it. Nothing this agent - // serves has one in its path, so refuse rather than guess. - if (/%2e/i.test(url.pathname)) return notFound(); - const match = AGENT_PATH.exec(url.pathname); - if (match && routeAccess(match[1] ?? '/') === 'public') return null; - return notFound(); -} - -function notFound(): Response { - return Response.json({ error: 'not found' }, { status: 404 }); -} diff --git a/agents/hosting/src/route-access.unit.test.ts b/agents/hosting/src/route-access.unit.test.ts deleted file mode 100644 index 2a3e3f4c..00000000 --- a/agents/hosting/src/route-access.unit.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import test from 'node:test'; -import { ROUTES, gateOperatorRoutes, routeAccess } from './route-access'; - -/** - * The gate only ever runs in deployments we do not operate (a consumer's - * public Worker, where `EXPOSE_OPERATOR_API` is unset), so nothing in the - * e2e stack exercises it - every environment we run sets the flag and the - * gate is a no-op. These tests are the coverage for the other shape. - */ - -const manifest = JSON.parse( - // The manifest is written with a BOM; JSON.parse chokes on one. - // .href, then fileURLToPath: the ambient URL here is workerd's, which is - // not assignable to node:fs's PathOrFileDescriptor, and a bare pathname - // would lose the drive letter on Windows. - readFileSync( - fileURLToPath(new URL('../cloudflarebase.agent.json', import.meta.url).href), - 'utf8', - ).replace(/^\uFEFF/, ''), -) as { routes: { path: string; access: string }[] }; - -test('the route table mirrors the manifest', () => { - assert.deepEqual([...ROUTES], manifest.routes); -}); - -test('this agent declares no public route', () => { - assert.equal( - ROUTES.every((rule) => rule.access === 'operator'), - true, - ); - assert.equal(routeAccess('/apps/site/deploys'), 'operator'); - assert.equal(routeAccess('/apps/site/secrets'), 'operator'); - assert.equal(routeAccess('/apps/site/secrets/API_KEY'), 'operator'); - assert.equal(routeAccess('/apps/site/vars'), 'operator'); - assert.equal(routeAccess('/apps/site/build-env'), 'operator'); - assert.equal(routeAccess('/apps/site/analytics'), 'operator'); - assert.equal(routeAccess('/overview'), 'operator'); - assert.equal(routeAccess('/deploys'), 'operator'); - assert.equal(routeAccess('/'), 'operator'); - assert.equal(routeAccess('/whatever-comes-next'), 'operator'); -}); - -const gate = (path: string, env: { EXPOSE_OPERATOR_API?: string } = {}) => - gateOperatorRoutes(new URL(`https://app.example${path}`), env); - -test('a public Worker refuses every surface', () => { - for (const path of [ - '/agents/hosting-agent/p1/apps/site/deploys', - '/agents/hosting-agent/p1/apps/site/secrets', - '/agents/hosting-agent/p1/overview', - '/agents/hosting-agent/p1', - '/internal/projects/p1', - '/internal/projects/p1/apps/site', - '/internal/projects/p1/apps/site/build-env', - ]) { - assert.equal(gate(path)?.status, 404, path); - } -}); - -test('the flag opens the surface for a control-plane-only Worker', () => { - assert.equal( - gate('/agents/hosting-agent/p1/apps/site/deploys', { EXPOSE_OPERATOR_API: 'true' }), - null, - ); - assert.equal(gate('/internal/projects/p1/apps/site', { EXPOSE_OPERATOR_API: 'true' }), null); - assert.equal( - gate('/agents/hosting-agent/p1/overview', { EXPOSE_OPERATOR_API: '1' })?.status, - 404, - ); -}); diff --git a/agents/hosting/src/schemas.ts b/agents/hosting/src/schemas.ts deleted file mode 100644 index 30b07cfc..00000000 --- a/agents/hosting/src/schemas.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { z } from 'zod'; - -/** - * Trust-boundary schemas: strictObject for request bodies, `.catch` for - * env/storage reads, and a schema - never a cast - for anything that crosses - * a trust boundary. - */ - -// 48 characters: branch ids are `--`, so the ceiling has to hold -// a root plus a usable branch name. Mirrored in the console's -// src/lib/schemas/auth.ts and in agents/auth + agents/db - keep all four in -// sync. -export const projectIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{0,47}$/); - -// Demo roots are demo-<12..20 hex>; a demo BRANCH is demo--- -// (branches-design.md). Hosting refuses the whole family - anonymous code -// execution is an abuse machine - so the pattern is the refusal key here, -// not a cap selector. Mirrored in the console's $lib/console.ts and the -// other agents. -export const DEMO_PROJECT_PATTERN = /^demo-[a-f0-9]{12,20}(?:--[a-z0-9][a-z0-9-]{0,15})?$/; - -/** App names: subdomain charset, 3-48 chars, no `--` (mirrors the console's - * appNameSchema - the console owns claims; this copy only guards the DO's - * own registry keys). */ -export const appNameSchema = z - .string() - .regex(/^[a-z0-9][a-z0-9-]{2,47}$/) - .refine((value) => !value.includes('--')); - -/** Subdomains are DNS labels: app name, optional branch and numbering. */ -export const subdomainSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/); - -/** The `meta` part of a deploy's multipart body. */ -export const deployMetaSchema = z.strictObject({ - /** Part name of the Worker entry module; absent = assets-only app. */ - mainModule: z - .string() - .regex(/^[A-Za-z0-9._-]+\.(?:js|mjs)$/) - .optional(), - compatibilityDate: z - .string() - .regex(/^\d{4}-\d{2}-\d{2}$/) - .optional(), - compatibilityFlags: z - .array(z.string().regex(/^[a-z0-9_]+$/)) - .max(10) - .optional(), - /** Plain-text vars injected as bindings beside PROJECT_ID/CLOUDFLAREBASE_URL. */ - vars: z.record(z.string().regex(/^[A-Z][A-Z0-9_]*$/), z.string().max(5000)).optional(), - /** Static-asset serving behaviour; defaults chosen for app-shaped sites. */ - notFoundHandling: z.enum(['single-page-application', '404-page', 'none']).optional(), -}); - -export type DeployMeta = z.infer; - -export const secretBodySchema = z.strictObject({ - name: z.string().regex(/^[A-Z][A-Z0-9_]*$/), - value: z.string().min(1).max(5000), -}); - -/** Env var and secret names: UPPER_SNAKE, bounded. */ -export const varNameSchema = z - .string() - .regex(/^[A-Z][A-Z0-9_]*$/) - .max(128); - -/** Stored env values are single-line on purpose: build-time env is exported - * line-by-line into the runner's $GITHUB_ENV, and one contract for both - * stores keeps the console editor honest. */ -export const varValueSchema = z - .string() - .max(5000) - .regex(/^[^\r\n]*$/); - -/** Replace-the-set body for the vars stores (runtime and build). */ -export const varsBodySchema = z.strictObject({ - vars: z.record(varNameSchema, varValueSchema), -}); - -/** `apps.last_deploy_vars` is a storage read, so it gets a schema - a row - * written by an older version (or by hand) degrades to "no CLI vars". */ -export const storedVarsSchema = z.record(z.string(), z.string()).catch({}); - -/** One build secret value - encrypted at rest, name rides the path. */ -export const buildSecretBodySchema = z.strictObject({ - value: varValueSchema.min(1), -}); - -/** Analytics Engine SQL API envelope (mirrors the auth agent's copy). */ -export const analyticsApiResponseSchema = z.object({ data: z.array(z.unknown()).optional() }); - -/** Keyset cursor for the deploy list: `:`. */ -export const deployCursorSchema = z - .string() - .regex(/^\d{1,16}:[0-9a-f-]{36}$/) - .optional() - .catch(undefined); diff --git a/agents/hosting/src/tar.fixture.ts b/agents/hosting/src/tar.fixture.ts deleted file mode 100644 index e25fd19c..00000000 --- a/agents/hosting/src/tar.fixture.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * A REAL gzipped tar produced by bsdtar, inlined so the reader is tested - * against the format as another implementation writes it - a parser checked - * only against its own writer proves nothing. - * - * It has the shape GitHub sends: one --/ wrapper directory, - * a build output directory, repository furniture that must never be - * published, and a path too long for tar's 100-byte name field, which - * therefore travels as a PAX extended header. - */ -export const TARBALL_BASE64 = - 'H4sIAIfffWoAA+2azY6bMBSFWVfqO2TZLsz4B7BUTStV6iy76QtUBN8AHYKRbZqJqj5ZF32kvkIdZRMREoaEWpPibwNESkxy' + - 'uOf4XkVualBIQSNRuswIZQJWd8G0YIw554vd0dI97i9ITBlnMU8wX2BCOY+CRRw4oNUmVfZWrv2c7pe7EXr1D/PSFO1yqudg' + - 'nP6J1T9ijHn9XXBS/zKvpYJJ1rC/R5IkZ/SnpFP/EY/jYIEDB8xc/z+/ftdSwNe1FG0F+vWrwDMneutflNpMuAkYn//2lHv/' + - 'd0Gv/oeOMMFzMD7/4yjGXn8X9Or/5eHjp88P4VpMs8Zg/hPWzX8a+fx3gs1/BalYg0/+WTLs/81jfmUGjM//hDLq/d8Fz9O/' + - 'rAU8hd/0ZWsM+j+NO/rzZDf/8f7/77H+vxc6hKdGKqPfE58EM+J0/5c2TZhpPcEa4/d/MaXM178LbP0vpdj++OmrfpacqX+t' + - 'wUzR/l/S/9PI9/9OOK3/fs9XmHV17RrD+78j/+eUe/93gfX/+4J8KKCq5P2dPfM5MCsOrT5F30FtUSXrHIlSQWakvazTNSAj' + - 'UdPqApkCUAGpsJ7RpNogWdvrthYKBMqKVKWZAdUNjdH+zxi5+f6/z1iDl8d0mtegjX1l1Sr7BoW0Katq/yDgsfMfxinh/6H+' + - 'hxur4GXQq5oAaC6e9hwzOv8jHCW3Pv8f3FhPVnlX3bbN/0zWWlYQVjJ/Q976/J8VZ///tZHqcVXJzZVdIB4//2ec+P7PBc/U' + - 'PyvD7cWN4KD/k6P5f4z9/M8Ju/9/2ch5t8hK7/wej8czG4LgLwGrArYAMgAA'; diff --git a/agents/hosting/src/tar.ts b/agents/hosting/src/tar.ts deleted file mode 100644 index 5067777a..00000000 --- a/agents/hosting/src/tar.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Just enough tar+gzip to unpack a GitHub source tarball (Phase B, direct - * deploys). No dependency: the format is 512-byte headers and the runtime - * already gunzips, so pulling in a tar library would be more surface than - * code. - * - * Everything here is bounded. A tarball is attacker-controlled input - a - * decompression bomb is a handful of bytes on the wire - so the reader stops - * at a byte ceiling while still decompressing, never after. - */ - -const BLOCK = 512; - -export interface TarEntry { - /** Path as recorded in the archive, including the leading directory. */ - name: string; - /** A view into the decompressed buffer, not a copy. */ - bytes: Uint8Array; -} - -export class ArchiveTooLarge extends Error { - constructor(limit: number) { - super(`the repository archive exceeds ${Math.round(limit / (1024 * 1024))} MB`); - } -} - -/** Decompresses a gzip stream, refusing to buffer more than `maxBytes`. */ -export async function gunzip( - stream: ReadableStream, - maxBytes: number, -): Promise { - const reader = stream.pipeThrough(new DecompressionStream('gzip')).getReader(); - const chunks: Uint8Array[] = []; - let total = 0; - try { - for (;;) { - const { done, value } = await reader.read(); - if (done) break; - total += value.length; - // Checked as it arrives: waiting until the end is what a bomb wants. - if (total > maxBytes) throw new ArchiveTooLarge(maxBytes); - chunks.push(value); - } - } finally { - await reader.cancel().catch(() => {}); - } - - const buffer = new Uint8Array(total); - let offset = 0; - for (const chunk of chunks) { - buffer.set(chunk, offset); - offset += chunk.length; - } - return buffer; -} - -function readString(bytes: Uint8Array): string { - let end = bytes.indexOf(0); - if (end === -1) end = bytes.length; - return new TextDecoder().decode(bytes.subarray(0, end)); -} - -function readOctal(bytes: Uint8Array): number { - let text = ''; - for (const byte of bytes) { - // Fields are octal digits padded with NULs or spaces. - if (byte === 0 || byte === 0x20) break; - text += String.fromCharCode(byte); - } - const value = text ? Number.parseInt(text, 8) : 0; - return Number.isSafeInteger(value) && value >= 0 ? value : 0; -} - -/** `path=` from a PAX extended header, which is how long names travel. */ -function paxPath(data: Uint8Array): string | null { - const text = new TextDecoder().decode(data); - const match = text.match(/\d+ path=([^\n]+)\n/); - return match ? match[1] : null; -} - -/** - * Parses a tar buffer into its regular files. - * - * Directories, symlinks, and every other entry type are dropped: a static - * deploy is a set of files, and honouring a symlink out of the archive would - * be a path-traversal primitive. - */ -export function parseTar(buffer: Uint8Array, maxFiles: number): TarEntry[] { - const entries: TarEntry[] = []; - let offset = 0; - let pendingName: string | null = null; - - while (offset + BLOCK <= buffer.length) { - const header = buffer.subarray(offset, offset + BLOCK); - // Two zero blocks end the archive; one is enough to stop reading. - if (header.every((byte) => byte === 0)) break; - - const size = readOctal(header.subarray(124, 136)); - const type = String.fromCharCode(header[156]); - const prefix = readString(header.subarray(345, 500)); - const base = readString(header.subarray(0, 100)); - - offset += BLOCK; - const data = buffer.subarray(offset, offset + size); - offset += Math.ceil(size / BLOCK) * BLOCK; - - // GNU long name and PAX extended headers both describe the NEXT entry. - if (type === 'L') { - pendingName = readString(data); - continue; - } - if (type === 'x' || type === 'g') { - pendingName = paxPath(data) ?? pendingName; - continue; - } - - const name = pendingName ?? (prefix ? `${prefix}/${base}` : base); - pendingName = null; - - // '0' and NUL are both "regular file"; anything else is not ours. - if (type !== '0' && type !== '\0') continue; - entries.push({ name, bytes: data }); - if (entries.length > maxFiles) { - throw new Error(`the repository holds more than ${maxFiles} files`); - } - } - - return entries; -} - -/** - * Turns archive entries into deployable asset paths. - * - * GitHub wraps every tarball in one `--/` directory, so the - * first segment is always stripped. `assetsDir` then selects a subtree, and - * the result is rooted at `/` - `dist/index.html` under `assetsDir: 'dist'` - * serves as `/index.html`. - * - * Dotfiles and `node_modules` are skipped, mirroring the CLI's collector: - * `.github`, `.gitignore`, and friends are repository furniture, not content, - * and publishing them is at best noise and at worst a leak. - */ -export function toAssetPaths( - entries: TarEntry[], - assetsDir: string, -): { path: string; bytes: Uint8Array }[] { - const prefix = assetsDir.replace(/^\/+|\/+$/g, ''); - const assets: { path: string; bytes: Uint8Array }[] = []; - - for (const entry of entries) { - const segments = entry.name.split('/'); - segments.shift(); // GitHub's wrapper directory - if (!segments.length) continue; - - let relative = segments.join('/'); - if (prefix) { - if (relative !== prefix && !relative.startsWith(`${prefix}/`)) continue; - relative = relative.slice(prefix.length).replace(/^\/+/, ''); - } - if (!relative) continue; - - const parts = relative.split('/'); - if (parts.some((part) => part.startsWith('.') || part === 'node_modules')) continue; - // Belt and braces: a crafted name must never escape the asset root. - if (parts.includes('..')) continue; - - assets.push({ path: `/${relative}`, bytes: entry.bytes }); - } - - return assets; -} diff --git a/agents/hosting/src/tar.unit.test.ts b/agents/hosting/src/tar.unit.test.ts deleted file mode 100644 index 41625010..00000000 --- a/agents/hosting/src/tar.unit.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { ArchiveTooLarge, gunzip, parseTar, toAssetPaths } from './tar.js'; -import { TARBALL_BASE64 } from './tar.fixture.js'; - -/** - * The tar reader is fed attacker-controlled input from a GitHub tarball, so - * the fixture is a REAL archive from another tar implementation rather than - * one written by these tests. - */ - -function fixtureStream(): ReadableStream { - const bytes = Uint8Array.from(Buffer.from(TARBALL_BASE64, 'base64')); - return new ReadableStream({ - start(controller) { - controller.enqueue(bytes); - controller.close(); - }, - }); -} - -async function fixtureEntries() { - return parseTar(await gunzip(fixtureStream(), 16 * 1024 * 1024), 1000); -} - -test('parses every regular file out of a real tarball', async () => { - const names = (await fixtureEntries()).map((entry) => entry.name).sort(); - assert.deepEqual( - names, - [ - 'owner-repo-abc123def/.github/workflows/ci.yml', - 'owner-repo-abc123def/.gitignore', - 'owner-repo-abc123def/README.md', - 'owner-repo-abc123def/dist/app.css', - 'owner-repo-abc123def/dist/index.html', - 'owner-repo-abc123def/dist/assets/a-very-long-directory-name-to-push-the-header-past-one-hundred-characters/nested-further-still/deep.js', - 'owner-repo-abc123def/node_modules/pkg/index.js', - ].sort(), - ); -}); - -test('reads a path too long for the tar name field', async () => { - // The 100-byte name field cannot hold this, so it arrives as a PAX record. - // Getting it wrong silently truncates paths rather than failing loudly. - const entries = await fixtureEntries(); - const deep = entries.find((entry) => entry.name.endsWith('deep.js')); - assert.ok(deep, 'the long-path entry survived'); - assert.ok(deep.name.length > 100); - assert.equal(new TextDecoder().decode(deep.bytes).trim(), 'console.log(1)'); -}); - -test('directories and file contents come through intact', async () => { - const entries = await fixtureEntries(); - const index = entries.find((entry) => entry.name.endsWith('dist/index.html')); - assert.ok(index); - assert.equal(new TextDecoder().decode(index.bytes).trim(), '

hello

'); -}); - -test('assets are rooted at the selected directory', async () => { - const assets = toAssetPaths(await fixtureEntries(), 'dist'); - const paths = assets.map((asset) => asset.path).sort(); - // The wrapper directory AND the `dist` prefix are both stripped. - assert.deepEqual(paths, [ - '/app.css', - '/assets/a-very-long-directory-name-to-push-the-header-past-one-hundred-characters/nested-further-still/deep.js', - '/index.html', - ]); -}); - -test('repository furniture is never published', async () => { - // Deploying from the repo root must not ship .github, .gitignore, or a - // committed node_modules - at best noise, at worst a leak. - const paths = toAssetPaths(await fixtureEntries(), '').map((asset) => asset.path); - assert.ok(paths.includes('/README.md')); - assert.ok(paths.includes('/dist/index.html')); - assert.ok(!paths.some((path) => path.includes('.github'))); - assert.ok(!paths.some((path) => path.includes('.gitignore'))); - assert.ok(!paths.some((path) => path.includes('node_modules'))); -}); - -test('a missing directory selects nothing rather than everything', async () => { - // The failure that matters: falling back to the repo root here would - // publish the whole source tree on a typo'd assets directory. - assert.deepEqual(toAssetPaths(await fixtureEntries(), 'build'), []); -}); - -test('decompression stops at the byte ceiling', async () => { - await assert.rejects(() => gunzip(fixtureStream(), 64), ArchiveTooLarge); -}); - -test('the file-count ceiling is enforced', async () => { - const buffer = await gunzip(fixtureStream(), 16 * 1024 * 1024); - assert.throws(() => parseTar(buffer, 2), /more than 2 files/); -}); diff --git a/agents/hosting/template/worker-entry.ts b/agents/hosting/template/worker-entry.ts deleted file mode 100644 index 8bb85fa7..00000000 --- a/agents/hosting/template/worker-entry.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Cloudflarebase hosting agent entrypoint (added by `cloudflarebase add hosting`). - * - * Re-exporting the Durable Object class from YOUR worker's entrypoint is not - * optional plumbing - it is how the `HostingAgent` binding in wrangler.jsonc - * resolves. If your project already has a default export, drop the `default` - * re-export below and keep your own: - * - * export { HostingAgent } from '@cloudflarebase/hosting'; - */ -export { HostingAgent, default } from '@cloudflarebase/hosting'; - -/** - * Compile-time binding contract: if wrangler.jsonc is missing a binding the - * agent needs (or types one wrongly), this line fails your typecheck with the - * field named - before anything deploys. Run `wrangler types` after changing - * bindings so `Env` is current. - */ -import type { AssertHostingAgentEnv } from '@cloudflarebase/hosting'; -export type _HostingAgentBindings = AssertHostingAgentEnv; diff --git a/agents/hosting/template/wrangler-fragment.jsonc b/agents/hosting/template/wrangler-fragment.jsonc deleted file mode 100644 index 984e18e1..00000000 --- a/agents/hosting/template/wrangler-fragment.jsonc +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Wrangler fragment for the Cloudflarebase hosting agent. - * - * `cloudflarebase add hosting` merges these keys into your wrangler.jsonc: - * comments survive, your existing values are never overwritten, and Durable - * Object migrations append under your next free tag. - * - * The `migrations` block below is a FRESH v1 declaring the HostingAgent - * class. Do not copy the tag history from the Cloudflarebase repository. - * - * Workers for Platforms is a PAID Cloudflare add-on, and this fragment - * deliberately does not assume you have it: with only these keys the agent - * deploys, records nothing, and answers hosting requests with a 503 pointing - * at the setup steps. To actually serve apps you additionally need, by hand: - * - * 1. A dispatch namespace: `npx wrangler dispatch-namespace create `, - * then a binding here - - * "dispatch_namespaces": [{ "binding": "DISPATCH", "namespace": "" }] - * (add "outbound": { "service": "", "parameters": - * ["subdomain"] } and the var "DISPATCH_OUTBOUND": "true" if you deploy - * one - optional but recommended; see src/outbound.ts in the package - * repository for the v1 pass-through). - * 2. Vars: DISPATCH_NAMESPACE (the namespace name - the REST API needs it) - * and HOSTING_DOMAIN (the apex your apps serve under). - * 3. Secrets: `wrangler secret put CF_ACCOUNT_ID` and - * `wrangler secret put CF_HOSTING_API_TOKEN` (Workers Scripts edit). - * 4. A wildcard route `*./*` to this worker on your zone. - */ -{ - "migrations": [{ "new_sqlite_classes": ["HostingAgent"], "tag": "v1" }], - "durable_objects": { - "bindings": [{ "class_name": "HostingAgent", "name": "HostingAgent" }], - }, - "vars": { - // Empty disables the serving path; set your apex domain to enable it. - "HOSTING_DOMAIN": "", - // The Workers for Platforms dispatch namespace name (see header). - "DISPATCH_NAMESPACE": "", - /** - * DELIBERATELY ABSENT: "EXPOSE_OPERATOR_API": "true". - * - * Every route this agent serves deploys code, mints subdomains, writes - * secrets, or reports what is deployed, and none of them authenticate a - * caller - they are meant to sit behind a console that already has. On - * this Worker, which serves your application on a public hostname, they - * answer 404. Serving deployed apps on your wildcard hostname is - * unaffected. - * - * Your own code is unaffected too: reach the agent through the - * HostingAgent Durable Object namespace binding, which no HTTP caller - * can. - * - * Set it ONLY on a Worker that has no public hostname of its own - * (workers_dev false, no route beyond the app wildcard) and is fronted - * by something that authenticates operators. - */ - /** - * OPTIONAL. Empty disables error reporting, which is the default and a - * perfectly good place to stay - nothing else about the agent changes. - * - * To turn it on, put your DSN on the line below. It belongs here as a - * var and not in `wrangler secret put`: a Sentry DSN is public by - * design, and this line is uploaded as a plain_text binding of the same - * name, so it would override a secret you set under it. - */ - "SENTRY_DSN": "", - }, - "secrets": { - "required": [], - }, -} diff --git a/agents/hosting/tsconfig.build.json b/agents/hosting/tsconfig.build.json deleted file mode 100644 index 08c28bdc..00000000 --- a/agents/hosting/tsconfig.build.json +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Publish build for `@cloudflarebase/hosting`. - * - * Emits `dist/` from `src/` only. Deliberately left out of the published - * package: - * - * - `worker-configuration.d.ts` is a `types` entry rather than an input, so it - * is never emitted. Shipping it would declare a global `interface Env` in the - * consumer's project and clobber their own generated bindings - the exact - * opposite of what makes this package portable. - * - `src/env.d.ts` is a declaration input, which tsc does not copy to `outDir`. - * Its augmentation describes deployment shapes, not the consumer's config. - * - `bindings.test-d.ts` (the binding-contract negatives), executed by - * `tsc --noEmit` instead. - * - `*.unit.test.ts` and the archive fixture they read, which are run by - * `npm run test:unit` and have no business in a consumer's bundle. - * - * The emitted declarations name `Env` instead of inlining it, so in a consumer - * project it resolves to their `wrangler types` output. `AssertHostingAgentEnv` - * is what checks that output actually carries the bindings the agent reads. - */ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": false, - "declaration": true, - "outDir": "dist", - "rootDir": "src" - }, - "include": ["src/**/*"], - "exclude": ["src/**/*.test-d.ts", "src/**/*.unit.test.ts", "src/**/*.fixture.ts"] -} diff --git a/agents/hosting/tsconfig.json b/agents/hosting/tsconfig.json deleted file mode 100644 index 7e76e467..00000000 --- a/agents/hosting/tsconfig.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - "target": "es2024", - /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - "lib": ["es2024"], - /* Specify what JSX code is generated. */ - "jsx": "react-jsx", - - /* Specify what module code is generated. */ - "module": "es2022", - /* Specify how TypeScript looks up a file from a given module specifier. */ - "moduleResolution": "node", - /* Enable importing .json files */ - "resolveJsonModule": true, - - /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ - "allowJs": true, - /* Enable error reporting in type-checked JavaScript files. */ - "checkJs": false, - - /* Disable emitting files from a compilation. */ - "noEmit": true, - - /* Ensure that each file can be safely transpiled without relying on other imports. */ - "isolatedModules": true, - /* Allow 'import x from y' when a module doesn't have a default export. */ - "allowSyntheticDefaultImports": true, - /* Ensure that casing is correct in imports. */ - "forceConsistentCasingInFileNames": true, - - /* Enable all strict type-checking options. */ - "strict": true, - - /* Skip type checking all .d.ts files. */ - "skipLibCheck": true, - "types": ["./worker-configuration.d.ts", "node"] - }, - - /* - * `dist` is this project's own build output - with allowJs on it would be - * typechecked alongside the sources it was emitted from. `template` holds - * files meant to be copied into a consumer's project, so they import - * `@cloudflarebase/auth` by package name, which does not resolve from - * inside the package itself. - */ - "exclude": ["dist", "template", "node_modules"] -} diff --git a/agents/hosting/worker-configuration.d.ts b/agents/hosting/worker-configuration.d.ts deleted file mode 100644 index 04707545..00000000 --- a/agents/hosting/worker-configuration.d.ts +++ /dev/null @@ -1,14893 +0,0 @@ -/* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 9133629e56c43a1f4c15afbe4ad4541d) -// Runtime types generated with workerd@1.20260811.1 2026-07-10 nodejs_als,nodejs_compat -interface __BaseEnv_Env { - LOCAL_ANALYTICS?: D1Database; - HOSTING_REQUESTS?: AnalyticsEngineDataset; - EXPOSE_OPERATOR_API: "true"; - SENTRY_DSN: "" | "https://50ed6e26b886124ac39e983efb72144d@o4510375271530496.ingest.de.sentry.io/4511764380778576" | "https://093f7505d223a871223f9a15130ef116@o4510375271530496.ingest.de.sentry.io/4511764377043024"; - SENTRY_ENV: "development" | "test" | "production" | "preview"; - HOSTING_STUB?: "true"; - HOSTING_DOMAIN: "cfbase.dev" | "cfbase-preview.dev" | ""; - DISPATCH_NAMESPACE: "" | "cfbase-apps" | "cfbase-apps-preview"; - HOSTING_MASTER_KEY?: "local-dev-master-key-not-a-secret"; - WAE_DATASET?: "cloudflarebase_hosting_requests_local" | "cloudflarebase_hosting_requests_test" | "cloudflarebase_hosting_requests" | "cloudflarebase_hosting_requests_preview"; - HostingAgent: DurableObjectNamespace; - DISPATCH?: DispatchNamespace; - DISPATCH_OUTBOUND?: "true"; - HOSTING_APEX_REDIRECT?: "https://cloudflarebase.com" | "https://cloudflarebase-com-preview.cloudflarebase.workers.dev"; - CF_ACCOUNT_ID?: string; - CF_HOSTING_API_TOKEN?: string; -} -declare namespace Cloudflare { - interface GlobalProps { - mainModule: typeof import("./src/index"); - durableNamespaces: "HostingAgent"; - } - interface LocalEnv { - LOCAL_ANALYTICS: D1Database; - HOSTING_REQUESTS: AnalyticsEngineDataset; - EXPOSE_OPERATOR_API: "true"; - SENTRY_DSN: ""; - SENTRY_ENV: "development"; - HOSTING_STUB: "true"; - HOSTING_DOMAIN: "cfbase.dev"; - DISPATCH_NAMESPACE: ""; - HOSTING_MASTER_KEY: "local-dev-master-key-not-a-secret"; - WAE_DATASET: "cloudflarebase_hosting_requests_local"; - HostingAgent: DurableObjectNamespace; - } - interface TestEnv { - LOCAL_ANALYTICS: D1Database; - HOSTING_REQUESTS: AnalyticsEngineDataset; - EXPOSE_OPERATOR_API: "true"; - SENTRY_DSN: ""; - SENTRY_ENV: "test"; - HOSTING_STUB: "true"; - HOSTING_DOMAIN: "cfbase.dev"; - DISPATCH_NAMESPACE: ""; - HOSTING_MASTER_KEY: "local-dev-master-key-not-a-secret"; - WAE_DATASET: "cloudflarebase_hosting_requests_test"; - HostingAgent: DurableObjectNamespace; - } - interface ProductionEnv { - HOSTING_REQUESTS: AnalyticsEngineDataset; - DISPATCH: DispatchNamespace; - EXPOSE_OPERATOR_API: "true"; - SENTRY_DSN: "https://50ed6e26b886124ac39e983efb72144d@o4510375271530496.ingest.de.sentry.io/4511764380778576"; - SENTRY_ENV: "production"; - HOSTING_DOMAIN: "cfbase.dev"; - DISPATCH_NAMESPACE: "cfbase-apps"; - WAE_DATASET: "cloudflarebase_hosting_requests"; - DISPATCH_OUTBOUND: "true"; - HOSTING_APEX_REDIRECT: "https://cloudflarebase.com"; - CF_ACCOUNT_ID: string; - CF_HOSTING_API_TOKEN: string; - HostingAgent: DurableObjectNamespace; - } - interface PreviewEnv { - HOSTING_REQUESTS: AnalyticsEngineDataset; - DISPATCH: DispatchNamespace; - EXPOSE_OPERATOR_API: "true"; - SENTRY_DSN: "https://093f7505d223a871223f9a15130ef116@o4510375271530496.ingest.de.sentry.io/4511764377043024"; - SENTRY_ENV: "preview"; - HOSTING_DOMAIN: "cfbase-preview.dev"; - DISPATCH_NAMESPACE: "cfbase-apps-preview"; - DISPATCH_OUTBOUND: "true"; - HOSTING_APEX_REDIRECT: "https://cloudflarebase-com-preview.cloudflarebase.workers.dev"; - WAE_DATASET: "cloudflarebase_hosting_requests_preview"; - CF_ACCOUNT_ID: string; - CF_HOSTING_API_TOKEN: string; - HostingAgent: DurableObjectNamespace; - } - interface Env extends __BaseEnv_Env {} -} -interface Env extends __BaseEnv_Env {} -type StringifyValues> = { - [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; -}; -declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} -} - -// Begin runtime types -/*! ***************************************************************************** -Copyright (c) Cloudflare. All rights reserved. -Copyright (c) Microsoft Corporation. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ -/* eslint-disable */ -// noinspection JSUnusedGlobalSymbols -declare var onmessage: never; -/** - * The **`DOMException`** interface represents an abnormal event (called an **exception**) that occurs as a result of calling a method or accessing a property of a web API. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException) - */ -declare class DOMException extends Error { - constructor(message?: string, name?: string); - /** - * The **`message`** read-only property of the a message or description associated with the given error name. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message) - */ - readonly message: string; - /** - * The **`name`** read-only property of the one of the strings associated with an error name. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name) - */ - readonly name: string; - /** - * The **`code`** read-only property of the DOMException interface returns one of the legacy error code constants, or `0` if none match. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code) - */ - readonly code: number; - static readonly INDEX_SIZE_ERR: number; - static readonly DOMSTRING_SIZE_ERR: number; - static readonly HIERARCHY_REQUEST_ERR: number; - static readonly WRONG_DOCUMENT_ERR: number; - static readonly INVALID_CHARACTER_ERR: number; - static readonly NO_DATA_ALLOWED_ERR: number; - static readonly NO_MODIFICATION_ALLOWED_ERR: number; - static readonly NOT_FOUND_ERR: number; - static readonly NOT_SUPPORTED_ERR: number; - static readonly INUSE_ATTRIBUTE_ERR: number; - static readonly INVALID_STATE_ERR: number; - static readonly SYNTAX_ERR: number; - static readonly INVALID_MODIFICATION_ERR: number; - static readonly NAMESPACE_ERR: number; - static readonly INVALID_ACCESS_ERR: number; - static readonly VALIDATION_ERR: number; - static readonly TYPE_MISMATCH_ERR: number; - static readonly SECURITY_ERR: number; - static readonly NETWORK_ERR: number; - static readonly ABORT_ERR: number; - static readonly URL_MISMATCH_ERR: number; - static readonly QUOTA_EXCEEDED_ERR: number; - static readonly TIMEOUT_ERR: number; - static readonly INVALID_NODE_TYPE_ERR: number; - static readonly DATA_CLONE_ERR: number; - get stack(): any; - set stack(value: any); -} -type WorkerGlobalScopeEventMap = { - fetch: FetchEvent; - scheduled: ScheduledEvent; - queue: QueueEvent; - unhandledrejection: PromiseRejectionEvent; - rejectionhandled: PromiseRejectionEvent; -}; -declare abstract class WorkerGlobalScope extends EventTarget { - EventTarget: typeof EventTarget; -} -/* The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). * - * The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) - */ -interface Console { - "assert"(condition?: boolean, ...data: any[]): void; - /** - * The **`console.clear()`** static method clears the console if possible. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static) - */ - clear(): void; - /** - * The **`console.count()`** static method logs the number of times that this particular call to `count()` has been called. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) - */ - count(label?: string): void; - /** - * The **`console.countReset()`** static method resets counter used with console/count_static. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static) - */ - countReset(label?: string): void; - /** - * The **`console.debug()`** static method outputs a message to the console at the 'debug' log level. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) - */ - debug(...data: any[]): void; - /** - * The **`console.dir()`** static method displays a list of the properties of the specified JavaScript object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static) - */ - dir(item?: any, options?: any): void; - /** - * The **`console.dirxml()`** static method displays an interactive tree of the descendant elements of the specified XML/HTML element. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static) - */ - dirxml(...data: any[]): void; - /** - * The **`console.error()`** static method outputs a message to the console at the 'error' log level. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static) - */ - error(...data: any[]): void; - /** - * The **`console.group()`** static method creates a new inline group in the Web console log, causing any subsequent console messages to be indented by an additional level, until console/groupEnd_static is called. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static) - */ - group(...data: any[]): void; - /** - * The **`console.groupCollapsed()`** static method creates a new inline group in the console. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static) - */ - groupCollapsed(...data: any[]): void; - /** - * The **`console.groupEnd()`** static method exits the current inline group in the console. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static) - */ - groupEnd(): void; - /** - * The **`console.info()`** static method outputs a message to the console at the 'info' log level. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) - */ - info(...data: any[]): void; - /** - * The **`console.log()`** static method outputs a message to the console. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) - */ - log(...data: any[]): void; - /** - * The **`console.table()`** static method displays tabular data as a table. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static) - */ - table(tabularData?: any, properties?: string[]): void; - /** - * The **`console.time()`** static method starts a timer you can use to track how long an operation takes. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static) - */ - time(label?: string): void; - /** - * The **`console.timeEnd()`** static method stops a timer that was previously started by calling console/time_static. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static) - */ - timeEnd(label?: string): void; - /** - * The **`console.timeLog()`** static method logs the current value of a timer that was previously started by calling console/time_static. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static) - */ - timeLog(label?: string, ...data: any[]): void; - timeStamp(label?: string): void; - /** - * The **`console.trace()`** static method outputs a stack trace to the console. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static) - */ - trace(...data: any[]): void; - /** - * The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static) - */ - warn(...data: any[]): void; -} -declare const console: Console; -type BufferSource = ArrayBufferView | ArrayBuffer; -type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array; -declare namespace WebAssembly { - class CompileError extends Error { - constructor(message?: string); - } - class RuntimeError extends Error { - constructor(message?: string); - } - type ValueType = "anyfunc" | "externref" | "f32" | "f64" | "i32" | "i64" | "v128"; - interface GlobalDescriptor { - value: ValueType; - mutable?: boolean; - } - class Global { - constructor(descriptor: GlobalDescriptor, value?: any); - value: any; - valueOf(): any; - } - type ImportValue = ExportValue | number; - type ModuleImports = Record; - type Imports = Record; - type ExportValue = Function | Global | Memory | Table; - type Exports = Record; - class Instance { - constructor(module: Module, imports?: Imports); - readonly exports: Exports; - } - interface MemoryDescriptor { - initial: number; - maximum?: number; - shared?: boolean; - } - class Memory { - constructor(descriptor: MemoryDescriptor); - readonly buffer: ArrayBuffer; - grow(delta: number): number; - } - type ImportExportKind = "function" | "global" | "memory" | "table"; - interface ModuleExportDescriptor { - kind: ImportExportKind; - name: string; - } - interface ModuleImportDescriptor { - kind: ImportExportKind; - module: string; - name: string; - } - abstract class Module { - static customSections(module: Module, sectionName: string): ArrayBuffer[]; - static exports(module: Module): ModuleExportDescriptor[]; - static imports(module: Module): ModuleImportDescriptor[]; - } - type TableKind = "anyfunc" | "externref"; - interface TableDescriptor { - element: TableKind; - initial: number; - maximum?: number; - } - class Table { - constructor(descriptor: TableDescriptor, value?: any); - readonly length: number; - get(index: number): any; - grow(delta: number, value?: any): number; - set(index: number, value?: any): void; - } - function instantiate(module: Module, imports?: Imports): Promise; - function validate(bytes: BufferSource): boolean; -} -/** - * The **`ServiceWorkerGlobalScope`** interface of the Service Worker API represents the global execution context of a service worker. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope) - */ -interface ServiceWorkerGlobalScope extends WorkerGlobalScope { - DOMException: typeof DOMException; - WorkerGlobalScope: typeof WorkerGlobalScope; - btoa(data: string): string; - atob(data: string): string; - setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; - setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; - clearTimeout(timeoutId: number | null): void; - setInterval(callback: (...args: any[]) => void, msDelay?: number): number; - setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; - clearInterval(timeoutId: number | null): void; - queueMicrotask(task: Function): void; - structuredClone(value: T, options?: StructuredSerializeOptions): T; - reportError(error: any): void; - fetch(input: RequestInfo | URL, init?: RequestInit): Promise; - self: ServiceWorkerGlobalScope; - crypto: Crypto; - caches: CacheStorage; - scheduler: Scheduler; - performance: Performance; - Cloudflare: Cloudflare; - readonly origin: string; - Event: typeof Event; - ExtendableEvent: typeof ExtendableEvent; - CustomEvent: typeof CustomEvent; - PromiseRejectionEvent: typeof PromiseRejectionEvent; - FetchEvent: typeof FetchEvent; - TailEvent: typeof TailEvent; - TraceEvent: typeof TailEvent; - ScheduledEvent: typeof ScheduledEvent; - MessageEvent: typeof MessageEvent; - CloseEvent: typeof CloseEvent; - ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader; - ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader; - ReadableStream: typeof ReadableStream; - WritableStream: typeof WritableStream; - WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter; - TransformStream: typeof TransformStream; - ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy; - CountQueuingStrategy: typeof CountQueuingStrategy; - ErrorEvent: typeof ErrorEvent; - MessageChannel: typeof MessageChannel; - MessagePort: typeof MessagePort; - EventSource: typeof EventSource; - ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest; - ReadableStreamDefaultController: typeof ReadableStreamDefaultController; - ReadableByteStreamController: typeof ReadableByteStreamController; - WritableStreamDefaultController: typeof WritableStreamDefaultController; - TransformStreamDefaultController: typeof TransformStreamDefaultController; - CompressionStream: typeof CompressionStream; - DecompressionStream: typeof DecompressionStream; - TextEncoderStream: typeof TextEncoderStream; - TextDecoderStream: typeof TextDecoderStream; - Headers: typeof Headers; - Body: typeof Body; - Request: typeof Request; - Response: typeof Response; - WebSocket: typeof WebSocket; - WebSocketPair: typeof WebSocketPair; - WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair; - AbortController: typeof AbortController; - AbortSignal: typeof AbortSignal; - TextDecoder: typeof TextDecoder; - TextEncoder: typeof TextEncoder; - navigator: Navigator; - Navigator: typeof Navigator; - URL: typeof URL; - URLSearchParams: typeof URLSearchParams; - URLPattern: typeof URLPattern; - Blob: typeof Blob; - File: typeof File; - FormData: typeof FormData; - Crypto: typeof Crypto; - SubtleCrypto: typeof SubtleCrypto; - CryptoKey: typeof CryptoKey; - CacheStorage: typeof CacheStorage; - Cache: typeof Cache; - FixedLengthStream: typeof FixedLengthStream; - IdentityTransformStream: typeof IdentityTransformStream; - HTMLRewriter: typeof HTMLRewriter; -} -declare function addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; -declare function removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; -/** - * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) - */ -declare function dispatchEvent(event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap]): boolean; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */ -declare function btoa(data: string): string; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */ -declare function atob(data: string): string; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ -declare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ -declare function setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */ -declare function clearTimeout(timeoutId: number | null): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ -declare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ -declare function setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */ -declare function clearInterval(timeoutId: number | null): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */ -declare function queueMicrotask(task: Function): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */ -declare function structuredClone(value: T, options?: StructuredSerializeOptions): T; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */ -declare function reportError(error: any): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */ -declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise; -declare const self: ServiceWorkerGlobalScope; -/** -* The Web Crypto API provides a set of low-level functions for common cryptographic tasks. -* The Workers runtime implements the full surface of this API, but with some differences in -* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) -* compared to those implemented in most browsers. -* -* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) -*/ -declare const crypto: Crypto; -/** -* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. -* -* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) -*/ -declare const caches: CacheStorage; -declare const scheduler: Scheduler; -/** -* The Workers runtime supports a subset of the Performance API, used to measure timing and performance, -* as well as timing of subrequests and other operations. -* -* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) -*/ -declare const performance: Performance; -declare const Cloudflare: Cloudflare; -declare const origin: string; -declare const navigator: Navigator; -interface TestController { -} -interface ExecutionContext { - waitUntil(promise: Promise): void; - passThroughOnException(): void; - readonly exports: Cloudflare.Exports; - readonly props: Props; - cache?: CacheContext; - readonly access?: CloudflareAccessContext; - tracing: Tracing; - abort(reason?: any): void; -} -type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; -type ExportedHandlerConnectHandler = (socket: Socket, env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; -type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise; -interface ExportedHandler { - fetch?: ExportedHandlerFetchHandler; - connect?: ExportedHandlerConnectHandler; - tail?: ExportedHandlerTailHandler; - trace?: ExportedHandlerTraceHandler; - tailStream?: ExportedHandlerTailStreamHandler; - scheduled?: ExportedHandlerScheduledHandler; - test?: ExportedHandlerTestHandler; - email?: EmailExportedHandler; - queue?: ExportedHandlerQueueHandler; -} -interface StructuredSerializeOptions { - transfer?: any[]; -} -declare abstract class Navigator { - sendBeacon(url: string, body?: BodyInit): boolean; - readonly userAgent: string; - readonly hardwareConcurrency: number; - readonly platform: string; - readonly language: string; - readonly languages: string[]; -} -interface AlarmInvocationInfo { - readonly isRetry: boolean; - readonly retryCount: number; - readonly scheduledTime: number; -} -interface Cloudflare { - readonly compatibilityFlags: Record; -} -interface CachePurgeError { - code: number; - message: string; -} -interface CachePurgeResult { - success: boolean; - errors: CachePurgeError[]; -} -interface CachePurgeOptions { - tags?: string[]; - pathPrefixes?: string[]; - purgeEverything?: boolean; -} -interface CacheContext { - purge(options: CachePurgeOptions): Promise; -} -interface CloudflareAccessContext { - readonly aud: string; - getIdentity(): Promise; -} -declare abstract class ColoLocalActorNamespace { - get(actorId: string): Fetcher; -} -interface DurableObject { - fetch(request: Request): Response | Promise; - connect?(socket: Socket): void | Promise; - alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; - webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; - webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; - webSocketError?(ws: WebSocket, error: unknown): void | Promise; -} -type DurableObjectStub = Fetcher & { - readonly id: DurableObjectId; - readonly name?: string; -}; -interface DurableObjectId { - toString(): string; - equals(other: DurableObjectId): boolean; - readonly name?: string; - readonly jurisdiction?: string; -} -declare abstract class DurableObjectNamespace { - newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId; - idFromName(name: string): DurableObjectId; - idFromString(id: string): DurableObjectId; - get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub; - getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub; - jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace; -} -type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high" | "us"; -interface DurableObjectNamespaceNewUniqueIdOptions { - jurisdiction?: DurableObjectJurisdiction; -} -type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "apac-ne" | "apac-se" | "oc" | "afr" | "me"; -type DurableObjectRoutingMode = "primary-only"; -interface DurableObjectNamespaceGetDurableObjectOptions { - locationHint?: DurableObjectLocationHint; - routingMode?: DurableObjectRoutingMode; -} -interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> { -} -interface DurableObjectState { - waitUntil(promise: Promise): void; - readonly exports: Cloudflare.Exports; - readonly props: Props; - readonly id: DurableObjectId; - readonly storage: DurableObjectStorage; - container?: Container; - facets: DurableObjectFacets; - blockConcurrencyWhile(callback: () => Promise): Promise; - acceptWebSocket(ws: WebSocket, tags?: string[]): void; - getWebSockets(tag?: string): WebSocket[]; - setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void; - getWebSocketAutoResponse(): WebSocketRequestResponsePair | null; - getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null; - setHibernatableWebSocketEventTimeout(timeoutMs?: number): void; - getHibernatableWebSocketEventTimeout(): number | null; - getTags(ws: WebSocket): string[]; - abort(reason?: string): void; -} -interface DurableObjectTransaction { - get(key: string, options?: DurableObjectGetOptions): Promise; - get(keys: string[], options?: DurableObjectGetOptions): Promise>; - list(options?: DurableObjectListOptions): Promise>; - put(key: string, value: T, options?: DurableObjectPutOptions): Promise; - put(entries: Record, options?: DurableObjectPutOptions): Promise; - delete(key: string, options?: DurableObjectPutOptions): Promise; - delete(keys: string[], options?: DurableObjectPutOptions): Promise; - rollback(): void; - getAlarm(options?: DurableObjectGetAlarmOptions): Promise; - setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; - deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; -} -interface DurableObjectStorage { - get(key: string, options?: DurableObjectGetOptions): Promise; - get(keys: string[], options?: DurableObjectGetOptions): Promise>; - list(options?: DurableObjectListOptions): Promise>; - put(key: string, value: T, options?: DurableObjectPutOptions): Promise; - put(entries: Record, options?: DurableObjectPutOptions): Promise; - delete(key: string, options?: DurableObjectPutOptions): Promise; - delete(keys: string[], options?: DurableObjectPutOptions): Promise; - deleteAll(options?: DurableObjectPutOptions): Promise; - transaction(closure: (txn: DurableObjectTransaction) => Promise): Promise; - getAlarm(options?: DurableObjectGetAlarmOptions): Promise; - setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; - deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; - sync(): Promise; - sql: SqlStorage; - kv: SyncKvStorage; - transactionSync(closure: () => T): T; - getCurrentBookmark(): Promise; - getBookmarkForTime(timestamp: number | Date): Promise; - onNextSessionRestoreBookmark(bookmark: string): Promise; -} -interface DurableObjectListOptions { - start?: string; - startAfter?: string; - end?: string; - prefix?: string; - reverse?: boolean; - limit?: number; - allowConcurrency?: boolean; - noCache?: boolean; -} -interface DurableObjectGetOptions { - allowConcurrency?: boolean; - noCache?: boolean; -} -interface DurableObjectGetAlarmOptions { - allowConcurrency?: boolean; -} -interface DurableObjectPutOptions { - allowConcurrency?: boolean; - allowUnconfirmed?: boolean; - noCache?: boolean; -} -interface DurableObjectSetAlarmOptions { - allowConcurrency?: boolean; - allowUnconfirmed?: boolean; -} -declare class WebSocketRequestResponsePair { - constructor(request: string, response: string); - get request(): string; - get response(): string; -} -interface DurableObjectFacets { - get(name: string, getStartupOptions: () => FacetStartupOptions | Promise>): Fetcher; - abort(name: string, reason: any): void; - delete(name: string): void; - clone(src: string, dst: string): void; -} -interface FacetStartupOptions { - id?: DurableObjectId | string; - class: DurableObjectClass; -} -interface AnalyticsEngineDataset { - writeDataPoint(event?: AnalyticsEngineDataPoint): void; -} -interface AnalyticsEngineDataPoint { - indexes?: ((ArrayBuffer | string) | null)[]; - doubles?: number[]; - blobs?: ((ArrayBuffer | string) | null)[]; -} -/** - * The **`Event`** interface represents an event which takes place on an `EventTarget`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event) - */ -declare class Event { - constructor(type: string, init?: EventInit); - /** - * The **`type`** read-only property of the Event interface returns a string containing the event's type. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type) - */ - get type(): string; - /** - * The **`eventPhase`** read-only property of the being evaluated. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase) - */ - get eventPhase(): number; - /** - * The read-only **`composed`** property of the or not the event will propagate across the shadow DOM boundary into the standard DOM. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed) - */ - get composed(): boolean; - /** - * The **`bubbles`** read-only property of the Event interface indicates whether the event bubbles up through the DOM tree or not. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles) - */ - get bubbles(): boolean; - /** - * The **`cancelable`** read-only property of the Event interface indicates whether the event can be canceled, and therefore prevented as if the event never happened. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable) - */ - get cancelable(): boolean; - /** - * The **`defaultPrevented`** read-only property of the Event interface returns a boolean value indicating whether or not the call to Event.preventDefault() canceled the event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented) - */ - get defaultPrevented(): boolean; - /** - * The Event property **`returnValue`** indicates whether the default action for this event has been prevented or not. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue) - */ - get returnValue(): boolean; - /** - * The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget) - */ - get currentTarget(): EventTarget | undefined; - /** - * The read-only **`target`** property of the dispatched. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target) - */ - get target(): EventTarget | undefined; - /** - * The deprecated **`Event.srcElement`** is an alias for the Event.target property. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement) - */ - get srcElement(): EventTarget | undefined; - /** - * The **`timeStamp`** read-only property of the Event interface returns the time (in milliseconds) at which the event was created. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp) - */ - get timeStamp(): number; - /** - * The **`isTrusted`** read-only property of the when the event was generated by the user agent (including via user actions and programmatic methods such as HTMLElement.focus()), and `false` when the event was dispatched via The only exception is the `click` event, which initializes the `isTrusted` property to `false` in user agents. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted) - */ - get isTrusted(): boolean; - /** - * The **`cancelBubble`** property of the Event interface is deprecated. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) - */ - get cancelBubble(): boolean; - /** - * The **`cancelBubble`** property of the Event interface is deprecated. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) - */ - set cancelBubble(value: boolean); - /** - * The **`stopImmediatePropagation()`** method of the If several listeners are attached to the same element for the same event type, they are called in the order in which they were added. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation) - */ - stopImmediatePropagation(): void; - /** - * The **`preventDefault()`** method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault) - */ - preventDefault(): void; - /** - * The **`stopPropagation()`** method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation) - */ - stopPropagation(): void; - /** - * The **`composedPath()`** method of the Event interface returns the event's path which is an array of the objects on which listeners will be invoked. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath) - */ - composedPath(): EventTarget[]; - static readonly NONE: number; - static readonly CAPTURING_PHASE: number; - static readonly AT_TARGET: number; - static readonly BUBBLING_PHASE: number; -} -interface EventInit { - bubbles?: boolean; - cancelable?: boolean; - composed?: boolean; -} -type EventListener = (event: EventType) => void; -interface EventListenerObject { - handleEvent(event: EventType): void; -} -type EventListenerOrEventListenerObject = EventListener | EventListenerObject; -/** - * The **`EventTarget`** interface is implemented by objects that can receive events and may have listeners for them. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget) - */ -declare class EventTarget = Record> { - constructor(); - /** - * The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) - */ - addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; - /** - * The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener) - */ - removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; - /** - * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) - */ - dispatchEvent(event: EventMap[keyof EventMap]): boolean; -} -interface EventTargetEventListenerOptions { - capture?: boolean; -} -interface EventTargetAddEventListenerOptions { - capture?: boolean; - passive?: boolean; - once?: boolean; - signal?: AbortSignal; -} -interface EventTargetHandlerObject { - handleEvent: (event: Event) => any | undefined; -} -/** - * The **`AbortController`** interface represents a controller object that allows you to abort one or more Web requests as and when desired. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController) - */ -declare class AbortController { - constructor(); - /** - * The **`signal`** read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal) - */ - get signal(): AbortSignal; - /** - * The **`abort()`** method of the AbortController interface aborts an asynchronous operation before it has completed. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort) - */ - abort(reason?: any): void; -} -/** - * The **`AbortSignal`** interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal) - */ -declare abstract class AbortSignal extends EventTarget { - /** - * The **`AbortSignal.abort()`** static method returns an AbortSignal that is already set as aborted (and which does not trigger an AbortSignal/abort_event event). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static) - */ - static abort(reason?: any): AbortSignal; - /** - * The **`AbortSignal.timeout()`** static method returns an AbortSignal that will automatically abort after a specified time. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static) - */ - static timeout(delay: number): AbortSignal; - /** - * The **`AbortSignal.any()`** static method takes an iterable of abort signals and returns an AbortSignal. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static) - */ - static any(signals: AbortSignal[]): AbortSignal; - /** - * The **`aborted`** read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (`true`) or not (`false`). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted) - */ - get aborted(): boolean; - /** - * The **`reason`** read-only property returns a JavaScript value that indicates the abort reason. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason) - */ - get reason(): any; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ - get onabort(): any | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ - set onabort(value: any | null); - /** - * The **`throwIfAborted()`** method throws the signal's abort AbortSignal.reason if the signal has been aborted; otherwise it does nothing. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted) - */ - throwIfAborted(): void; -} -interface Scheduler { - wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise; -} -interface SchedulerWaitOptions { - signal?: AbortSignal; -} -/** - * The **`ExtendableEvent`** interface extends the lifetime of the `install` and `activate` events dispatched on the global scope as part of the service worker lifecycle. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent) - */ -declare abstract class ExtendableEvent extends Event { - /** - * The **`ExtendableEvent.waitUntil()`** method tells the event dispatcher that work is ongoing. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil) - */ - waitUntil(promise: Promise): void; -} -/** - * The **`CustomEvent`** interface represents events initialized by an application for any purpose. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent) - */ -declare class CustomEvent extends Event { - constructor(type: string, init?: CustomEventCustomEventInit); - /** - * The read-only **`detail`** property of the CustomEvent interface returns any data passed when initializing the event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail) - */ - get detail(): T; -} -interface CustomEventCustomEventInit { - bubbles?: boolean; - cancelable?: boolean; - composed?: boolean; - detail?: any; -} -/** - * The **`Blob`** interface represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob) - */ -declare class Blob { - constructor(bits?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); - /** - * The **`size`** read-only property of the Blob interface returns the size of the Blob or File in bytes. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) - */ - get size(): number; - /** - * The **`type`** read-only property of the Blob interface returns the MIME type of the file. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) - */ - get type(): string; - /** - * The **`slice()`** method of the Blob interface creates and returns a new `Blob` object which contains data from a subset of the blob on which it's called. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) - */ - slice(start?: number, end?: number, type?: string): Blob; - /** - * The **`arrayBuffer()`** method of the Blob interface returns a Promise that resolves with the contents of the blob as binary data contained in an ArrayBuffer. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer) - */ - arrayBuffer(): Promise; - /** - * The **`bytes()`** method of the Blob interface returns a Promise that resolves with a Uint8Array containing the contents of the blob as an array of bytes. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes) - */ - bytes(): Promise; - /** - * The **`text()`** method of the string containing the contents of the blob, interpreted as UTF-8. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) - */ - text(): Promise; - /** - * The **`stream()`** method of the Blob interface returns a ReadableStream which upon reading returns the data contained within the `Blob`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream) - */ - stream(): ReadableStream; -} -interface BlobOptions { - type?: string; -} -/** - * The **`File`** interface provides information about files and allows JavaScript in a web page to access their content. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File) - */ -declare class File extends Blob { - constructor(bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined, name: string, options?: FileOptions); - /** - * The **`name`** read-only property of the File interface returns the name of the file represented by a File object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) - */ - get name(): string; - /** - * The **`lastModified`** read-only property of the File interface provides the last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) - */ - get lastModified(): number; -} -interface FileOptions { - type?: string; - lastModified?: number; -} -/** -* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. -* -* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) -*/ -declare abstract class CacheStorage { - /** - * The **`open()`** method of the the Cache object matching the `cacheName`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open) - */ - open(cacheName: string): Promise; - readonly default: Cache; -} -/** -* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. -* -* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) -*/ -declare abstract class Cache { - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */ - delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */ - match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */ - put(request: RequestInfo | URL, response: Response): Promise; -} -interface CacheQueryOptions { - ignoreMethod?: boolean; -} -/** -* The Web Crypto API provides a set of low-level functions for common cryptographic tasks. -* The Workers runtime implements the full surface of this API, but with some differences in -* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) -* compared to those implemented in most browsers. -* -* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) -*/ -declare abstract class Crypto { - /** - * The **`Crypto.subtle`** read-only property returns a cryptographic operations. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle) - */ - get subtle(): SubtleCrypto; - /** - * The **`Crypto.getRandomValues()`** method lets you get cryptographically strong random values. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues) - */ - getRandomValues(buffer: T): T; - /** - * The **`randomUUID()`** method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID) - */ - randomUUID(): string; - DigestStream: typeof DigestStream; -} -/** - * The **`SubtleCrypto`** interface of the Web Crypto API provides a number of low-level cryptographic functions. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto) - */ -declare abstract class SubtleCrypto { - /** - * The **`encrypt()`** method of the SubtleCrypto interface encrypts data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt) - */ - encrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, key: CryptoKey, plainText: ArrayBuffer | ArrayBufferView): Promise; - /** - * The **`decrypt()`** method of the SubtleCrypto interface decrypts some encrypted data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt) - */ - decrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, key: CryptoKey, cipherText: ArrayBuffer | ArrayBufferView): Promise; - /** - * The **`sign()`** method of the SubtleCrypto interface generates a digital signature. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign) - */ - sign(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, data: ArrayBuffer | ArrayBufferView): Promise; - /** - * The **`verify()`** method of the SubtleCrypto interface verifies a digital signature. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify) - */ - verify(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, signature: ArrayBuffer | ArrayBufferView, data: ArrayBuffer | ArrayBufferView): Promise; - /** - * The **`digest()`** method of the SubtleCrypto interface generates a _digest_ of the given data, using the specified hash function. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest) - */ - digest(algorithm: string | SubtleCryptoHashAlgorithm, data: ArrayBuffer | ArrayBufferView): Promise; - /** - * The **`generateKey()`** method of the SubtleCrypto interface is used to generate a new key (for symmetric algorithms) or key pair (for public-key algorithms). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) - */ - generateKey(algorithm: string | SubtleCryptoGenerateKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; - /** - * The **`deriveKey()`** method of the SubtleCrypto interface can be used to derive a secret key from a master key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey) - */ - deriveKey(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, baseKey: CryptoKey, derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; - /** - * The **`deriveBits()`** method of the key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits) - */ - deriveBits(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, baseKey: CryptoKey, length?: number | null): Promise; - /** - * The **`importKey()`** method of the SubtleCrypto interface imports a key: that is, it takes as input a key in an external, portable format and gives you a CryptoKey object that you can use in the Web Crypto API. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey) - */ - importKey(format: string, keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey, algorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; - /** - * The **`exportKey()`** method of the SubtleCrypto interface exports a key: that is, it takes as input a CryptoKey object and gives you the key in an external, portable format. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey) - */ - exportKey(format: string, key: CryptoKey): Promise; - /** - * The **`wrapKey()`** method of the SubtleCrypto interface 'wraps' a key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) - */ - wrapKey(format: string, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm): Promise; - /** - * The **`unwrapKey()`** method of the SubtleCrypto interface 'unwraps' a key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey) - */ - unwrapKey(format: string, wrappedKey: ArrayBuffer | ArrayBufferView, unwrappingKey: CryptoKey, unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm, unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; - timingSafeEqual(a: ArrayBuffer | ArrayBufferView, b: ArrayBuffer | ArrayBufferView): boolean; -} -/** - * The **`CryptoKey`** interface of the Web Crypto API represents a cryptographic key obtained from one of the SubtleCrypto methods SubtleCrypto.generateKey, SubtleCrypto.deriveKey, SubtleCrypto.importKey, or SubtleCrypto.unwrapKey. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey) - */ -declare abstract class CryptoKey { - /** - * The read-only **`type`** property of the CryptoKey interface indicates which kind of key is represented by the object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type) - */ - readonly type: string; - /** - * The read-only **`extractable`** property of the CryptoKey interface indicates whether or not the key may be extracted using `SubtleCrypto.exportKey()` or `SubtleCrypto.wrapKey()`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable) - */ - readonly extractable: boolean; - /** - * The read-only **`algorithm`** property of the CryptoKey interface returns an object describing the algorithm for which this key can be used, and any associated extra parameters. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm) - */ - readonly algorithm: CryptoKeyKeyAlgorithm | CryptoKeyAesKeyAlgorithm | CryptoKeyHmacKeyAlgorithm | CryptoKeyRsaKeyAlgorithm | CryptoKeyEllipticKeyAlgorithm | CryptoKeyArbitraryKeyAlgorithm; - /** - * The read-only **`usages`** property of the CryptoKey interface indicates what can be done with the key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages) - */ - readonly usages: string[]; -} -interface CryptoKeyPair { - publicKey: CryptoKey; - privateKey: CryptoKey; -} -interface JsonWebKey { - kty: string; - use?: string; - key_ops?: string[]; - alg?: string; - ext?: boolean; - crv?: string; - x?: string; - y?: string; - d?: string; - n?: string; - e?: string; - p?: string; - q?: string; - dp?: string; - dq?: string; - qi?: string; - oth?: RsaOtherPrimesInfo[]; - k?: string; -} -interface RsaOtherPrimesInfo { - r?: string; - d?: string; - t?: string; -} -interface SubtleCryptoDeriveKeyAlgorithm { - name: string; - salt?: (ArrayBuffer | ArrayBufferView); - iterations?: number; - hash?: (string | SubtleCryptoHashAlgorithm); - $public?: CryptoKey; - info?: (ArrayBuffer | ArrayBufferView); -} -interface SubtleCryptoEncryptAlgorithm { - name: string; - iv?: (ArrayBuffer | ArrayBufferView); - additionalData?: (ArrayBuffer | ArrayBufferView); - tagLength?: number; - counter?: (ArrayBuffer | ArrayBufferView); - length?: number; - label?: (ArrayBuffer | ArrayBufferView); -} -interface SubtleCryptoGenerateKeyAlgorithm { - name: string; - hash?: (string | SubtleCryptoHashAlgorithm); - modulusLength?: number; - publicExponent?: (ArrayBuffer | ArrayBufferView); - length?: number; - namedCurve?: string; -} -interface SubtleCryptoHashAlgorithm { - name: string; -} -interface SubtleCryptoImportKeyAlgorithm { - name: string; - hash?: (string | SubtleCryptoHashAlgorithm); - length?: number; - namedCurve?: string; - compressed?: boolean; -} -interface SubtleCryptoSignAlgorithm { - name: string; - hash?: (string | SubtleCryptoHashAlgorithm); - dataLength?: number; - saltLength?: number; -} -interface CryptoKeyKeyAlgorithm { - name: string; -} -interface CryptoKeyAesKeyAlgorithm { - name: string; - length: number; -} -interface CryptoKeyHmacKeyAlgorithm { - name: string; - hash: CryptoKeyKeyAlgorithm; - length: number; -} -interface CryptoKeyRsaKeyAlgorithm { - name: string; - modulusLength: number; - publicExponent: ArrayBuffer | ArrayBufferView; - hash?: CryptoKeyKeyAlgorithm; -} -interface CryptoKeyEllipticKeyAlgorithm { - name: string; - namedCurve: string; -} -interface CryptoKeyArbitraryKeyAlgorithm { - name: string; - hash?: CryptoKeyKeyAlgorithm; - namedCurve?: string; - length?: number; -} -declare class DigestStream extends WritableStream { - constructor(algorithm: string | SubtleCryptoHashAlgorithm); - readonly digest: Promise; - get bytesWritten(): number | bigint; -} -/** - * The **`TextDecoder`** interface represents a decoder for a specific text encoding, such as `UTF-8`, `ISO-8859-2`, `KOI8-R`, `GBK`, etc. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder) - */ -declare class TextDecoder { - constructor(label?: string, options?: TextDecoderConstructorOptions); - /** - * The **`TextDecoder.decode()`** method returns a string containing text decoded from the buffer passed as a parameter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode) - */ - decode(input?: (ArrayBuffer | ArrayBufferView), options?: TextDecoderDecodeOptions): string; - get encoding(): string; - get fatal(): boolean; - get ignoreBOM(): boolean; -} -/** - * The **`TextEncoder`** interface takes a stream of code points as input and emits a stream of UTF-8 bytes. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder) - */ -declare class TextEncoder { - constructor(); - /** - * The **`TextEncoder.encode()`** method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode) - */ - encode(input?: string): Uint8Array; - /** - * The **`TextEncoder.encodeInto()`** method takes a string to encode and a destination Uint8Array to put resulting UTF-8 encoded text into, and returns a dictionary object indicating the progress of the encoding. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto) - */ - encodeInto(input: string, buffer: Uint8Array): TextEncoderEncodeIntoResult; - get encoding(): string; -} -interface TextDecoderConstructorOptions { - fatal: boolean; - ignoreBOM: boolean; -} -interface TextDecoderDecodeOptions { - stream: boolean; -} -interface TextEncoderEncodeIntoResult { - read: number; - written: number; -} -/** - * The **`ErrorEvent`** interface represents events providing information related to errors in scripts or in files. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent) - */ -declare class ErrorEvent extends Event { - constructor(type: string, init?: ErrorEventErrorEventInit); - /** - * The **`filename`** read-only property of the ErrorEvent interface returns a string containing the name of the script file in which the error occurred. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename) - */ - get filename(): string; - /** - * The **`message`** read-only property of the ErrorEvent interface returns a string containing a human-readable error message describing the problem. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message) - */ - get message(): string; - /** - * The **`lineno`** read-only property of the ErrorEvent interface returns an integer containing the line number of the script file on which the error occurred. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno) - */ - get lineno(): number; - /** - * The **`colno`** read-only property of the ErrorEvent interface returns an integer containing the column number of the script file on which the error occurred. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno) - */ - get colno(): number; - /** - * The **`error`** read-only property of the ErrorEvent interface returns a JavaScript value, such as an Error or DOMException, representing the error associated with this event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error) - */ - get error(): any; -} -interface ErrorEventErrorEventInit { - message?: string; - filename?: string; - lineno?: number; - colno?: number; - error?: any; -} -/** - * The **`MessageEvent`** interface represents a message received by a target object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent) - */ -declare class MessageEvent extends Event { - constructor(type: string, initializer: MessageEventInit); - /** - * The **`data`** read-only property of the The data sent by the message emitter; this can be any data type, depending on what originated this event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data) - */ - readonly data: any; - /** - * The **`origin`** read-only property of the origin of the message emitter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin) - */ - readonly origin: string | null; - /** - * The **`lastEventId`** read-only property of the unique ID for the event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId) - */ - readonly lastEventId: string; - /** - * The **`source`** read-only property of the a WindowProxy, MessagePort, or a `MessageEventSource` (which can be a WindowProxy, message emitter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source) - */ - readonly source: MessagePort | null; - /** - * The **`ports`** read-only property of the containing all MessagePort objects sent with the message, in order. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports) - */ - readonly ports: MessagePort[]; -} -interface MessageEventInit { - data: ArrayBuffer | string; -} -/** - * The **`PromiseRejectionEvent`** interface represents events which are sent to the global script context when JavaScript Promises are rejected. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent) - */ -declare abstract class PromiseRejectionEvent extends Event { - /** - * The PromiseRejectionEvent interface's **`promise`** read-only property indicates the JavaScript rejected. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise) - */ - readonly promise: Promise; - /** - * The PromiseRejectionEvent **`reason`** read-only property is any JavaScript value or Object which provides the reason passed into Promise.reject(). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason) - */ - readonly reason: any; -} -/** - * The **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the Window/fetch, XMLHttpRequest.send() or navigator.sendBeacon() methods. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData) - */ -declare class FormData { - constructor(); - /** - * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) - */ - append(name: string, value: string | Blob): void; - /** - * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) - */ - append(name: string, value: string): void; - /** - * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) - */ - append(name: string, value: Blob, filename?: string): void; - /** - * The **`delete()`** method of the FormData interface deletes a key and its value(s) from a `FormData` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete) - */ - delete(name: string): void; - /** - * The **`get()`** method of the FormData interface returns the first value associated with a given key from within a `FormData` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get) - */ - get(name: string): (File | string) | null; - /** - * The **`getAll()`** method of the FormData interface returns all the values associated with a given key from within a `FormData` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll) - */ - getAll(name: string): (File | string)[]; - /** - * The **`has()`** method of the FormData interface returns whether a `FormData` object contains a certain key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has) - */ - has(name: string): boolean; - /** - * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) - */ - set(name: string, value: string | Blob): void; - /** - * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) - */ - set(name: string, value: string): void; - /** - * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) - */ - set(name: string, value: Blob, filename?: string): void; - /* Returns an array of key, value pairs for every entry in the list. */ - entries(): IterableIterator<[ - key: string, - value: File | string - ]>; - /* Returns a list of keys in the list. */ - keys(): IterableIterator; - /* Returns a list of values in the list. */ - values(): IterableIterator<(File | string)>; - forEach(callback: (this: This, value: File | string, key: string, parent: FormData) => void, thisArg?: This): void; - [Symbol.iterator](): IterableIterator<[ - key: string, - value: File | string - ]>; -} -interface ContentOptions { - html?: boolean; -} -declare class HTMLRewriter { - constructor(); - on(selector: string, handlers: HTMLRewriterElementContentHandlers): HTMLRewriter; - onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter; - transform(response: Response): Response; -} -interface HTMLRewriterElementContentHandlers { - element?(element: Element): void | Promise; - comments?(comment: Comment): void | Promise; - text?(element: Text): void | Promise; -} -interface HTMLRewriterDocumentContentHandlers { - doctype?(doctype: Doctype): void | Promise; - comments?(comment: Comment): void | Promise; - text?(text: Text): void | Promise; - end?(end: DocumentEnd): void | Promise; -} -interface Doctype { - readonly name: string | null; - readonly publicId: string | null; - readonly systemId: string | null; -} -interface Element { - tagName: string; - readonly attributes: IterableIterator; - readonly removed: boolean; - readonly namespaceURI: string; - getAttribute(name: string): string | null; - hasAttribute(name: string): boolean; - setAttribute(name: string, value: string): Element; - removeAttribute(name: string): Element; - before(content: string | ReadableStream | Response, options?: ContentOptions): Element; - after(content: string | ReadableStream | Response, options?: ContentOptions): Element; - prepend(content: string | ReadableStream | Response, options?: ContentOptions): Element; - append(content: string | ReadableStream | Response, options?: ContentOptions): Element; - replace(content: string | ReadableStream | Response, options?: ContentOptions): Element; - remove(): Element; - removeAndKeepContent(): Element; - setInnerContent(content: string | ReadableStream | Response, options?: ContentOptions): Element; - onEndTag(handler: (tag: EndTag) => void | Promise): void; -} -interface EndTag { - name: string; - before(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; - after(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; - remove(): EndTag; -} -interface Comment { - text: string; - readonly removed: boolean; - before(content: string, options?: ContentOptions): Comment; - after(content: string, options?: ContentOptions): Comment; - replace(content: string, options?: ContentOptions): Comment; - remove(): Comment; -} -interface Text { - readonly text: string; - readonly lastInTextNode: boolean; - readonly removed: boolean; - before(content: string | ReadableStream | Response, options?: ContentOptions): Text; - after(content: string | ReadableStream | Response, options?: ContentOptions): Text; - replace(content: string | ReadableStream | Response, options?: ContentOptions): Text; - remove(): Text; -} -interface DocumentEnd { - append(content: string, options?: ContentOptions): DocumentEnd; -} -/** - * This is the event type for `fetch` events dispatched on the ServiceWorkerGlobalScope. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent) - */ -declare abstract class FetchEvent extends ExtendableEvent { - /** - * The **`request`** read-only property of the the event handler. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request) - */ - readonly request: Request; - /** - * The **`respondWith()`** method of allows you to provide a promise for a Response yourself. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) - */ - respondWith(promise: Response | Promise): void; - passThroughOnException(): void; -} -type HeadersInit = Headers | Iterable> | Record; -/** - * The **`Headers`** interface of the Fetch API allows you to perform various actions on HTTP request and response headers. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers) - */ -declare class Headers { - constructor(init?: HeadersInit); - /** - * The **`get()`** method of the Headers interface returns a byte string of all the values of a header within a `Headers` object with a given name. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get) - */ - get(name: string): string | null; - getAll(name: string): string[]; - /** - * The **`getSetCookie()`** method of the Headers interface returns an array containing the values of all Set-Cookie headers associated with a response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie) - */ - getSetCookie(): string[]; - /** - * The **`has()`** method of the Headers interface returns a boolean stating whether a `Headers` object contains a certain header. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has) - */ - has(name: string): boolean; - /** - * The **`set()`** method of the Headers interface sets a new value for an existing header inside a `Headers` object, or adds the header if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set) - */ - set(name: string, value: string): void; - /** - * The **`append()`** method of the Headers interface appends a new value onto an existing header inside a `Headers` object, or adds the header if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append) - */ - append(name: string, value: string): void; - /** - * The **`delete()`** method of the Headers interface deletes a header from the current `Headers` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete) - */ - delete(name: string): void; - forEach(callback: (this: This, value: string, key: string, parent: Headers) => void, thisArg?: This): void; - /* Returns an iterator allowing to go through all key/value pairs contained in this object. */ - entries(): IterableIterator<[ - key: string, - value: string - ]>; - /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */ - keys(): IterableIterator; - /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */ - values(): IterableIterator; - [Symbol.iterator](): IterableIterator<[ - key: string, - value: string - ]>; -} -type BodyInit = ReadableStream | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData | Iterable | AsyncIterable; -declare abstract class Body { - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */ - get body(): ReadableStream | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ - get bodyUsed(): boolean; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ - arrayBuffer(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */ - bytes(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */ - text(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */ - json(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */ - formData(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ - blob(): Promise; -} -/** - * The **`Response`** interface of the Fetch API represents the response to a request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) - */ -declare var Response: { - prototype: Response; - new (body?: BodyInit | null, init?: ResponseInit): Response; - error(): Response; - redirect(url: string, status?: number): Response; - json(any: any, maybeInit?: (ResponseInit | Response)): Response; -}; -/** - * The **`Response`** interface of the Fetch API represents the response to a request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) - */ -interface Response extends Body { - /** - * The **`clone()`** method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone) - */ - clone(): Response; - /** - * The **`status`** read-only property of the Response interface contains the HTTP status codes of the response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status) - */ - status: number; - /** - * The **`statusText`** read-only property of the Response interface contains the status message corresponding to the HTTP status code in Response.status. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText) - */ - statusText: string; - /** - * The **`headers`** read-only property of the with the response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) - */ - headers: Headers; - /** - * The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok) - */ - ok: boolean; - /** - * The **`redirected`** read-only property of the Response interface indicates whether or not the response is the result of a request you made which was redirected. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected) - */ - redirected: boolean; - /** - * The **`url`** read-only property of the Response interface contains the URL of the response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url) - */ - url: string; - webSocket: WebSocket | null; - cf: any | undefined; - /** - * The **`type`** read-only property of the Response interface contains the type of the response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type) - */ - type: "default" | "error"; -} -interface ResponseInit { - status?: number; - statusText?: string; - headers?: HeadersInit; - cf?: any; - webSocket?: (WebSocket | null); - encodeBody?: "automatic" | "manual"; -} -type RequestInfo> = Request | string; -/** - * The **`Request`** interface of the Fetch API represents a resource request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) - */ -declare var Request: { - prototype: Request; - new >(input: RequestInfo | URL, init?: RequestInit): Request; -}; -/** - * The **`Request`** interface of the Fetch API represents a resource request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) - */ -interface Request> extends Body { - /** - * The **`clone()`** method of the Request interface creates a copy of the current `Request` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone) - */ - clone(): Request; - /** - * The **`method`** read-only property of the `POST`, etc.) A String indicating the method of the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method) - */ - method: string; - /** - * The **`url`** read-only property of the Request interface contains the URL of the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url) - */ - url: string; - /** - * The **`headers`** read-only property of the with the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers) - */ - headers: Headers; - /** - * The **`redirect`** read-only property of the Request interface contains the mode for how redirects are handled. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect) - */ - redirect: string; - fetcher: Fetcher | null; - /** - * The read-only **`signal`** property of the Request interface returns the AbortSignal associated with the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal) - */ - signal: AbortSignal; - cf?: Cf; - /** - * The **`integrity`** read-only property of the Request interface contains the subresource integrity value of the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity) - */ - integrity: string; - /** - * The **`keepalive`** read-only property of the Request interface contains the request's `keepalive` setting (`true` or `false`), which indicates whether the browser will keep the associated request alive if the page that initiated it is unloaded before the request is complete. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive) - */ - keepalive: boolean; - /** - * The **`cache`** read-only property of the Request interface contains the cache mode of the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache) - */ - cache?: "no-store" | "no-cache"; -} -interface RequestInit { - /* A string to set request's method. */ - method?: string; - /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */ - headers?: HeadersInit; - /* A BodyInit object or null to set request's body. */ - body?: BodyInit | null; - /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ - redirect?: string; - fetcher?: (Fetcher | null); - cf?: Cf; - /* A string indicating how the request will interact with the browser's cache to set request's cache. */ - cache?: "no-store" | "no-cache"; - /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */ - integrity?: string; - /* An AbortSignal to set request's signal. */ - signal?: (AbortSignal | null); - encodeResponseBody?: "automatic" | "manual"; -} -type Service Rpc.WorkerEntrypointBranded) | Rpc.WorkerEntrypointBranded | ExportedHandler | undefined = undefined> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? Fetcher> : T extends Rpc.WorkerEntrypointBranded ? Fetcher : T extends Exclude ? never : Fetcher; -type Fetcher = (T extends Rpc.EntrypointBranded ? Rpc.Provider : unknown) & { - fetch(input: RequestInfo | URL, init?: RequestInit): Promise; - connect(address: SocketAddress | string, options?: SocketOptions): Socket; -}; -interface KVNamespaceListKey { - name: Key; - expiration?: number; - metadata?: Metadata; -} -type KVNamespaceListResult = { - list_complete: false; - keys: KVNamespaceListKey[]; - cursor: string; - cacheStatus: string | null; -} | { - list_complete: true; - keys: KVNamespaceListKey[]; - cacheStatus: string | null; -}; -interface KVNamespace { - get(key: Key, options?: Partial>): Promise; - get(key: Key, type: "text"): Promise; - get(key: Key, type: "json"): Promise; - get(key: Key, type: "arrayBuffer"): Promise; - get(key: Key, type: "stream"): Promise; - get(key: Key, options?: KVNamespaceGetOptions<"text">): Promise; - get(key: Key, options?: KVNamespaceGetOptions<"json">): Promise; - get(key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">): Promise; - get(key: Key, options?: KVNamespaceGetOptions<"stream">): Promise; - get(key: Array, type: "text"): Promise>; - get(key: Array, type: "json"): Promise>; - get(key: Array, options?: Partial>): Promise>; - get(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>; - get(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>; - list(options?: KVNamespaceListOptions): Promise>; - put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise; - getWithMetadata(key: Key, options?: Partial>): Promise>; - getWithMetadata(key: Key, type: "text"): Promise>; - getWithMetadata(key: Key, type: "json"): Promise>; - getWithMetadata(key: Key, type: "arrayBuffer"): Promise>; - getWithMetadata(key: Key, type: "stream"): Promise>; - getWithMetadata(key: Key, options: KVNamespaceGetOptions<"text">): Promise>; - getWithMetadata(key: Key, options: KVNamespaceGetOptions<"json">): Promise>; - getWithMetadata(key: Key, options: KVNamespaceGetOptions<"arrayBuffer">): Promise>; - getWithMetadata(key: Key, options: KVNamespaceGetOptions<"stream">): Promise>; - getWithMetadata(key: Array, type: "text"): Promise>>; - getWithMetadata(key: Array, type: "json"): Promise>>; - getWithMetadata(key: Array, options?: Partial>): Promise>>; - getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>>; - getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>>; - delete(key: Key): Promise; -} -interface KVNamespaceListOptions { - limit?: number; - prefix?: (string | null); - cursor?: (string | null); -} -interface KVNamespaceGetOptions { - type: Type; - cacheTtl?: number; -} -interface KVNamespacePutOptions { - expiration?: number; - expirationTtl?: number; - metadata?: (any | null); -} -interface KVNamespaceGetWithMetadataResult { - value: Value | null; - metadata: Metadata | null; - cacheStatus: string | null; -} -type QueueContentType = "text" | "bytes" | "json" | "v8"; -interface Queue { - metrics(): Promise; - send(message: Body, options?: QueueSendOptions): Promise; - sendBatch(messages: Iterable>, options?: QueueSendBatchOptions): Promise; -} -interface QueueSendMetrics { - backlogCount: number; - backlogBytes: number; - oldestMessageTimestamp?: Date; -} -interface QueueSendMetadata { - metrics: QueueSendMetrics; -} -interface QueueSendResponse { - metadata: QueueSendMetadata; -} -interface QueueSendBatchMetrics { - backlogCount: number; - backlogBytes: number; - oldestMessageTimestamp?: Date; -} -interface QueueSendBatchMetadata { - metrics: QueueSendBatchMetrics; -} -interface QueueSendBatchResponse { - metadata: QueueSendBatchMetadata; -} -interface QueueSendOptions { - contentType?: QueueContentType; - delaySeconds?: number; -} -interface QueueSendBatchOptions { - delaySeconds?: number; -} -interface MessageSendRequest { - body: Body; - contentType?: QueueContentType; - delaySeconds?: number; -} -interface QueueMetrics { - backlogCount: number; - backlogBytes: number; - oldestMessageTimestamp?: Date; -} -interface MessageBatchMetrics { - backlogCount: number; - backlogBytes: number; - oldestMessageTimestamp?: Date; -} -interface MessageBatchMetadata { - metrics: MessageBatchMetrics; -} -interface QueueRetryOptions { - delaySeconds?: number; -} -interface Message { - readonly id: string; - readonly timestamp: Date; - readonly body: Body; - readonly attempts: number; - retry(options?: QueueRetryOptions): void; - ack(): void; -} -interface QueueEvent extends ExtendableEvent { - readonly messages: readonly Message[]; - readonly queue: string; - readonly metadata: MessageBatchMetadata; - retryAll(options?: QueueRetryOptions): void; - ackAll(): void; -} -interface MessageBatch { - readonly messages: readonly Message[]; - readonly queue: string; - readonly metadata: MessageBatchMetadata; - retryAll(options?: QueueRetryOptions): void; - ackAll(): void; -} -interface R2Error extends Error { - readonly name: string; - readonly code: number; - readonly message: string; - readonly action: string; - readonly stack: any; -} -interface R2ListOptions { - limit?: number; - prefix?: string; - cursor?: string; - delimiter?: string; - startAfter?: string; - include?: ("httpMetadata" | "customMetadata")[]; -} -interface R2Bucket { - head(key: string): Promise; - get(key: string, options: R2GetOptions & { - onlyIf: R2Conditional | Headers; - }): Promise; - get(key: string, options?: R2GetOptions): Promise; - put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions & { - onlyIf: R2Conditional | Headers; - }): Promise; - put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions): Promise; - createMultipartUpload(key: string, options?: R2MultipartOptions): Promise; - resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload; - delete(keys: string | string[]): Promise; - list(options?: R2ListOptions): Promise; -} -interface R2MultipartUpload { - readonly key: string; - readonly uploadId: string; - uploadPart(partNumber: number, value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob, options?: R2UploadPartOptions): Promise; - abort(): Promise; - complete(uploadedParts: R2UploadedPart[]): Promise; -} -interface R2UploadedPart { - partNumber: number; - etag: string; -} -declare abstract class R2Object { - readonly key: string; - readonly version: string; - readonly size: number; - readonly etag: string; - readonly httpEtag: string; - readonly checksums: R2Checksums; - readonly uploaded: Date; - readonly httpMetadata?: R2HTTPMetadata; - readonly customMetadata?: Record; - readonly range?: R2Range; - readonly storageClass: string; - readonly ssecKeyMd5?: string; - writeHttpMetadata(headers: Headers): void; -} -interface R2ObjectBody extends R2Object { - get body(): ReadableStream; - get bodyUsed(): boolean; - arrayBuffer(): Promise; - bytes(): Promise; - text(): Promise; - json(): Promise; - blob(): Promise; -} -type R2Range = { - offset: number; - length?: number; -} | { - offset?: number; - length: number; -} | { - suffix: number; -}; -interface R2Conditional { - etagMatches?: string; - etagDoesNotMatch?: string; - uploadedBefore?: Date; - uploadedAfter?: Date; - secondsGranularity?: boolean; -} -interface R2GetOptions { - onlyIf?: (R2Conditional | Headers); - range?: (R2Range | Headers); - ssecKey?: (ArrayBuffer | string); -} -interface R2PutOptions { - onlyIf?: (R2Conditional | Headers); - httpMetadata?: (R2HTTPMetadata | Headers); - customMetadata?: Record; - md5?: ((ArrayBuffer | ArrayBufferView) | string); - sha1?: ((ArrayBuffer | ArrayBufferView) | string); - sha256?: ((ArrayBuffer | ArrayBufferView) | string); - sha384?: ((ArrayBuffer | ArrayBufferView) | string); - sha512?: ((ArrayBuffer | ArrayBufferView) | string); - storageClass?: string; - ssecKey?: (ArrayBuffer | string); -} -interface R2MultipartOptions { - httpMetadata?: (R2HTTPMetadata | Headers); - customMetadata?: Record; - storageClass?: string; - ssecKey?: (ArrayBuffer | string); -} -interface R2Checksums { - readonly md5?: ArrayBuffer; - readonly sha1?: ArrayBuffer; - readonly sha256?: ArrayBuffer; - readonly sha384?: ArrayBuffer; - readonly sha512?: ArrayBuffer; - toJSON(): R2StringChecksums; -} -interface R2StringChecksums { - md5?: string; - sha1?: string; - sha256?: string; - sha384?: string; - sha512?: string; -} -interface R2HTTPMetadata { - contentType?: string; - contentLanguage?: string; - contentDisposition?: string; - contentEncoding?: string; - cacheControl?: string; - cacheExpiry?: Date; -} -type R2Objects = { - objects: R2Object[]; - delimitedPrefixes: string[]; -} & ({ - truncated: true; - cursor: string; -} | { - truncated: false; -}); -interface R2UploadPartOptions { - ssecKey?: (ArrayBuffer | string); -} -declare abstract class ScheduledEvent extends ExtendableEvent { - readonly scheduledTime: number; - readonly cron: string; - noRetry(): void; -} -interface ScheduledController { - readonly scheduledTime: number; - readonly cron: string; - noRetry(): void; -} -interface QueuingStrategy { - highWaterMark?: (number | bigint); - size?: (chunk: T) => number | bigint; -} -interface UnderlyingSink { - type?: string; - start?: (controller: WritableStreamDefaultController) => void | Promise; - write?: (chunk: W, controller: WritableStreamDefaultController) => void | Promise; - abort?: (reason: any) => void | Promise; - close?: () => void | Promise; -} -interface UnderlyingByteSource { - type: "bytes"; - autoAllocateChunkSize?: number; - start?: (controller: ReadableByteStreamController) => void | Promise; - pull?: (controller: ReadableByteStreamController) => void | Promise; - cancel?: (reason: any) => void | Promise; -} -interface UnderlyingSource { - type?: "" | undefined; - start?: (controller: ReadableStreamDefaultController) => void | Promise; - pull?: (controller: ReadableStreamDefaultController) => void | Promise; - cancel?: (reason: any) => void | Promise; - expectedLength?: (number | bigint); -} -interface Transformer { - readableType?: string; - writableType?: string; - start?: (controller: TransformStreamDefaultController) => void | Promise; - transform?: (chunk: I, controller: TransformStreamDefaultController) => void | Promise; - flush?: (controller: TransformStreamDefaultController) => void | Promise; - cancel?: (reason: any) => void | Promise; - expectedLength?: number; -} -interface StreamPipeOptions { - preventAbort?: boolean; - preventCancel?: boolean; - /** - * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. - * - * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. - * - * Errors and closures of the source and destination streams propagate as follows: - * - * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination. - * - * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source. - * - * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error. - * - * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source. - * - * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set. - */ - preventClose?: boolean; - signal?: AbortSignal; -} -type ReadableStreamReadResult = { - done: false; - value: R; -} | { - done: true; - value?: undefined; -}; -/** - * The `ReadableStream` interface of the Streams API represents a readable stream of byte data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) - */ -interface ReadableStream { - /** - * The **`locked`** read-only property of the ReadableStream interface returns whether or not the readable stream is locked to a reader. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked) - */ - get locked(): boolean; - /** - * The **`cancel()`** method of the ReadableStream interface returns a Promise that resolves when the stream is canceled. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel) - */ - cancel(reason?: any): Promise; - /** - * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) - */ - getReader(): ReadableStreamDefaultReader; - /** - * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) - */ - getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader; - /** - * The **`pipeThrough()`** method of the ReadableStream interface provides a chainable way of piping the current stream through a transform stream or any other writable/readable pair. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough) - */ - pipeThrough(transform: ReadableWritablePair, options?: StreamPipeOptions): ReadableStream; - /** - * The **`pipeTo()`** method of the ReadableStream interface pipes the current `ReadableStream` to a given WritableStream and returns a Promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo) - */ - pipeTo(destination: WritableStream, options?: StreamPipeOptions): Promise; - /** - * The **`tee()`** method of the two-element array containing the two resulting branches as new ReadableStream instances. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee) - */ - tee(): [ - ReadableStream, - ReadableStream - ]; - values(options?: ReadableStreamValuesOptions): AsyncIterableIterator; - [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator; -} -/** - * The `ReadableStream` interface of the Streams API represents a readable stream of byte data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) - */ -declare const ReadableStream: { - prototype: ReadableStream; - new (underlyingSource: UnderlyingByteSource, strategy?: QueuingStrategy): ReadableStream; - new (underlyingSource?: UnderlyingSource, strategy?: QueuingStrategy): ReadableStream; -}; -/** - * The **`ReadableStreamDefaultReader`** interface of the Streams API represents a default reader that can be used to read stream data supplied from a network (such as a fetch request). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader) - */ -declare class ReadableStreamDefaultReader { - constructor(stream: ReadableStream); - get closed(): Promise; - cancel(reason?: any): Promise; - /** - * The **`read()`** method of the ReadableStreamDefaultReader interface returns a Promise providing access to the next chunk in the stream's internal queue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read) - */ - read(): Promise>; - /** - * The **`releaseLock()`** method of the ReadableStreamDefaultReader interface releases the reader's lock on the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock) - */ - releaseLock(): void; -} -/** - * The `ReadableStreamBYOBReader` interface of the Streams API defines a reader for a ReadableStream that supports zero-copy reading from an underlying byte source. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader) - */ -declare class ReadableStreamBYOBReader { - constructor(stream: ReadableStream); - get closed(): Promise; - cancel(reason?: any): Promise; - /** - * The **`read()`** method of the ReadableStreamBYOBReader interface is used to read data into a view on a user-supplied buffer from an associated readable byte stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read) - */ - read(view: T): Promise>; - /** - * The **`releaseLock()`** method of the ReadableStreamBYOBReader interface releases the reader's lock on the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock) - */ - releaseLock(): void; - readAtLeast(minElements: number, view: T): Promise>; -} -interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions { - min?: number; -} -interface ReadableStreamGetReaderOptions { - /** - * Creates a ReadableStreamBYOBReader and locks the stream to the new reader. - * - * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle "bring your own buffer" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation. - */ - mode: "byob"; -} -/** - * The **`ReadableStreamBYOBRequest`** interface of the Streams API represents a 'pull request' for data from an underlying source that will made as a zero-copy transfer to a consumer (bypassing the stream's internal queues). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest) - */ -declare abstract class ReadableStreamBYOBRequest { - /** - * The **`view`** getter property of the ReadableStreamBYOBRequest interface returns the current view. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view) - */ - get view(): Uint8Array | null; - /** - * The **`respond()`** method of the ReadableStreamBYOBRequest interface is used to signal to the associated readable byte stream that the specified number of bytes were written into the ReadableStreamBYOBRequest.view. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond) - */ - respond(bytesWritten: number): void; - /** - * The **`respondWithNewView()`** method of the ReadableStreamBYOBRequest interface specifies a new view that the consumer of the associated readable byte stream should write to instead of ReadableStreamBYOBRequest.view. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView) - */ - respondWithNewView(view: ArrayBuffer | ArrayBufferView): void; - get atLeast(): number | null; -} -/** - * The **`ReadableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a ReadableStream's state and internal queue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController) - */ -declare abstract class ReadableStreamDefaultController { - /** - * The **`desiredSize`** read-only property of the required to fill the stream's internal queue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize) - */ - get desiredSize(): number | null; - /** - * The **`close()`** method of the ReadableStreamDefaultController interface closes the associated stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close) - */ - close(): void; - /** - * The **`enqueue()`** method of the ```js-nolint enqueue(chunk) ``` - `chunk` - : The chunk to enqueue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue) - */ - enqueue(chunk?: R): void; - /** - * The **`error()`** method of the with the associated stream to error. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error) - */ - error(reason: any): void; -} -/** - * The **`ReadableByteStreamController`** interface of the Streams API represents a controller for a readable byte stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController) - */ -declare abstract class ReadableByteStreamController { - /** - * The **`byobRequest`** read-only property of the ReadableByteStreamController interface returns the current BYOB request, or `null` if there are no pending requests. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest) - */ - get byobRequest(): ReadableStreamBYOBRequest | null; - /** - * The **`desiredSize`** read-only property of the ReadableByteStreamController interface returns the number of bytes required to fill the stream's internal queue to its 'desired size'. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize) - */ - get desiredSize(): number | null; - /** - * The **`close()`** method of the ReadableByteStreamController interface closes the associated stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close) - */ - close(): void; - /** - * The **`enqueue()`** method of the ReadableByteStreamController interface enqueues a given chunk on the associated readable byte stream (the chunk is copied into the stream's internal queues). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue) - */ - enqueue(chunk: ArrayBuffer | ArrayBufferView): void; - /** - * The **`error()`** method of the ReadableByteStreamController interface causes any future interactions with the associated stream to error with the specified reason. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error) - */ - error(reason: any): void; -} -/** - * The **`WritableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a WritableStream's state. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController) - */ -declare abstract class WritableStreamDefaultController { - /** - * The read-only **`signal`** property of the WritableStreamDefaultController interface returns the AbortSignal associated with the controller. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal) - */ - get signal(): AbortSignal; - /** - * The **`error()`** method of the with the associated stream to error. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error) - */ - error(reason?: any): void; -} -/** - * The **`TransformStreamDefaultController`** interface of the Streams API provides methods to manipulate the associated ReadableStream and WritableStream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController) - */ -declare abstract class TransformStreamDefaultController { - /** - * The **`desiredSize`** read-only property of the TransformStreamDefaultController interface returns the desired size to fill the queue of the associated ReadableStream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize) - */ - get desiredSize(): number | null; - /** - * The **`enqueue()`** method of the TransformStreamDefaultController interface enqueues the given chunk in the readable side of the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue) - */ - enqueue(chunk?: O): void; - /** - * The **`error()`** method of the TransformStreamDefaultController interface errors both sides of the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error) - */ - error(reason: any): void; - /** - * The **`terminate()`** method of the TransformStreamDefaultController interface closes the readable side and errors the writable side of the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate) - */ - terminate(): void; -} -interface ReadableWritablePair { - readable: ReadableStream; - /** - * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use. - * - * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. - */ - writable: WritableStream; -} -/** - * The **`WritableStream`** interface of the Streams API provides a standard abstraction for writing streaming data to a destination, known as a sink. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream) - */ -declare class WritableStream { - constructor(underlyingSink?: UnderlyingSink, queuingStrategy?: QueuingStrategy); - /** - * The **`locked`** read-only property of the WritableStream interface returns a boolean indicating whether the `WritableStream` is locked to a writer. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked) - */ - get locked(): boolean; - /** - * The **`abort()`** method of the WritableStream interface aborts the stream, signaling that the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort) - */ - abort(reason?: any): Promise; - /** - * The **`close()`** method of the WritableStream interface closes the associated stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close) - */ - close(): Promise; - /** - * The **`getWriter()`** method of the WritableStream interface returns a new instance of WritableStreamDefaultWriter and locks the stream to that instance. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter) - */ - getWriter(): WritableStreamDefaultWriter; -} -/** - * The **`WritableStreamDefaultWriter`** interface of the Streams API is the object returned by WritableStream.getWriter() and once created locks the writer to the `WritableStream` ensuring that no other streams can write to the underlying sink. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter) - */ -declare class WritableStreamDefaultWriter { - constructor(stream: WritableStream); - /** - * The **`closed`** read-only property of the the stream errors or the writer's lock is released. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed) - */ - get closed(): Promise; - /** - * The **`ready`** read-only property of the that resolves when the desired size of the stream's internal queue transitions from non-positive to positive, signaling that it is no longer applying backpressure. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready) - */ - get ready(): Promise; - /** - * The **`desiredSize`** read-only property of the to fill the stream's internal queue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize) - */ - get desiredSize(): number | null; - /** - * The **`abort()`** method of the the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort) - */ - abort(reason?: any): Promise; - /** - * The **`close()`** method of the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close) - */ - close(): Promise; - /** - * The **`write()`** method of the operation. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write) - */ - write(chunk?: W): Promise; - /** - * The **`releaseLock()`** method of the corresponding stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock) - */ - releaseLock(): void; -} -/** - * The **`TransformStream`** interface of the Streams API represents a concrete implementation of the pipe chain _transform stream_ concept. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream) - */ -declare class TransformStream { - constructor(transformer?: Transformer, writableStrategy?: QueuingStrategy, readableStrategy?: QueuingStrategy); - /** - * The **`readable`** read-only property of the TransformStream interface returns the ReadableStream instance controlled by this `TransformStream`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable) - */ - get readable(): ReadableStream; - /** - * The **`writable`** read-only property of the TransformStream interface returns the WritableStream instance controlled by this `TransformStream`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable) - */ - get writable(): WritableStream; -} -declare class FixedLengthStream extends IdentityTransformStream { - constructor(expectedLength: number | bigint, queuingStrategy?: IdentityTransformStreamQueuingStrategy); -} -declare class IdentityTransformStream extends TransformStream { - constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy); -} -interface IdentityTransformStreamQueuingStrategy { - highWaterMark?: (number | bigint); -} -interface ReadableStreamValuesOptions { - preventCancel?: boolean; -} -/** - * The **`CompressionStream`** interface of the Compression Streams API is an API for compressing a stream of data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream) - */ -declare class CompressionStream extends TransformStream { - constructor(format: "gzip" | "deflate" | "deflate-raw"); -} -/** - * The **`DecompressionStream`** interface of the Compression Streams API is an API for decompressing a stream of data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream) - */ -declare class DecompressionStream extends TransformStream { - constructor(format: "gzip" | "deflate" | "deflate-raw"); -} -/** - * The **`TextEncoderStream`** interface of the Encoding API converts a stream of strings into bytes in the UTF-8 encoding. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream) - */ -declare class TextEncoderStream extends TransformStream { - constructor(); - get encoding(): string; -} -/** - * The **`TextDecoderStream`** interface of the Encoding API converts a stream of text in a binary encoding, such as UTF-8 etc., to a stream of strings. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream) - */ -declare class TextDecoderStream extends TransformStream { - constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit); - get encoding(): string; - get fatal(): boolean; - get ignoreBOM(): boolean; -} -interface TextDecoderStreamTextDecoderStreamInit { - fatal?: boolean; - ignoreBOM?: boolean; -} -/** - * The **`ByteLengthQueuingStrategy`** interface of the Streams API provides a built-in byte length queuing strategy that can be used when constructing streams. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy) - */ -declare class ByteLengthQueuingStrategy implements QueuingStrategy { - constructor(init: QueuingStrategyInit); - /** - * The read-only **`ByteLengthQueuingStrategy.highWaterMark`** property returns the total number of bytes that can be contained in the internal queue before backpressure is applied. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark) - */ - get highWaterMark(): number; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */ - get size(): (chunk?: any) => number; -} -/** - * The **`CountQueuingStrategy`** interface of the Streams API provides a built-in chunk counting queuing strategy that can be used when constructing streams. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy) - */ -declare class CountQueuingStrategy implements QueuingStrategy { - constructor(init: QueuingStrategyInit); - /** - * The read-only **`CountQueuingStrategy.highWaterMark`** property returns the total number of chunks that can be contained in the internal queue before backpressure is applied. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark) - */ - get highWaterMark(): number; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */ - get size(): (chunk?: any) => number; -} -interface QueuingStrategyInit { - /** - * Creates a new ByteLengthQueuingStrategy with the provided high water mark. - * - * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw. - */ - highWaterMark: number; -} -interface TracePreviewInfo { - id: string; - slug: string; - name: string; -} -interface ScriptVersion { - id?: string; - tag?: string; - message?: string; -} -declare abstract class TailEvent extends ExtendableEvent { - readonly events: TraceItem[]; - readonly traces: TraceItem[]; -} -interface TraceItem { - readonly event: (TraceItemFetchEventInfo | TraceItemJsRpcEventInfo | TraceItemConnectEventInfo | TraceItemScheduledEventInfo | TraceItemAlarmEventInfo | TraceItemQueueEventInfo | TraceItemEmailEventInfo | TraceItemTailEventInfo | TraceItemCustomEventInfo | TraceItemHibernatableWebSocketEventInfo) | null; - readonly eventTimestamp: number | null; - readonly logs: TraceLog[]; - readonly exceptions: TraceException[]; - readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[]; - readonly scriptName: string | null; - readonly entrypoint?: string; - readonly scriptVersion?: ScriptVersion; - readonly dispatchNamespace?: string; - readonly scriptTags?: string[]; - readonly tailAttributes?: Record; - readonly preview?: TracePreviewInfo; - readonly durableObjectId?: string; - readonly outcome: string; - readonly executionModel: string; - readonly truncated: boolean; - readonly cpuTime: number; - readonly wallTime: number; -} -interface TraceItemAlarmEventInfo { - readonly scheduledTime: Date; -} -interface TraceItemConnectEventInfo { -} -interface TraceItemCustomEventInfo { -} -interface TraceItemScheduledEventInfo { - readonly scheduledTime: number; - readonly cron: string; -} -interface TraceItemQueueEventInfo { - readonly queue: string; - readonly batchSize: number; -} -interface TraceItemEmailEventInfo { - readonly mailFrom: string; - readonly rcptTo: string; - readonly rawSize: number; -} -interface TraceItemTailEventInfo { - readonly consumedEvents: TraceItemTailEventInfoTailItem[]; -} -interface TraceItemTailEventInfoTailItem { - readonly scriptName: string | null; -} -interface TraceItemFetchEventInfo { - readonly response?: TraceItemFetchEventInfoResponse; - readonly request: TraceItemFetchEventInfoRequest; -} -interface TraceItemFetchEventInfoRequest { - readonly cf?: any; - readonly headers: Record; - readonly method: string; - readonly url: string; - getUnredacted(): TraceItemFetchEventInfoRequest; -} -interface TraceItemFetchEventInfoResponse { - readonly status: number; -} -interface TraceItemJsRpcEventInfo { - readonly rpcMethod: string; -} -interface TraceItemHibernatableWebSocketEventInfo { - readonly getWebSocketEvent: TraceItemHibernatableWebSocketEventInfoMessage | TraceItemHibernatableWebSocketEventInfoClose | TraceItemHibernatableWebSocketEventInfoError; -} -interface TraceItemHibernatableWebSocketEventInfoMessage { - readonly webSocketEventType: string; -} -interface TraceItemHibernatableWebSocketEventInfoClose { - readonly webSocketEventType: string; - readonly code: number; - readonly wasClean: boolean; -} -interface TraceItemHibernatableWebSocketEventInfoError { - readonly webSocketEventType: string; -} -interface TraceLog { - readonly timestamp: number; - readonly level: string; - readonly message: any; - readonly errorInfo?: (TraceLogErrorInfo | null)[]; -} -interface TraceLogErrorInfo { - name: string; - message: string; - stack?: string; -} -interface TraceException { - readonly timestamp: number; - readonly message: string; - readonly name: string; - readonly stack?: string; -} -interface TraceDiagnosticChannelEvent { - readonly timestamp: number; - readonly channel: string; - readonly message: any; -} -interface TraceMetrics { - readonly cpuTime: number; - readonly wallTime: number; -} -interface UnsafeTraceMetrics { - fromTrace(item: TraceItem): TraceMetrics; -} -/** - * The **`URL`** interface is used to parse, construct, normalize, and encode URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL) - */ -declare class URL { - constructor(url: string | URL, base?: string | URL); - /** - * The **`origin`** read-only property of the URL interface returns a string containing the Unicode serialization of the origin of the represented URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin) - */ - get origin(): string; - /** - * The **`href`** property of the URL interface is a string containing the whole URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) - */ - get href(): string; - /** - * The **`href`** property of the URL interface is a string containing the whole URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) - */ - set href(value: string); - /** - * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) - */ - get protocol(): string; - /** - * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) - */ - set protocol(value: string); - /** - * The **`username`** property of the URL interface is a string containing the username component of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) - */ - get username(): string; - /** - * The **`username`** property of the URL interface is a string containing the username component of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) - */ - set username(value: string); - /** - * The **`password`** property of the URL interface is a string containing the password component of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) - */ - get password(): string; - /** - * The **`password`** property of the URL interface is a string containing the password component of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) - */ - set password(value: string); - /** - * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) - */ - get host(): string; - /** - * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) - */ - set host(value: string); - /** - * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) - */ - get hostname(): string; - /** - * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) - */ - set hostname(value: string); - /** - * The **`port`** property of the URL interface is a string containing the port number of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) - */ - get port(): string; - /** - * The **`port`** property of the URL interface is a string containing the port number of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) - */ - set port(value: string); - /** - * The **`pathname`** property of the URL interface represents a location in a hierarchical structure. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) - */ - get pathname(): string; - /** - * The **`pathname`** property of the URL interface represents a location in a hierarchical structure. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) - */ - set pathname(value: string); - /** - * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) - */ - get search(): string; - /** - * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) - */ - set search(value: string); - /** - * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) - */ - get hash(): string; - /** - * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) - */ - set hash(value: string); - /** - * The **`searchParams`** read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams) - */ - get searchParams(): URLSearchParams; - /** - * The **`toJSON()`** method of the URL interface returns a string containing a serialized version of the URL, although in practice it seems to have the same effect as ```js-nolint toJSON() ``` None. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON) - */ - toJSON(): string; - /*function toString() { [native code] }*/ - toString(): string; - /** - * The **`URL.canParse()`** static method of the URL interface returns a boolean indicating whether or not an absolute URL, or a relative URL combined with a base URL, are parsable and valid. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static) - */ - static canParse(url: string, base?: string): boolean; - /** - * The **`URL.parse()`** static method of the URL interface returns a newly created URL object representing the URL defined by the parameters. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static) - */ - static parse(url: string, base?: string): URL | null; - /** - * The **`createObjectURL()`** static method of the URL interface creates a string containing a URL representing the object given in the parameter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static) - */ - static createObjectURL(object: File | Blob): string; - /** - * The **`revokeObjectURL()`** static method of the URL interface releases an existing object URL which was previously created by calling Call this method when you've finished using an object URL to let the browser know not to keep the reference to the file any longer. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static) - */ - static revokeObjectURL(object_url: string): void; -} -/** - * The **`URLSearchParams`** interface defines utility methods to work with the query string of a URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams) - */ -declare class URLSearchParams { - constructor(init?: (Iterable> | Record | string)); - /** - * The **`size`** read-only property of the URLSearchParams interface indicates the total number of search parameter entries. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size) - */ - get size(): number; - /** - * The **`append()`** method of the URLSearchParams interface appends a specified key/value pair as a new search parameter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append) - */ - append(name: string, value: string): void; - /** - * The **`delete()`** method of the URLSearchParams interface deletes specified parameters and their associated value(s) from the list of all search parameters. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete) - */ - delete(name: string, value?: string): void; - /** - * The **`get()`** method of the URLSearchParams interface returns the first value associated to the given search parameter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get) - */ - get(name: string): string | null; - /** - * The **`getAll()`** method of the URLSearchParams interface returns all the values associated with a given search parameter as an array. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll) - */ - getAll(name: string): string[]; - /** - * The **`has()`** method of the URLSearchParams interface returns a boolean value that indicates whether the specified parameter is in the search parameters. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has) - */ - has(name: string, value?: string): boolean; - /** - * The **`set()`** method of the URLSearchParams interface sets the value associated with a given search parameter to the given value. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set) - */ - set(name: string, value: string): void; - /** - * The **`URLSearchParams.sort()`** method sorts all key/value pairs contained in this object in place and returns `undefined`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort) - */ - sort(): void; - /* Returns an array of key, value pairs for every entry in the search params. */ - entries(): IterableIterator<[ - key: string, - value: string - ]>; - /* Returns a list of keys in the search params. */ - keys(): IterableIterator; - /* Returns a list of values in the search params. */ - values(): IterableIterator; - forEach(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void; - /*function toString() { [native code] }*/ - toString(): string; - [Symbol.iterator](): IterableIterator<[ - key: string, - value: string - ]>; -} -declare class URLPattern { - constructor(input?: (string | URLPatternInit), baseURL?: (string | URLPatternOptions), patternOptions?: URLPatternOptions); - get protocol(): string; - get username(): string; - get password(): string; - get hostname(): string; - get port(): string; - get pathname(): string; - get search(): string; - get hash(): string; - get hasRegExpGroups(): boolean; - test(input?: (string | URLPatternInit), baseURL?: string): boolean; - exec(input?: (string | URLPatternInit), baseURL?: string): URLPatternResult | null; -} -interface URLPatternInit { - protocol?: string; - username?: string; - password?: string; - hostname?: string; - port?: string; - pathname?: string; - search?: string; - hash?: string; - baseURL?: string; -} -interface URLPatternComponentResult { - input: string; - groups: Record; -} -interface URLPatternResult { - inputs: (string | URLPatternInit)[]; - protocol: URLPatternComponentResult; - username: URLPatternComponentResult; - password: URLPatternComponentResult; - hostname: URLPatternComponentResult; - port: URLPatternComponentResult; - pathname: URLPatternComponentResult; - search: URLPatternComponentResult; - hash: URLPatternComponentResult; -} -interface URLPatternOptions { - ignoreCase?: boolean; -} -/** - * A `CloseEvent` is sent to clients using WebSockets when the connection is closed. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent) - */ -declare class CloseEvent extends Event { - constructor(type: string, initializer?: CloseEventInit); - /** - * The **`code`** read-only property of the CloseEvent interface returns a WebSocket connection close code indicating the reason the connection was closed. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code) - */ - readonly code: number; - /** - * The **`reason`** read-only property of the CloseEvent interface returns the WebSocket connection close reason the server gave for closing the connection; that is, a concise human-readable prose explanation for the closure. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason) - */ - readonly reason: string; - /** - * The **`wasClean`** read-only property of the CloseEvent interface returns `true` if the connection closed cleanly. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean) - */ - readonly wasClean: boolean; -} -interface CloseEventInit { - code?: number; - reason?: string; - wasClean?: boolean; -} -type WebSocketEventMap = { - close: CloseEvent; - message: MessageEvent; - open: Event; - error: ErrorEvent; -}; -/** - * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) - */ -declare var WebSocket: { - prototype: WebSocket; - new (url: string, protocols?: (string[] | string)): WebSocket; - readonly READY_STATE_CONNECTING: number; - readonly CONNECTING: number; - readonly READY_STATE_OPEN: number; - readonly OPEN: number; - readonly READY_STATE_CLOSING: number; - readonly CLOSING: number; - readonly READY_STATE_CLOSED: number; - readonly CLOSED: number; -}; -/** - * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) - */ -interface WebSocket extends EventTarget { - accept(options?: WebSocketAcceptOptions): void; - /** - * The **`WebSocket.send()`** method enqueues the specified data to be transmitted to the server over the WebSocket connection, increasing the value of `bufferedAmount` by the number of bytes needed to contain the data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send) - */ - send(message: (ArrayBuffer | ArrayBufferView) | string): void; - /** - * The **`WebSocket.close()`** method closes the already `CLOSED`, this method does nothing. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close) - */ - close(code?: number, reason?: string): void; - serializeAttachment(attachment: any): void; - deserializeAttachment(): any | null; - /** - * The **`WebSocket.readyState`** read-only property returns the current state of the WebSocket connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState) - */ - readyState: number; - /** - * The **`WebSocket.url`** read-only property returns the absolute URL of the WebSocket as resolved by the constructor. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url) - */ - url: string | null; - /** - * The **`WebSocket.protocol`** read-only property returns the name of the sub-protocol the server selected; this will be one of the strings specified in the `protocols` parameter when creating the WebSocket object, or the empty string if no connection is established. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol) - */ - protocol: string | null; - /** - * The **`WebSocket.extensions`** read-only property returns the extensions selected by the server. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions) - */ - extensions: string | null; - /** - * The **`WebSocket.binaryType`** property controls the type of binary data being received over the WebSocket connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType) - */ - binaryType: "blob" | "arraybuffer"; -} -interface WebSocketAcceptOptions { - /** - * When set to `true`, receiving a server-initiated WebSocket Close frame will not - * automatically send a reciprocal Close frame, leaving the connection in a half-open - * state. This is useful for proxying scenarios where you need to coordinate closing - * both sides independently. Defaults to `false` when the - * `no_web_socket_half_open_by_default` compatibility flag is enabled. - */ - allowHalfOpen?: boolean; -} -declare const WebSocketPair: { - new (): { - 0: WebSocket; - 1: WebSocket; - }; -}; -interface SqlStorage { - exec>(query: string, ...bindings: any[]): SqlStorageCursor; - get databaseSize(): number; - Cursor: typeof SqlStorageCursor; - Statement: typeof SqlStorageStatement; -} -declare abstract class SqlStorageStatement { -} -type SqlStorageValue = ArrayBuffer | string | number | null; -declare abstract class SqlStorageCursor> { - next(): { - done?: false; - value: T; - } | { - done: true; - value?: never; - }; - toArray(): T[]; - one(): T; - raw(): IterableIterator; - columnNames: string[]; - get rowsRead(): number; - get rowsWritten(): number; - [Symbol.iterator](): IterableIterator; -} -interface Socket { - get readable(): ReadableStream; - get writable(): WritableStream; - get closed(): Promise; - get opened(): Promise; - get upgraded(): boolean; - get secureTransport(): "on" | "off" | "starttls"; - close(): Promise; - startTls(options?: TlsOptions): Socket; -} -interface SocketOptions { - secureTransport?: string; - allowHalfOpen: boolean; - highWaterMark?: (number | bigint); -} -interface SocketAddress { - hostname: string; - port: number; -} -interface TlsOptions { - expectedServerHostname?: string; -} -interface SocketInfo { - remoteAddress?: string; - localAddress?: string; -} -/** - * The **`EventSource`** interface is web content's interface to server-sent events. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource) - */ -declare class EventSource extends EventTarget { - constructor(url: string, init?: EventSourceEventSourceInit); - /** - * The **`close()`** method of the EventSource interface closes the connection, if one is made, and sets the ```js-nolint close() ``` None. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close) - */ - close(): void; - /** - * The **`url`** read-only property of the URL of the source. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url) - */ - get url(): string; - /** - * The **`withCredentials`** read-only property of the the `EventSource` object was instantiated with CORS credentials set. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials) - */ - get withCredentials(): boolean; - /** - * The **`readyState`** read-only property of the connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState) - */ - get readyState(): number; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ - get onopen(): any | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ - set onopen(value: any | null); - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ - get onmessage(): any | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ - set onmessage(value: any | null); - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ - get onerror(): any | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ - set onerror(value: any | null); - static readonly CONNECTING: number; - static readonly OPEN: number; - static readonly CLOSED: number; - static from(stream: ReadableStream): EventSource; -} -interface EventSourceEventSourceInit { - withCredentials?: boolean; - fetcher?: Fetcher; -} -interface ExecOutput { - readonly stdout: ArrayBuffer; - readonly stderr: ArrayBuffer; - readonly exitCode: number; -} -interface ContainerExecOptions { - cwd?: string; - env?: Record; - user?: string; - signal?: AbortSignal; - pty?: boolean | ContainerExecPtyOptions; - stdin?: ReadableStream | "pipe"; - stdout?: "pipe" | "ignore"; - stderr?: "pipe" | "ignore" | "combined"; -} -interface ContainerExecPtyOptions { - cols?: number; - rows?: number; -} -interface ExecProcess { - readonly stdin: WritableStream | null; - readonly stdout: ReadableStream | null; - readonly stderr: ReadableStream | null; - readonly pid: number; - readonly isPty: boolean; - readonly exitCode: Promise; - output(): Promise; - kill(signal?: number): void; - resize(cols: number, rows: number): void; -} -interface Container { - get running(): boolean; - start(options?: ContainerStartupOptions): void; - monitor(): Promise; - destroy(error?: any): Promise; - signal(signo: number): void; - getTcpPort(port: number): Fetcher; - setInactivityTimeout(durationMs: number | bigint): Promise; - interceptOutboundHttp(addr: string, binding: Fetcher): Promise; - interceptAllOutboundHttp(binding: Fetcher): Promise; - snapshotDirectory(options: ContainerDirectorySnapshotOptions): Promise; - snapshotContainer(options: ContainerSnapshotOptions): Promise; - interceptOutboundHttps(addr: string, binding: Fetcher): Promise; - exec(cmd: string[], options?: ContainerExecOptions): Promise; -} -interface ContainerDirectorySnapshot { - id: string; - size: number; - dir: string; - name?: string; -} -interface ContainerDirectorySnapshotOptions { - dir: string; - name?: string; -} -interface ContainerDirectorySnapshotRestoreParams { - snapshot: ContainerDirectorySnapshot; - mountPoint?: string; -} -interface ContainerSnapshot { - id: string; - size: number; - name?: string; -} -interface ContainerSnapshotOptions { - name?: string; -} -interface ContainerStartupOptions { - entrypoint?: string[]; - enableInternet: boolean; - env?: Record; - labels?: Record; - directorySnapshots?: ContainerDirectorySnapshotRestoreParams[]; - containerSnapshot?: ContainerSnapshot; -} -interface ContainerStartResources { - vcpu: number; - memoryMib: number; - diskMb: number; -} -/** - * The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort) - */ -declare abstract class MessagePort extends EventTarget { - /** - * The **`postMessage()`** method of the transfers ownership of objects to other browsing contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage) - */ - postMessage(data?: any, options?: (any[] | MessagePortPostMessageOptions)): void; - /** - * The **`close()`** method of the MessagePort interface disconnects the port, so it is no longer active. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close) - */ - close(): void; - /** - * The **`start()`** method of the MessagePort interface starts the sending of messages queued on the port. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start) - */ - start(): void; - get onmessage(): any | null; - set onmessage(value: any | null); -} -/** - * The **`MessageChannel`** interface of the Channel Messaging API allows us to create a new message channel and send data through it via its two MessagePort properties. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel) - */ -declare class MessageChannel { - constructor(); - /** - * The **`port1`** read-only property of the the port attached to the context that originated the channel. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port1) - */ - readonly port1: MessagePort; - /** - * The **`port2`** read-only property of the the port attached to the context at the other end of the channel, which the message is initially sent to. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port2) - */ - readonly port2: MessagePort; -} -interface MessagePortPostMessageOptions { - transfer?: any[]; -} -type LoopbackForExport Rpc.EntrypointBranded) | ExportedHandler | undefined = undefined> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? LoopbackServiceStub> : T extends new (...args: any[]) => Rpc.DurableObjectBranded ? LoopbackDurableObjectClass> : T extends ExportedHandler ? LoopbackServiceStub : undefined; -type LoopbackServiceStub = Fetcher & (T extends CloudflareWorkersModule.WorkerEntrypoint ? (opts: { - props?: Props; -}) => Fetcher : (opts: { - props?: any; -}) => Fetcher); -type LoopbackDurableObjectClass = DurableObjectClass & (T extends CloudflareWorkersModule.DurableObject ? (opts: { - props?: Props; -}) => DurableObjectClass : (opts: { - props?: any; -}) => DurableObjectClass); -interface LoopbackDurableObjectNamespace extends DurableObjectNamespace { -} -interface LoopbackColoLocalActorNamespace extends ColoLocalActorNamespace { -} -interface SyncKvStorage { - get(key: string): T | undefined; - list(options?: SyncKvListOptions): Iterable<[ - string, - T - ]>; - put(key: string, value: T): void; - delete(key: string): boolean; -} -interface SyncKvListOptions { - start?: string; - startAfter?: string; - end?: string; - prefix?: string; - reverse?: boolean; - limit?: number; -} -interface WorkerStub { - getEntrypoint(name?: string, options?: WorkerStubEntrypointOptions): Fetcher; - getDurableObjectClass(name?: string, options?: WorkerStubEntrypointOptions): DurableObjectClass; -} -interface WorkerStubEntrypointOptions { - props?: any; - limits?: workerdResourceLimits; -} -interface WorkerLoader { - get(name: string | null, getCode: () => WorkerLoaderWorkerCode | Promise): WorkerStub; - load(code: WorkerLoaderWorkerCode): WorkerStub; -} -interface WorkerLoaderModule { - js?: string; - cjs?: string; - text?: string; - data?: ArrayBuffer; - json?: any; - py?: string; - wasm?: ArrayBuffer; -} -interface WorkerLoaderWorkerCode { - compatibilityDate: string; - compatibilityFlags?: string[]; - allowExperimental?: boolean; - limits?: workerdResourceLimits; - mainModule: string; - modules: Record; - env?: any; - globalOutbound?: (Fetcher | null); - tails?: Fetcher[]; - streamingTails?: Fetcher[]; -} -interface workerdResourceLimits { - cpuMs?: number; - subRequests?: number; -} -/** -* The Workers runtime supports a subset of the Performance API, used to measure timing and performance, -* as well as timing of subrequests and other operations. -* -* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) -*/ -declare abstract class Performance { - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */ - get timeOrigin(): number; - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */ - now(): number; - /** - * The **`toJSON()`** method of the Performance interface is a Serialization; it returns a JSON representation of the Performance object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/toJSON) - */ - toJSON(): object; -} -interface Tracing { - enterSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; - startActiveSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; - startSpan(name: string): Span; - Span: typeof Span; -} -declare abstract class Span { - get isTraced(): boolean; - setAttribute(key: string, value: boolean | number | string): this; - setAttributes(attributes: Record): this; - end(): void; -} -/** - * Represents the identity of a user authenticated via Cloudflare Access. - * This matches the result of calling /cdn-cgi/access/get-identity. - * - * The exact structure of the returned object depends on the identity provider - * configuration for the Access application. The fields below represent commonly - * available properties, but additional provider-specific fields may be present. - */ -interface CloudflareAccessIdentity extends Record { - /** The user's email address, if available from the identity provider. */ - email?: string; - /** The user's display name. */ - name?: string; - /** The user's unique identifier. */ - user_uuid?: string; - /** The Cloudflare account ID. */ - account_id?: string; - /** Login timestamp (Unix epoch seconds). */ - iat?: number; - /** The user's IP address at authentication time. */ - ip?: string; - /** Authentication methods used (e.g., "pwd"). */ - amr?: string[]; - /** Identity provider information. */ - idp?: { - id: string; - type: string; - }; - /** Geographic information about where the user authenticated. */ - geo?: { - country: string; - }; - /** Group memberships from the identity provider. */ - groups?: Array<{ - id: string; - name: string; - email?: string; - }>; - /** Device posture check results, keyed by check ID. */ - devicePosture?: Record; - /** True if the user connected via Cloudflare WARP. */ - is_warp?: boolean; - /** True if the user is authenticated via Cloudflare Gateway. */ - is_gateway?: boolean; -} -// ============================================================================ -// Agent Memory -// -// Public type surface for user Workers binding to an Agent Memory namespace. -// ============================================================================ -/** Memory type — every memory is classified into exactly one. */ -type AgentMemoryMemoryType = "fact" | "event" | "instruction" | "task"; -/** Search intensity for recall. */ -type AgentMemoryThinkingLevel = "low" | "medium" | "high"; -/** Response verbosity for recall. */ -type AgentMemoryResponseLength = "short" | "medium" | "long"; -/** A conversation message passed to ingest(). */ -interface AgentMemoryMessage { - role: "system" | "user" | "assistant"; - content: string; - /** Optional message timestamp. */ - timestamp?: Date; -} -/** Raw memory content passed to remember(). */ -interface AgentMemoryIncomingMemory { - /** Raw memory content. The service classifies and summarizes automatically. */ - content: string; - /** Optional session identifier to associate with this memory. */ - sessionId?: string | null | undefined; -} -/** A stored memory returned from remember(), get(), and delete(). */ -interface AgentMemoryMemory { - /** Memory ID. */ - id: string; - /** Memory type. */ - type: AgentMemoryMemoryType; - /** Text summary. */ - summary: string; - /** Memory text. */ - content: string; - /** Session that created this memory. */ - sessionId: string | null; - /** Memory creation time. */ - createdAt: Date; - /** Memory last-update time. */ - updatedAt: Date; -} -/** Single entry in a list() response. Same shape as Memory minus full content. */ -type AgentMemoryMemoryListEntry = Omit; -/** A scored memory candidate in a recall result. */ -interface AgentMemoryScoredCandidate { - /** Candidate ID. */ - id: string; - /** Text summary. */ - summary: string; - /** Session that created this candidate, when known. */ - sessionId: string | null; - /** Relevance score (higher is better). Comparable only within a single query. */ - score: number; -} -/** Options for the ingest() method. */ -interface AgentMemoryIngestOptions { - /** Session identifier to associate with memories created during ingestion. */ - sessionId?: string | null | undefined; -} -/** Options for the getSummary() method. */ -interface AgentMemoryGetSummaryOptions { - /** Session identifier to retrieve session summary for. */ - sessionId?: string | null | undefined; -} -/** Response from the getSummary() method. */ -interface AgentMemoryGetSummaryResponse { - /** Markdown summary. */ - summary: string; -} -/** - * Options for the recall() method. - * - * `referenceDate` accepts a Date object, an ISO-8601 date string - * (YYYY-MM-DD), or a full ISO-8601 datetime string. When provided, this - * date is used as "today" for resolving relative time references - * ("how many days ago", "last week") instead of the server's wall-clock time. - */ -interface AgentMemoryRecallOptions { - /** Recall intensity: "low" (default), "medium", or "high". */ - thinkingLevel?: AgentMemoryThinkingLevel; - /** Response verbosity: "short", "medium" (default), or "long". */ - responseLength?: AgentMemoryResponseLength; - /** Temporal anchor for date arithmetic. */ - referenceDate?: Date | string; -} -/** Response from the recall() method. */ -interface AgentMemoryRecallResult { - /** Number of memories retrieved. */ - count: number; - /** LLM-generated answer synthesizing the matching memories. */ - answer: string; - /** Matching memories ranked by relevance. */ - candidates: AgentMemoryScoredCandidate[]; -} -/** - * Options for the list() method. - * - * `cursor` is the opaque continuation token returned by the previous page; - * pass it back unchanged to fetch the next page. `sessionId` and `type` - * are exact-match filters; combining them is allowed. - */ -interface AgentMemoryListMemoriesOptions { - /** Maximum number of memories to return. Default 20, max 500. */ - limit?: number; - /** Opaque cursor from a previous page. */ - cursor?: string; - /** Exact-match session filter. */ - sessionId?: string; - /** Exact-match memory-type filter. */ - type?: AgentMemoryMemoryType; -} -/** Response from the list() method. */ -interface AgentMemoryListMemoriesResult { - memories: AgentMemoryMemoryListEntry[]; - /** Continuation cursor; absent when this page exhausted the result set. */ - cursor?: string; -} -/** - * A single Agent Memory profile, scoped to a profile name. - * - * Returned by {@link AgentMemoryNamespace.getProfile}. - */ -declare abstract class AgentMemoryProfile { - /** - * Retrieve a memory by ID. - * - * @param memoryId - ULID of the memory to retrieve. - * @throws if the memory does not exist. - */ - get(memoryId: string): Promise; - /** - * Delete a memory by ID. - * - * Removes the memory and any source messages linked by the memory's - * source message IDs. - * - * @param memoryId - ULID of the memory to delete. - * @throws if the memory does not exist. - */ - delete(memoryId: string): Promise; - /** - * Store a memory in this profile. The content is automatically classified, - * summarized, and indexed. - * - * @param memory - Raw memory content to persist. - */ - remember(memory: AgentMemoryIncomingMemory): Promise; - /** - * Extract memories from a conversation. - * - * @param messages - Conversation messages to extract memories from. - * @param options - Optional ingest options. - */ - ingest(messages: Iterable, options?: AgentMemoryIngestOptions): Promise; - /** - * Get a profile summary. - * - * @param options - Optional getSummary options. - */ - getSummary(options?: AgentMemoryGetSummaryOptions): Promise; - /** - * Recall memories in this profile. - * - * @param query - Recall query matched against memory content and keywords. - * @param options - Optional recall parameters. - * @returns Matching memories with relevance scores and a synthesized answer. - */ - recall(query: string, options?: AgentMemoryRecallOptions): Promise; - /** - * List active memories in this profile. - * - * Returns a paginated, filterable view of stored memories. Superseded - * versions are excluded. Use the returned `cursor` (when present) to - * fetch the next page. - * - * @param options - Optional pagination and filter options. - */ - list(options?: AgentMemoryListMemoriesOptions): Promise; - /** - * Soft-delete every memory and message in this profile that is tagged - * with `sessionId`. - * - * Idempotent: deleting a sessionId that has no rows is a no-op. - * - * @param sessionId - Session to delete. - */ - deleteSession(sessionId: string): Promise; -} -/** - * Namespace-level Agent Memory binding. - * - * Used as the type of an `env.MEMORY`-style binding backed by the Agent - * Memory product. - * - * @example - * ```ts - * export default { - * async fetch(_request: Request, env: Env): Promise { - * const profile = await env.MEMORY.getProfile("wrangler-e2e"); - * const summary = await profile.getSummary(); - * return Response.json(summary); - * }, - * }; - * ``` - */ -declare abstract class AgentMemoryNamespace { - /** - * Get a memory profile by name. Profiles are isolated by namespace and - * addressed by a compound key (namespaceId:profileName). - * - * @param profileName - Profile name (validated against naming rules). - * @returns RPC target for interacting with the profile. - */ - getProfile(profileName: string): Promise; - /** - * Soft-delete a profile and schedule deferred purge. Marks all - * memories and messages as deleted. - * - * @param profileName - Name of the profile to delete. - */ - deleteProfile(profileName: string): Promise; -} -// ============ AI Search Error Interfaces ============ -interface AiSearchInternalError extends Error { -} -interface AiSearchNotFoundError extends Error { -} -// ============ AI Search Common Types ============ -/** A single message in a conversation-style search or chat request. */ -type AiSearchMessage = { - role: 'system' | 'developer' | 'user' | 'assistant' | 'tool'; - content: string | null; -}; -/** - * Common shape for `ai_search_options` used by both single-instance and multi-instance requests. - * Contains retrieval, query rewrite, reranking, and cache sub-options. - */ -type AiSearchOptions = { - retrieval?: { - /** Which retrieval backend to use. Defaults to the instance's configured index_method. */ - retrieval_type?: 'vector' | 'keyword' | 'hybrid'; - /** Fusion method for combining vector + keyword results. */ - fusion_method?: 'max' | 'rrf'; - /** How keyword terms are combined: "and" = all terms must match, "or" = any term matches. */ - keyword_match_mode?: 'and' | 'or'; - /** Minimum similarity score (0-1) for a result to be included. Default 0.4. */ - match_threshold?: number; - /** Maximum number of results to return (1-50). Default 10. */ - max_num_results?: number; - /** Vectorize metadata filters applied to the search. */ - filters?: VectorizeVectorMetadataFilter; - /** Number of surrounding chunks to include for context (0-3). Default 0. */ - context_expansion?: number; - /** If true, return only item metadata without chunk text. */ - metadata_only?: boolean; - /** If true (default), return empty results on retrieval failure instead of throwing. */ - return_on_failure?: boolean; - /** Boost results by metadata field values. Max 3 entries. */ - boost_by?: Array<{ - field: string; - direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; - }>; - [key: string]: unknown; - }; - query_rewrite?: { - enabled?: boolean; - model?: string; - rewrite_prompt?: string; - [key: string]: unknown; - }; - reranking?: { - enabled?: boolean; - model?: string; - /** Match threshold (0-1, default 0.4) */ - match_threshold?: number; - [key: string]: unknown; - }; - cache?: { - enabled?: boolean; - cache_threshold?: 'super_strict_match' | 'close_enough' | 'flexible_friend' | 'anything_goes'; - }; - [key: string]: unknown; -}; -// ============ AI Search Request Types ============ -/** - * Request body for single-instance search. - * Exactly one of `query` or `messages` must be provided. - */ -type AiSearchSearchRequest = { - /** Simple query string. */ - query: string; - messages?: never; - ai_search_options?: AiSearchOptions; -} | { - query?: never; - /** Conversation-style input. At least one user message with non-empty content is required. */ - messages: AiSearchMessage[]; - ai_search_options?: AiSearchOptions; -}; -type AiSearchChatCompletionsRequest = { - messages: AiSearchMessage[]; - model?: string; - stream?: boolean; - ai_search_options?: AiSearchOptions; - [key: string]: unknown; -}; -// ============ AI Search Multi-Instance Types (Namespace-Scoped) ============ -/** `ai_search_options` shape for multi-instance requests — requires `instance_ids`. */ -type AiSearchMultiSearchOptions = AiSearchOptions & { - /** Instance IDs to search across (1-10). */ - instance_ids: string[]; -}; -/** - * Request for searching across multiple instances within a namespace. - * `ai_search_options` is required and must include `instance_ids`. - * Exactly one of `query` or `messages` must be provided. - */ -type AiSearchMultiSearchRequest = { - /** Simple query string. */ - query: string; - messages?: never; - ai_search_options: AiSearchMultiSearchOptions; -} | { - query?: never; - /** Conversation-style input. */ - messages: AiSearchMessage[]; - ai_search_options: AiSearchMultiSearchOptions; -}; -/** A search result chunk tagged with the instance it originated from. */ -type AiSearchMultiSearchChunk = AiSearchSearchResponse['chunks'][number] & { - instance_id: string; -}; -/** Describes a per-instance error during a multi-instance operation. */ -type AiSearchMultiSearchError = { - instance_id: string; - message: string; -}; -/** Response from a multi-instance search, with chunks tagged by instance and optional partial-failure errors. */ -type AiSearchMultiSearchResponse = { - search_query: string; - chunks: AiSearchMultiSearchChunk[]; - errors?: AiSearchMultiSearchError[]; -}; -/** Request for chat completions across multiple instances within a namespace. `ai_search_options` is required and must include `instance_ids`. */ -type AiSearchMultiChatCompletionsRequest = Omit & { - ai_search_options: AiSearchMultiSearchOptions; -}; -/** Response from multi-instance chat completions, with chunks tagged by instance and optional partial-failure errors. */ -type AiSearchMultiChatCompletionsResponse = Omit & { - chunks: AiSearchMultiSearchChunk[]; - errors?: AiSearchMultiSearchError[]; -}; -// ============ AI Search Response Types ============ -type AiSearchSearchResponse = { - search_query: string; - chunks: Array<{ - id: string; - type: string; - /** Match score (0-1) */ - score: number; - text: string; - item: { - timestamp?: number; - key: string; - metadata?: Record; - }; - scoring_details?: { - /** Keyword match score (0-1) */ - keyword_score?: number; - /** Vector similarity score (0-1) */ - vector_score?: number; - /** Keyword rank position */ - keyword_rank?: number; - /** Vector rank position */ - vector_rank?: number; - /** Reranking model score */ - reranking_score?: number; - /** Fusion method used to combine results */ - fusion_method?: 'rrf' | 'max'; - [key: string]: unknown; - }; - }>; -}; -type AiSearchChatCompletionsResponse = { - id?: string; - object?: string; - model?: string; - choices: Array<{ - index?: number; - message: { - role: 'system' | 'developer' | 'user' | 'assistant' | 'tool'; - content: string | null; - [key: string]: unknown; - }; - [key: string]: unknown; - }>; - chunks: AiSearchSearchResponse['chunks']; - [key: string]: unknown; -}; -type AiSearchStatsResponse = { - queued?: number; - running?: number; - completed?: number; - error?: number; - skipped?: number; - outdated?: number; - last_activity?: string; - /** Storage engine statistics. */ - engine?: { - vectorize?: { - vectorsCount: number; - dimensions: number; - }; - r2?: { - payloadSizeBytes: number; - metadataSizeBytes: number; - objectCount: number; - }; - }; -}; -// ============ AI Search Instance Info Types ============ -type AiSearchInstanceInfo = { - id: string; - type?: 'r2' | 'web-crawler' | string; - source?: string; - source_params?: unknown; - paused?: boolean; - status?: string; - namespace?: string; - created_at?: string; - modified_at?: string; - token_id?: string; - ai_gateway_id?: string; - rewrite_query?: boolean; - reranking?: boolean; - embedding_model?: string; - ai_search_model?: string; - rewrite_model?: string; - reranking_model?: string; - /** @deprecated Use index_method instead. */ - hybrid_search_enabled?: boolean; - /** Controls which storage backends are active. */ - index_method?: { - vector?: boolean; - keyword?: boolean; - }; - /** Fusion method for combining vector and keyword results. */ - fusion_method?: 'max' | 'rrf'; - indexing_options?: { - keyword_tokenizer?: 'porter' | 'trigram'; - } | null; - retrieval_options?: { - keyword_match_mode?: 'and' | 'or'; - boost_by?: Array<{ - field: string; - direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; - }>; - } | null; - chunk?: boolean; - chunk_size?: number; - chunk_overlap?: number; - score_threshold?: number; - max_num_results?: number; - cache?: boolean; - cache_threshold?: 'super_strict_match' | 'close_enough' | 'flexible_friend' | 'anything_goes'; - custom_metadata?: Array<{ - field_name: string; - data_type: 'text' | 'number' | 'boolean' | 'datetime'; - }>; - /** Sync interval in seconds. */ - sync_interval?: 3600 | 7200 | 14400 | 21600 | 43200 | 86400; - metadata?: Record; - [key: string]: unknown; -}; -/** Pagination, search, and ordering parameters for listing instances within a namespace. */ -type AiSearchListInstancesParams = { - page?: number; - per_page?: number; - /** Search instances by ID. */ - search?: string; - /** Field to sort by. */ - order_by?: 'created_at'; - /** Sort direction. */ - order_by_direction?: 'asc' | 'desc'; -}; -type AiSearchListResponse = { - result: AiSearchInstanceInfo[]; - result_info?: { - count: number; - page: number; - per_page: number; - total_count: number; - }; -}; -// ============ AI Search Config Types ============ -type AiSearchConfig = { - /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */ - id: string; - /** Instance type. Omit to create with built-in storage. */ - type?: 'r2' | 'web-crawler' | string; - /** Source URL (required for web-crawler type). */ - source?: string; - source_params?: unknown; - /** Token ID (UUID format) */ - token_id?: string; - ai_gateway_id?: string; - /** Enable query rewriting (default false) */ - rewrite_query?: boolean; - /** Enable reranking (default false) */ - reranking?: boolean; - embedding_model?: string; - ai_search_model?: string; - rewrite_model?: string; - reranking_model?: string; - /** @deprecated Use index_method instead. */ - hybrid_search_enabled?: boolean; - /** Controls which storage backends are used during indexing. Defaults to vector-only. */ - index_method?: { - vector?: boolean; - keyword?: boolean; - }; - /** Fusion method for combining vector and keyword results. "rrf" = reciprocal rank fusion (default), "max" = maximum score. */ - fusion_method?: 'max' | 'rrf'; - indexing_options?: { - keyword_tokenizer?: 'porter' | 'trigram'; - } | null; - retrieval_options?: { - keyword_match_mode?: 'and' | 'or'; - boost_by?: Array<{ - field: string; - direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; - }>; - } | null; - chunk?: boolean; - chunk_size?: number; - chunk_overlap?: number; - /** Minimum similarity score (0-1) for a result to be included. */ - score_threshold?: number; - max_num_results?: number; - cache?: boolean; - /** Similarity threshold for cache hits. Stricter = fewer cache hits but higher relevance. */ - cache_threshold?: 'super_strict_match' | 'close_enough' | 'flexible_friend' | 'anything_goes'; - custom_metadata?: Array<{ - field_name: string; - data_type: 'text' | 'number' | 'boolean' | 'datetime'; - }>; - namespace?: string; - /** Sync interval in seconds. 3600=1h, 7200=2h, 14400=4h, 21600=6h, 43200=12h, 86400=24h. */ - sync_interval?: 3600 | 7200 | 14400 | 21600 | 43200 | 86400; - metadata?: Record; - [key: string]: unknown; -}; -// ============ AI Search Item Types ============ -type AiSearchItemInfo = { - id: string; - key: string; - status: 'completed' | 'error' | 'skipped' | 'queued' | 'running' | 'outdated'; - next_action?: 'INDEX' | 'DELETE' | null; - error?: string; - checksum?: string; - namespace?: string; - chunks_count?: number | null; - file_size?: number | null; - source_id?: string | null; - last_seen_at?: string; - created_at?: string; - metadata?: Record; - [key: string]: unknown; -}; -type AiSearchItemContentResult = { - body: ReadableStream; - contentType: string; - filename: string; - size: number; -}; -type AiSearchUploadItemOptions = { - metadata?: Record; -}; -type AiSearchListItemsParams = { - page?: number; - per_page?: number; - /** Search items by key name. */ - search?: string; - /** Sort order for results. */ - sort_by?: 'status' | 'modified_at'; - /** Filter items by processing status. */ - status?: 'queued' | 'running' | 'completed' | 'error' | 'skipped' | 'outdated'; - /** Filter items by source (e.g. "builtin" or "web-crawler:https://example.com"). */ - source?: string; - /** JSON-encoded Vectorize filter for metadata filtering. */ - metadata_filter?: string; - /** Filter items by their unique ID. Returns at most one item. */ - item_id?: string; - /** - * Filter items by their exact key (object key / filename). Keys are unique - * per source, so combine with `source` to disambiguate across data sources. - */ - key?: string; -}; -type AiSearchListItemsResponse = { - result: AiSearchItemInfo[]; - result_info?: { - count: number; - page: number; - per_page: number; - total_count: number; - }; -}; -// ============ AI Search Item Logs Types ============ -type AiSearchItemLogsParams = { - /** Maximum number of log entries to return (1-100, default 50). */ - limit?: number; - /** Opaque cursor for pagination. Pass the `cursor` value from a previous response. */ - cursor?: string; -}; -type AiSearchItemLog = { - timestamp: string; - action: string; - message: string; - fileKey?: string; - chunkCount?: number; - processingTimeMs?: number; - errorType?: string; -}; -/** Paginated response for item processing logs (cursor-based). */ -type AiSearchItemLogsResponse = { - result: AiSearchItemLog[]; - result_info: { - count: number; - per_page: number; - cursor: string | null; - truncated: boolean; - }; -}; -// ============ AI Search Item Chunks Types ============ -type AiSearchItemChunksParams = { - /** Maximum number of chunks to return (1-100, default 20). */ - limit?: number; - /** Offset into the chunks list (default 0). */ - offset?: number; -}; -/** A single indexed chunk belonging to an item, including its text content and byte range. */ -type AiSearchItemChunk = { - id: string; - text: string; - start_byte: number; - end_byte: number; - item?: { - timestamp?: number; - key: string; - metadata?: Record; - }; -}; -/** Paginated response for item chunks (offset-based). */ -type AiSearchItemChunksResponse = { - result: AiSearchItemChunk[]; - result_info: { - count: number; - total: number; - limit: number; - offset: number; - }; -}; -// ============ AI Search Job Types ============ -type AiSearchJobInfo = { - id: string; - source: 'user' | 'schedule'; - description?: string; - last_seen_at?: string; - started_at?: string; - ended_at?: string; - end_reason?: string; -}; -type AiSearchJobLog = { - id: number; - message: string; - message_type: number; - created_at: number; -}; -type AiSearchCreateJobParams = { - description?: string; -}; -type AiSearchListJobsParams = { - page?: number; - per_page?: number; -}; -type AiSearchListJobsResponse = { - result: AiSearchJobInfo[]; - result_info?: { - count: number; - page: number; - per_page: number; - total_count: number; - }; -}; -type AiSearchJobLogsParams = { - page?: number; - per_page?: number; -}; -type AiSearchJobLogsResponse = { - result: AiSearchJobLog[]; - result_info?: { - count: number; - page: number; - per_page: number; - total_count: number; - }; -}; -// ============ AI Search Sub-Service Classes ============ -/** - * Single item service for an AI Search instance. - * Provides info, download, sync, logs, and chunks operations on a specific item. - */ -declare abstract class AiSearchItem { - /** Get metadata about this item. */ - info(): Promise; - /** - * Download the item's content. - * @returns Object with body stream, content type, filename, and size. - */ - download(): Promise; - /** - * Trigger re-indexing of this item. - * @returns The updated item info. - */ - sync(): Promise; - /** - * Retrieve processing logs for this item (cursor-based pagination). - * @param params Optional pagination parameters (limit, cursor). - * @returns Paginated log entries for this item. - */ - logs(params?: AiSearchItemLogsParams): Promise; - /** - * List indexed chunks for this item (offset-based pagination). - * @param params Optional pagination parameters (limit, offset). - * @returns Paginated chunk entries for this item. - */ - chunks(params?: AiSearchItemChunksParams): Promise; -} -/** - * Items collection service for an AI Search instance. - * Provides list, upload, and access to individual items. - */ -declare abstract class AiSearchItems { - /** List items in this instance. */ - list(params?: AiSearchListItemsParams): Promise; - /** - * Upload a file as an item. Behaves as an upsert: if an item with the same - * filename already exists, it is overwritten and re-indexed. - * @param name Filename for the uploaded item. - * @param content File content as a ReadableStream, Blob, or string. - * @param options Optional metadata to attach to the item. - * @returns The created item info. - */ - upload(name: string, content: ReadableStream | Blob | string, options?: AiSearchUploadItemOptions): Promise; - /** - * Upload a file and poll until processing completes. - * Behaves as an upsert: if an item with the same filename already exists, - * it is overwritten and re-indexed. - * @param name Filename for the uploaded item. - * @param content File content as a ReadableStream, Blob, or string. - * @param options Optional metadata and polling configuration. - * @returns The item info after processing completes (or timeout). - */ - uploadAndPoll(name: string, content: ReadableStream | Blob | string, options?: AiSearchUploadItemOptions & { - /** Polling interval in milliseconds (default 1000). */ - pollIntervalMs?: number; - /** Maximum time to wait in milliseconds (default 30000). */ - timeoutMs?: number; - }): Promise; - /** - * Get an item by ID. - * @param itemId The item identifier. - * @returns Item service for info, download, sync, logs, and chunks operations. - */ - get(itemId: string): AiSearchItem; - /** - * Delete an item from the instance. - * @param itemId The item identifier. - */ - delete(itemId: string): Promise; -} -/** - * Single job service for an AI Search instance. - * Provides info, logs, and cancel operations for a specific job. - */ -declare abstract class AiSearchJob { - /** Get metadata about this job. */ - info(): Promise; - /** Get logs for this job. */ - logs(params?: AiSearchJobLogsParams): Promise; - /** - * Cancel a running job. - * @returns The updated job info. - * @throws AiSearchNotFoundError if the job does not exist. - */ - cancel(): Promise; -} -/** - * Jobs collection service for an AI Search instance. - * Provides list, create, and access to individual jobs. - */ -declare abstract class AiSearchJobs { - /** List jobs for this instance. */ - list(params?: AiSearchListJobsParams): Promise; - /** - * Create a new indexing job. - * @param params Optional job parameters. - * @returns The created job info. - */ - create(params?: AiSearchCreateJobParams): Promise; - /** - * Get a job by ID. - * @param jobId The job identifier. - * @returns Job service for info, logs, and cancel operations. - */ - get(jobId: string): AiSearchJob; -} -// ============ AI Search Binding Classes ============ -/** - * Instance-level AI Search service. - * - * Used as: - * - The return type of `AiSearchNamespace.get(name)` (namespace binding) - * - The type of `env.BLOG_SEARCH` (single instance binding via `ai_search`) - * - * Provides search, chat, update, stats, items, and jobs operations. - * - * @example - * ```ts - * // Via namespace binding - * const instance = env.AI_SEARCH.get("blog"); - * const results = await instance.search({ - * query: "How does caching work?", - * }); - * - * // Via single instance binding - * const results = await env.BLOG_SEARCH.search({ - * messages: [{ role: "user", content: "How does caching work?" }], - * }); - * ``` - */ -declare abstract class AiSearchInstance { - /** - * Search the AI Search instance for relevant chunks. - * @param params Search request with query or messages and optional AI search options. - * @returns Search response with matching chunks and search query. - */ - search(params: AiSearchSearchRequest): Promise; - /** - * Generate chat completions with AI Search context (streaming). - * @param params Chat completions request with stream: true. - * @returns ReadableStream of server-sent events. - */ - chatCompletions(params: AiSearchChatCompletionsRequest & { - stream: true; - }): Promise; - /** - * Generate chat completions with AI Search context. - * @param params Chat completions request. - * @returns Chat completion response with choices and RAG chunks. - */ - chatCompletions(params: AiSearchChatCompletionsRequest): Promise; - /** - * Update the instance configuration. - * @param config Partial configuration to update. - * @returns Updated instance info. - */ - update(config: Partial): Promise; - /** Get metadata about this instance. */ - info(): Promise; - /** - * Get instance statistics (item count, indexing status, etc.). - * @returns Statistics with counts per status, last activity time, and engine details. - */ - stats(): Promise; - /** Items collection — list, upload, and manage items in this instance. */ - get items(): AiSearchItems; - /** Jobs collection — list, create, and inspect indexing jobs. */ - get jobs(): AiSearchJobs; -} -/** - * Namespace-level AI Search service. - * - * Used as the type of `env.AI_SEARCH` (namespace binding via `ai_search_namespaces`). - * Scoped to a single namespace. Provides dynamic instance access, creation, deletion, - * and multi-instance search/chat operations. - * - * @example - * ```ts - * // Access an instance within the namespace - * const blog = env.AI_SEARCH.get("blog"); - * const results = await blog.search({ query: "How does caching work?" }); - * - * // List all instances in the namespace - * const instances = await env.AI_SEARCH.list(); - * - * // Create a new instance with built-in storage - * const tenant = await env.AI_SEARCH.create({ id: "tenant-123" }); - * - * // Upload items into the instance - * await tenant.items.upload("doc.pdf", fileContent); - * - * // Search across multiple instances - * const multi = await env.AI_SEARCH.search({ - * query: "caching", - * ai_search_options: { instance_ids: ["blog", "docs"] }, - * }); - * - * // Delete an instance - * await env.AI_SEARCH.delete("tenant-123"); - * ``` - */ -declare abstract class AiSearchNamespace { - /** - * Get an instance by name within the bound namespace. - * @param name Instance name. - * @returns Instance service for search, chat, update, stats, items, and jobs. - */ - get(name: string): AiSearchInstance; - /** - * List instances in the bound namespace. - * @param params Optional pagination, search, and ordering parameters. - * @returns Array of instance metadata with pagination info. - */ - list(params?: AiSearchListInstancesParams): Promise; - /** - * Create a new instance within the bound namespace. - * @param config Instance configuration. Only `id` is required — omit `type` and `source` to create with built-in storage. - * @returns Instance service for the newly created instance. - * - * @example - * ```ts - * // Create with built-in storage (upload items manually) - * const instance = await env.AI_SEARCH.create({ id: "my-search" }); - * - * // Create with web crawler source - * const instance = await env.AI_SEARCH.create({ - * id: "docs-search", - * type: "web-crawler", - * source: "https://developers.cloudflare.com", - * }); - * ``` - */ - create(config: AiSearchConfig): Promise; - /** - * Delete an instance from the bound namespace. - * @param name Instance name to delete. - */ - delete(name: string): Promise; - /** - * Search across multiple instances within the bound namespace. - * Fans out to the specified instance_ids and merges results. - * @param params Search request with required `ai_search_options.instance_ids`. - * @returns Search response with chunks tagged by instance_id and optional partial-failure errors. - */ - search(params: AiSearchMultiSearchRequest): Promise; - /** - * Generate chat completions across multiple instances within the bound namespace (streaming). - * Fans out to the specified instance_ids, merges context, and generates a response. - * @param params Chat completions request with stream: true and required `ai_search_options.instance_ids`. - * @returns ReadableStream of server-sent events. - */ - chatCompletions(params: AiSearchMultiChatCompletionsRequest & { - stream: true; - }): Promise; - /** - * Generate chat completions across multiple instances within the bound namespace. - * Fans out to the specified instance_ids, merges context, and generates a response. - * @param params Chat completions request with required `ai_search_options.instance_ids`. - * @returns Chat completion response with choices, chunks tagged by instance_id, and optional partial-failure errors. - */ - chatCompletions(params: AiSearchMultiChatCompletionsRequest): Promise; -} -type AiImageClassificationInput = { - image: number[]; -}; -type AiImageClassificationOutput = { - score?: number; - label?: string; -}[]; -declare abstract class BaseAiImageClassification { - inputs: AiImageClassificationInput; - postProcessedOutputs: AiImageClassificationOutput; -} -type AiImageToTextInput = { - image: number[]; - prompt?: string; - max_tokens?: number; - temperature?: number; - top_p?: number; - top_k?: number; - seed?: number; - repetition_penalty?: number; - frequency_penalty?: number; - presence_penalty?: number; - raw?: boolean; - messages?: RoleScopedChatInput[]; -}; -type AiImageToTextOutput = { - description: string; -}; -declare abstract class BaseAiImageToText { - inputs: AiImageToTextInput; - postProcessedOutputs: AiImageToTextOutput; -} -type AiImageTextToTextInput = { - image: string; - prompt?: string; - max_tokens?: number; - temperature?: number; - ignore_eos?: boolean; - top_p?: number; - top_k?: number; - seed?: number; - repetition_penalty?: number; - frequency_penalty?: number; - presence_penalty?: number; - raw?: boolean; - messages?: RoleScopedChatInput[]; -}; -type AiImageTextToTextOutput = { - description: string; -}; -declare abstract class BaseAiImageTextToText { - inputs: AiImageTextToTextInput; - postProcessedOutputs: AiImageTextToTextOutput; -} -type AiMultimodalEmbeddingsInput = { - image: string; - text: string[]; -}; -type AiIMultimodalEmbeddingsOutput = { - data: number[][]; - shape: number[]; -}; -declare abstract class BaseAiMultimodalEmbeddings { - inputs: AiImageTextToTextInput; - postProcessedOutputs: AiImageTextToTextOutput; -} -type AiObjectDetectionInput = { - image: number[]; -}; -type AiObjectDetectionOutput = { - score?: number; - label?: string; -}[]; -declare abstract class BaseAiObjectDetection { - inputs: AiObjectDetectionInput; - postProcessedOutputs: AiObjectDetectionOutput; -} -type AiSentenceSimilarityInput = { - source: string; - sentences: string[]; -}; -type AiSentenceSimilarityOutput = number[]; -declare abstract class BaseAiSentenceSimilarity { - inputs: AiSentenceSimilarityInput; - postProcessedOutputs: AiSentenceSimilarityOutput; -} -type AiAutomaticSpeechRecognitionInput = { - audio: number[]; -}; -type AiAutomaticSpeechRecognitionOutput = { - text?: string; - words?: { - word: string; - start: number; - end: number; - }[]; - vtt?: string; -}; -declare abstract class BaseAiAutomaticSpeechRecognition { - inputs: AiAutomaticSpeechRecognitionInput; - postProcessedOutputs: AiAutomaticSpeechRecognitionOutput; -} -type AiSummarizationInput = { - input_text: string; - max_length?: number; -}; -type AiSummarizationOutput = { - summary: string; -}; -declare abstract class BaseAiSummarization { - inputs: AiSummarizationInput; - postProcessedOutputs: AiSummarizationOutput; -} -type AiTextClassificationInput = { - text: string; -}; -type AiTextClassificationOutput = { - score?: number; - label?: string; -}[]; -declare abstract class BaseAiTextClassification { - inputs: AiTextClassificationInput; - postProcessedOutputs: AiTextClassificationOutput; -} -type AiTextEmbeddingsInput = { - text: string | string[]; -}; -type AiTextEmbeddingsOutput = { - shape: number[]; - data: number[][]; -}; -declare abstract class BaseAiTextEmbeddings { - inputs: AiTextEmbeddingsInput; - postProcessedOutputs: AiTextEmbeddingsOutput; -} -type RoleScopedChatInput = { - role: "user" | "assistant" | "system" | "tool" | (string & NonNullable); - content: string; - name?: string; -}; -type AiTextGenerationToolLegacyInput = { - name: string; - description: string; - parameters?: { - type: "object" | (string & NonNullable); - properties: { - [key: string]: { - type: string; - description?: string; - }; - }; - required: string[]; - }; -}; -type AiTextGenerationToolInput = { - type: "function" | (string & NonNullable); - function: { - name: string; - description: string; - parameters?: { - type: "object" | (string & NonNullable); - properties: { - [key: string]: { - type: string; - description?: string; - }; - }; - required: string[]; - }; - }; -}; -type AiTextGenerationFunctionsInput = { - name: string; - code: string; -}; -type AiTextGenerationResponseFormat = { - type: string; - json_schema?: any; -}; -type AiTextGenerationInput = { - prompt?: string; - raw?: boolean; - stream?: boolean; - max_tokens?: number; - temperature?: number; - top_p?: number; - top_k?: number; - seed?: number; - repetition_penalty?: number; - frequency_penalty?: number; - presence_penalty?: number; - messages?: RoleScopedChatInput[]; - response_format?: AiTextGenerationResponseFormat; - tools?: AiTextGenerationToolInput[] | AiTextGenerationToolLegacyInput[] | (object & NonNullable); - functions?: AiTextGenerationFunctionsInput[]; -}; -type AiTextGenerationToolLegacyOutput = { - name: string; - arguments: unknown; -}; -type AiTextGenerationToolOutput = { - id: string; - type: "function"; - function: { - name: string; - arguments: string; - }; -}; -type UsageTags = { - prompt_tokens: number; - completion_tokens: number; - total_tokens: number; -}; -type AiTextGenerationOutput = { - response?: string; - tool_calls?: AiTextGenerationToolLegacyOutput[] & AiTextGenerationToolOutput[]; - usage?: UsageTags; -}; -declare abstract class BaseAiTextGeneration { - inputs: AiTextGenerationInput; - postProcessedOutputs: AiTextGenerationOutput; -} -type AiTextToSpeechInput = { - prompt: string; - lang?: string; -}; -type AiTextToSpeechOutput = Uint8Array | { - audio: string; -}; -declare abstract class BaseAiTextToSpeech { - inputs: AiTextToSpeechInput; - postProcessedOutputs: AiTextToSpeechOutput; -} -type AiTextToImageInput = { - prompt: string; - negative_prompt?: string; - height?: number; - width?: number; - image?: number[]; - image_b64?: string; - mask?: number[]; - num_steps?: number; - strength?: number; - guidance?: number; - seed?: number; -}; -type AiTextToImageOutput = ReadableStream; -declare abstract class BaseAiTextToImage { - inputs: AiTextToImageInput; - postProcessedOutputs: AiTextToImageOutput; -} -type AiTranslationInput = { - text: string; - target_lang: string; - source_lang?: string; -}; -type AiTranslationOutput = { - translated_text?: string; -}; -declare abstract class BaseAiTranslation { - inputs: AiTranslationInput; - postProcessedOutputs: AiTranslationOutput; -} -/** - * Workers AI support for OpenAI's Chat Completions API - */ -type ChatCompletionContentPartText = { - type: "text"; - text: string; -}; -type ChatCompletionContentPartImage = { - type: "image_url"; - image_url: { - url: string; - detail?: "auto" | "low" | "high"; - }; -}; -type ChatCompletionContentPartInputAudio = { - type: "input_audio"; - input_audio: { - /** Base64 encoded audio data. */ - data: string; - format: "wav" | "mp3"; - }; -}; -type ChatCompletionContentPartFile = { - type: "file"; - file: { - /** Base64 encoded file data. */ - file_data?: string; - /** The ID of an uploaded file. */ - file_id?: string; - filename?: string; - }; -}; -type ChatCompletionContentPartRefusal = { - type: "refusal"; - refusal: string; -}; -type ChatCompletionContentPart = ChatCompletionContentPartText | ChatCompletionContentPartImage | ChatCompletionContentPartInputAudio | ChatCompletionContentPartFile; -type FunctionDefinition = { - name: string; - description?: string; - parameters?: Record; - strict?: boolean | null; -}; -type ChatCompletionFunctionTool = { - type: "function"; - function: FunctionDefinition; -}; -type ChatCompletionCustomToolGrammarFormat = { - type: "grammar"; - grammar: { - definition: string; - syntax: "lark" | "regex"; - }; -}; -type ChatCompletionCustomToolTextFormat = { - type: "text"; -}; -type ChatCompletionCustomToolFormat = ChatCompletionCustomToolTextFormat | ChatCompletionCustomToolGrammarFormat; -type ChatCompletionCustomTool = { - type: "custom"; - custom: { - name: string; - description?: string; - format?: ChatCompletionCustomToolFormat; - }; -}; -type ChatCompletionTool = ChatCompletionFunctionTool | ChatCompletionCustomTool; -type ChatCompletionMessageFunctionToolCall = { - id: string; - type: "function"; - function: { - name: string; - /** JSON-encoded arguments string. */ - arguments: string; - }; -}; -type ChatCompletionMessageCustomToolCall = { - id: string; - type: "custom"; - custom: { - name: string; - input: string; - }; -}; -type ChatCompletionMessageToolCall = ChatCompletionMessageFunctionToolCall | ChatCompletionMessageCustomToolCall; -type ChatCompletionToolChoiceFunction = { - type: "function"; - function: { - name: string; - }; -}; -type ChatCompletionToolChoiceCustom = { - type: "custom"; - custom: { - name: string; - }; -}; -type ChatCompletionToolChoiceAllowedTools = { - type: "allowed_tools"; - allowed_tools: { - mode: "auto" | "required"; - tools: Array>; - }; -}; -type ChatCompletionToolChoiceOption = "none" | "auto" | "required" | ChatCompletionToolChoiceFunction | ChatCompletionToolChoiceCustom | ChatCompletionToolChoiceAllowedTools; -type DeveloperMessage = { - role: "developer"; - content: string | Array<{ - type: "text"; - text: string; - }>; - name?: string; -}; -type SystemMessage = { - role: "system"; - content: string | Array<{ - type: "text"; - text: string; - }>; - name?: string; -}; -/** - * Permissive merged content part used inside UserMessage arrays. - * - * Cabidela has a limitation where anyOf/oneOf with enum-based discrimination - * inside nested array items does not correctly match different branches for - * different array elements, so the schema uses a single merged object. - */ -type UserMessageContentPart = { - type: "text" | "image_url" | "input_audio" | "file"; - text?: string; - image_url?: { - url?: string; - detail?: "auto" | "low" | "high"; - }; - input_audio?: { - data?: string; - format?: "wav" | "mp3"; - }; - file?: { - file_data?: string; - file_id?: string; - filename?: string; - }; -}; -type UserMessage = { - role: "user"; - content: string | Array; - name?: string; -}; -type AssistantMessageContentPart = { - type: "text" | "refusal"; - text?: string; - refusal?: string; -}; -type AssistantMessage = { - role: "assistant"; - content?: string | null | Array; - refusal?: string | null; - name?: string; - audio?: { - id: string; - }; - tool_calls?: Array; - function_call?: { - name: string; - arguments: string; - }; -}; -type ToolMessage = { - role: "tool"; - content: string | Array<{ - type: "text"; - text: string; - }>; - tool_call_id: string; -}; -type FunctionMessage = { - role: "function"; - content: string; - name: string; -}; -type ChatCompletionMessageParam = DeveloperMessage | SystemMessage | UserMessage | AssistantMessage | ToolMessage | FunctionMessage; -type ChatCompletionsResponseFormatText = { - type: "text"; -}; -type ChatCompletionsResponseFormatJSONObject = { - type: "json_object"; -}; -type ResponseFormatJSONSchema = { - type: "json_schema"; - json_schema: { - name: string; - description?: string; - schema?: Record; - strict?: boolean | null; - }; -}; -type ResponseFormat = ChatCompletionsResponseFormatText | ChatCompletionsResponseFormatJSONObject | ResponseFormatJSONSchema; -type ChatCompletionsStreamOptions = { - include_usage?: boolean; - include_obfuscation?: boolean; -}; -type PredictionContent = { - type: "content"; - content: string | Array<{ - type: "text"; - text: string; - }>; -}; -type AudioParams = { - voice: string | { - id: string; - }; - format: "wav" | "aac" | "mp3" | "flac" | "opus" | "pcm16"; -}; -type WebSearchUserLocation = { - type: "approximate"; - approximate: { - city?: string; - country?: string; - region?: string; - timezone?: string; - }; -}; -type WebSearchOptions = { - search_context_size?: "low" | "medium" | "high"; - user_location?: WebSearchUserLocation; -}; -type ChatTemplateKwargs = { - /** Whether to enable reasoning, enabled by default. */ - enable_thinking?: boolean; - /** If false, preserves reasoning context between turns. */ - clear_thinking?: boolean; -}; -/** Shared optional properties used by both Prompt and Messages input branches. */ -type ChatCompletionsCommonOptions = { - model?: string; - audio?: AudioParams; - frequency_penalty?: number | null; - logit_bias?: Record | null; - logprobs?: boolean | null; - top_logprobs?: number | null; - max_tokens?: number | null; - max_completion_tokens?: number | null; - metadata?: Record | null; - modalities?: Array<"text" | "audio"> | null; - n?: number | null; - parallel_tool_calls?: boolean; - prediction?: PredictionContent; - presence_penalty?: number | null; - reasoning_effort?: "low" | "medium" | "high" | null; - chat_template_kwargs?: ChatTemplateKwargs; - response_format?: ResponseFormat; - seed?: number | null; - service_tier?: "auto" | "default" | "flex" | "scale" | "priority" | null; - stop?: string | Array | null; - store?: boolean | null; - stream?: boolean | null; - stream_options?: ChatCompletionsStreamOptions; - temperature?: number | null; - tool_choice?: ChatCompletionToolChoiceOption; - tools?: Array; - top_p?: number | null; - user?: string; - web_search_options?: WebSearchOptions; - function_call?: "none" | "auto" | { - name: string; - }; - functions?: Array; -}; -type PromptTokensDetails = { - cached_tokens?: number; - audio_tokens?: number; -}; -type CompletionTokensDetails = { - reasoning_tokens?: number; - audio_tokens?: number; - accepted_prediction_tokens?: number; - rejected_prediction_tokens?: number; -}; -type CompletionUsage = { - prompt_tokens: number; - completion_tokens: number; - total_tokens: number; - prompt_tokens_details?: PromptTokensDetails; - completion_tokens_details?: CompletionTokensDetails; -}; -type ChatCompletionTopLogprob = { - token: string; - logprob: number; - bytes: Array | null; -}; -type ChatCompletionTokenLogprob = { - token: string; - logprob: number; - bytes: Array | null; - top_logprobs: Array; -}; -type ChatCompletionAudio = { - id: string; - /** Base64 encoded audio bytes. */ - data: string; - expires_at: number; - transcript: string; -}; -type ChatCompletionUrlCitation = { - type: "url_citation"; - url_citation: { - url: string; - title: string; - start_index: number; - end_index: number; - }; -}; -type ChatCompletionResponseMessage = { - role: "assistant"; - content: string | null; - refusal: string | null; - annotations?: Array; - audio?: ChatCompletionAudio; - tool_calls?: Array; - function_call?: { - name: string; - arguments: string; - } | null; -}; -type ChatCompletionLogprobs = { - content: Array | null; - refusal?: Array | null; -}; -type ChatCompletionChoice = { - index: number; - message: ChatCompletionResponseMessage; - finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | "function_call"; - logprobs: ChatCompletionLogprobs | null; -}; -type ChatCompletionsMessagesInput = { - messages: Array; -} & ChatCompletionsCommonOptions; -type ChatCompletionsOutput = { - id: string; - object: string; - created: number; - model: string; - choices: Array; - usage?: CompletionUsage; - system_fingerprint?: string | null; - service_tier?: "auto" | "default" | "flex" | "scale" | "priority" | null; -}; -/** - * Workers AI support for OpenAI's Responses API - * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts - * - * It's a stripped down version from its source. - * It currently supports basic function calling, json mode and accepts images as input. - * - * It does not include types for WebSearch, CodeInterpreter, FileInputs, MCP, CustomTools. - * We plan to add those incrementally as model + platform capabilities evolve. - */ -type ResponsesInput = { - background?: boolean | null; - conversation?: string | ResponseConversationParam | null; - include?: Array | null; - input?: string | ResponseInput; - instructions?: string | null; - max_output_tokens?: number | null; - parallel_tool_calls?: boolean | null; - previous_response_id?: string | null; - prompt_cache_key?: string; - reasoning?: Reasoning | null; - safety_identifier?: string; - service_tier?: "auto" | "default" | "flex" | "scale" | "priority" | null; - stream?: boolean | null; - stream_options?: StreamOptions | null; - temperature?: number | null; - text?: ResponseTextConfig; - tool_choice?: ToolChoiceOptions | ToolChoiceFunction; - tools?: Array; - top_p?: number | null; - truncation?: "auto" | "disabled" | null; -}; -type ResponsesOutput = { - id?: string; - created_at?: number; - output_text?: string; - error?: ResponseError | null; - incomplete_details?: ResponseIncompleteDetails | null; - instructions?: string | Array | null; - object?: "response"; - output?: Array; - parallel_tool_calls?: boolean; - temperature?: number | null; - tool_choice?: ToolChoiceOptions | ToolChoiceFunction; - tools?: Array; - top_p?: number | null; - max_output_tokens?: number | null; - previous_response_id?: string | null; - prompt?: ResponsePrompt | null; - reasoning?: Reasoning | null; - safety_identifier?: string; - service_tier?: "auto" | "default" | "flex" | "scale" | "priority" | null; - status?: ResponseStatus; - text?: ResponseTextConfig; - truncation?: "auto" | "disabled" | null; - usage?: ResponseUsage; -}; -type EasyInputMessage = { - content: string | ResponseInputMessageContentList; - role: "user" | "assistant" | "system" | "developer"; - type?: "message"; -}; -type ResponsesFunctionTool = { - name: string; - parameters: { - [key: string]: unknown; - } | null; - strict: boolean | null; - type: "function"; - description?: string | null; -}; -type ResponseIncompleteDetails = { - reason?: "max_output_tokens" | "content_filter"; -}; -type ResponsePrompt = { - id: string; - variables?: { - [key: string]: string | ResponseInputText | ResponseInputImage; - } | null; - version?: string | null; -}; -type Reasoning = { - effort?: ReasoningEffort | null; - generate_summary?: "auto" | "concise" | "detailed" | null; - summary?: "auto" | "concise" | "detailed" | null; -}; -type ResponseContent = ResponseInputText | ResponseInputImage | ResponseOutputText | ResponseOutputRefusal | ResponseContentReasoningText; -type ResponseContentReasoningText = { - text: string; - type: "reasoning_text"; -}; -type ResponseConversationParam = { - id: string; -}; -type ResponseCreatedEvent = { - response: Response; - sequence_number: number; - type: "response.created"; -}; -type ResponseCustomToolCallOutput = { - call_id: string; - output: string | Array; - type: "custom_tool_call_output"; - id?: string; -}; -type ResponseError = { - code: "server_error" | "rate_limit_exceeded" | "invalid_prompt" | "vector_store_timeout" | "invalid_image" | "invalid_image_format" | "invalid_base64_image" | "invalid_image_url" | "image_too_large" | "image_too_small" | "image_parse_error" | "image_content_policy_violation" | "invalid_image_mode" | "image_file_too_large" | "unsupported_image_media_type" | "empty_image_file" | "failed_to_download_image" | "image_file_not_found"; - message: string; -}; -type ResponseErrorEvent = { - code: string | null; - message: string; - param: string | null; - sequence_number: number; - type: "error"; -}; -type ResponseFailedEvent = { - response: Response; - sequence_number: number; - type: "response.failed"; -}; -type ResponseFormatText = { - type: "text"; -}; -type ResponseFormatJSONObject = { - type: "json_object"; -}; -type ResponseFormatTextConfig = ResponseFormatText | ResponseFormatTextJSONSchemaConfig | ResponseFormatJSONObject; -type ResponseFormatTextJSONSchemaConfig = { - name: string; - schema: { - [key: string]: unknown; - }; - type: "json_schema"; - description?: string; - strict?: boolean | null; -}; -type ResponseFunctionCallArgumentsDeltaEvent = { - delta: string; - item_id: string; - output_index: number; - sequence_number: number; - type: "response.function_call_arguments.delta"; -}; -type ResponseFunctionCallArgumentsDoneEvent = { - arguments: string; - item_id: string; - name: string; - output_index: number; - sequence_number: number; - type: "response.function_call_arguments.done"; -}; -type ResponseFunctionCallOutputItem = ResponseInputTextContent | ResponseInputImageContent; -type ResponseFunctionCallOutputItemList = Array; -type ResponseFunctionToolCall = { - arguments: string; - call_id: string; - name: string; - type: "function_call"; - id?: string; - status?: "in_progress" | "completed" | "incomplete"; -}; -interface ResponseFunctionToolCallItem extends ResponseFunctionToolCall { - id: string; -} -type ResponseFunctionToolCallOutputItem = { - id: string; - call_id: string; - output: string | Array; - type: "function_call_output"; - status?: "in_progress" | "completed" | "incomplete"; -}; -type ResponseIncludable = "message.input_image.image_url" | "message.output_text.logprobs"; -type ResponseIncompleteEvent = { - response: Response; - sequence_number: number; - type: "response.incomplete"; -}; -type ResponseInput = Array; -type ResponseInputContent = ResponseInputText | ResponseInputImage; -type ResponseInputImage = { - detail: "low" | "high" | "auto"; - type: "input_image"; - /** - * Base64 encoded image - */ - image_url?: string | null; -}; -type ResponseInputImageContent = { - type: "input_image"; - detail?: "low" | "high" | "auto" | null; - /** - * Base64 encoded image - */ - image_url?: string | null; -}; -type ResponseInputItem = EasyInputMessage | ResponseInputItemMessage | ResponseOutputMessage | ResponseFunctionToolCall | ResponseInputItemFunctionCallOutput | ResponseReasoningItem; -type ResponseInputItemFunctionCallOutput = { - call_id: string; - output: string | ResponseFunctionCallOutputItemList; - type: "function_call_output"; - id?: string | null; - status?: "in_progress" | "completed" | "incomplete" | null; -}; -type ResponseInputItemMessage = { - content: ResponseInputMessageContentList; - role: "user" | "system" | "developer"; - status?: "in_progress" | "completed" | "incomplete"; - type?: "message"; -}; -type ResponseInputMessageContentList = Array; -type ResponseInputMessageItem = { - id: string; - content: ResponseInputMessageContentList; - role: "user" | "system" | "developer"; - status?: "in_progress" | "completed" | "incomplete"; - type?: "message"; -}; -type ResponseInputText = { - text: string; - type: "input_text"; -}; -type ResponseInputTextContent = { - text: string; - type: "input_text"; -}; -type ResponseItem = ResponseInputMessageItem | ResponseOutputMessage | ResponseFunctionToolCallItem | ResponseFunctionToolCallOutputItem; -type ResponseOutputItem = ResponseOutputMessage | ResponseFunctionToolCall | ResponseReasoningItem; -type ResponseOutputItemAddedEvent = { - item: ResponseOutputItem; - output_index: number; - sequence_number: number; - type: "response.output_item.added"; -}; -type ResponseOutputItemDoneEvent = { - item: ResponseOutputItem; - output_index: number; - sequence_number: number; - type: "response.output_item.done"; -}; -type ResponseOutputMessage = { - id: string; - content: Array; - role: "assistant"; - status: "in_progress" | "completed" | "incomplete"; - type: "message"; -}; -type ResponseOutputRefusal = { - refusal: string; - type: "refusal"; -}; -type ResponseOutputText = { - text: string; - type: "output_text"; - logprobs?: Array; -}; -type ResponseReasoningItem = { - id: string; - summary: Array; - type: "reasoning"; - content?: Array; - encrypted_content?: string | null; - status?: "in_progress" | "completed" | "incomplete"; -}; -type ResponseReasoningSummaryItem = { - text: string; - type: "summary_text"; -}; -type ResponseReasoningContentItem = { - text: string; - type: "reasoning_text"; -}; -type ResponseReasoningTextDeltaEvent = { - content_index: number; - delta: string; - item_id: string; - output_index: number; - sequence_number: number; - type: "response.reasoning_text.delta"; -}; -type ResponseReasoningTextDoneEvent = { - content_index: number; - item_id: string; - output_index: number; - sequence_number: number; - text: string; - type: "response.reasoning_text.done"; -}; -type ResponseRefusalDeltaEvent = { - content_index: number; - delta: string; - item_id: string; - output_index: number; - sequence_number: number; - type: "response.refusal.delta"; -}; -type ResponseRefusalDoneEvent = { - content_index: number; - item_id: string; - output_index: number; - refusal: string; - sequence_number: number; - type: "response.refusal.done"; -}; -type ResponseStatus = "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete"; -type ResponseStreamEvent = ResponseCompletedEvent | ResponseCreatedEvent | ResponseErrorEvent | ResponseFunctionCallArgumentsDeltaEvent | ResponseFunctionCallArgumentsDoneEvent | ResponseFailedEvent | ResponseIncompleteEvent | ResponseOutputItemAddedEvent | ResponseOutputItemDoneEvent | ResponseReasoningTextDeltaEvent | ResponseReasoningTextDoneEvent | ResponseRefusalDeltaEvent | ResponseRefusalDoneEvent | ResponseTextDeltaEvent | ResponseTextDoneEvent; -type ResponseCompletedEvent = { - response: Response; - sequence_number: number; - type: "response.completed"; -}; -type ResponseTextConfig = { - format?: ResponseFormatTextConfig; - verbosity?: "low" | "medium" | "high" | null; -}; -type ResponseTextDeltaEvent = { - content_index: number; - delta: string; - item_id: string; - logprobs: Array; - output_index: number; - sequence_number: number; - type: "response.output_text.delta"; -}; -type ResponseTextDoneEvent = { - content_index: number; - item_id: string; - logprobs: Array; - output_index: number; - sequence_number: number; - text: string; - type: "response.output_text.done"; -}; -type Logprob = { - token: string; - logprob: number; - top_logprobs?: Array; -}; -type TopLogprob = { - token?: string; - logprob?: number; -}; -type ResponseUsage = { - input_tokens: number; - output_tokens: number; - total_tokens: number; -}; -type Tool = ResponsesFunctionTool; -type ToolChoiceFunction = { - name: string; - type: "function"; -}; -type ToolChoiceOptions = "none"; -type ReasoningEffort = "minimal" | "low" | "medium" | "high" | null; -type StreamOptions = { - include_obfuscation?: boolean; -}; -/** Marks keys from T that aren't in U as optional never */ -type Without = { - [P in Exclude]?: never; -}; -/** Either T or U, but not both (mutually exclusive) */ -type XOR = (T & Without) | (U & Without); -type Ai_Cf_Baai_Bge_Base_En_V1_5_Input = { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; -} | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - }[]; -}; -type Ai_Cf_Baai_Bge_Base_En_V1_5_Output = { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: "mean" | "cls"; -} | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse; -interface Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 { - inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output; -} -type Ai_Cf_Openai_Whisper_Input = string | { - /** - * An array of integers that represent the audio data constrained to 8-bit unsigned integer values - */ - audio: number[]; -}; -interface Ai_Cf_Openai_Whisper_Output { - /** - * The transcription - */ - text: string; - word_count?: number; - words?: { - word?: string; - /** - * The second this word begins in the recording - */ - start?: number; - /** - * The ending second when the word completes - */ - end?: number; - }[]; - vtt?: string; -} -declare abstract class Base_Ai_Cf_Openai_Whisper { - inputs: Ai_Cf_Openai_Whisper_Input; - postProcessedOutputs: Ai_Cf_Openai_Whisper_Output; -} -type Ai_Cf_Meta_M2M100_1_2B_Input = { - /** - * The text to be translated - */ - text: string; - /** - * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified - */ - source_lang?: string; - /** - * The language code to translate the text into (e.g., 'es' for Spanish) - */ - target_lang: string; -} | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - /** - * The text to be translated - */ - text: string; - /** - * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified - */ - source_lang?: string; - /** - * The language code to translate the text into (e.g., 'es' for Spanish) - */ - target_lang: string; - }[]; -}; -type Ai_Cf_Meta_M2M100_1_2B_Output = { - /** - * The translated text in the target language - */ - translated_text?: string; -} | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse; -interface Ai_Cf_Meta_M2M100_1_2B_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B { - inputs: Ai_Cf_Meta_M2M100_1_2B_Input; - postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output; -} -type Ai_Cf_Baai_Bge_Small_En_V1_5_Input = { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; -} | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - }[]; -}; -type Ai_Cf_Baai_Bge_Small_En_V1_5_Output = { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: "mean" | "cls"; -} | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse; -interface Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 { - inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output; -} -type Ai_Cf_Baai_Bge_Large_En_V1_5_Input = { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; -} | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - }[]; -}; -type Ai_Cf_Baai_Bge_Large_En_V1_5_Output = { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: "mean" | "cls"; -} | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse; -interface Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 { - inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output; -} -type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = string | { - /** - * The input text prompt for the model to generate a response. - */ - prompt?: string; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; - image: number[] | (string & NonNullable); - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; -}; -interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output { - description?: string; -} -declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M { - inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input; - postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output; -} -type Ai_Cf_Openai_Whisper_Tiny_En_Input = string | { - /** - * An array of integers that represent the audio data constrained to 8-bit unsigned integer values - */ - audio: number[]; -}; -interface Ai_Cf_Openai_Whisper_Tiny_En_Output { - /** - * The transcription - */ - text: string; - word_count?: number; - words?: { - word?: string; - /** - * The second this word begins in the recording - */ - start?: number; - /** - * The ending second when the word completes - */ - end?: number; - }[]; - vtt?: string; -} -declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En { - inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input; - postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output; -} -interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { - audio: string | { - body?: object; - contentType?: string; - }; - /** - * Supported tasks are 'translate' or 'transcribe'. - */ - task?: string; - /** - * The language of the audio being transcribed or translated. - */ - language?: string; - /** - * Preprocess the audio with a voice activity detection model. - */ - vad_filter?: boolean; - /** - * A text prompt to help provide context to the model on the contents of the audio. - */ - initial_prompt?: string; - /** - * The prefix appended to the beginning of the output of the transcription and can guide the transcription result. - */ - prefix?: string; - /** - * The number of beams to use in beam search decoding. Higher values may improve accuracy at the cost of speed. - */ - beam_size?: number; - /** - * Whether to condition on previous text during transcription. Setting to false may help prevent hallucination loops. - */ - condition_on_previous_text?: boolean; - /** - * Threshold for detecting no-speech segments. Segments with no-speech probability above this value are skipped. - */ - no_speech_threshold?: number; - /** - * Threshold for filtering out segments with high compression ratio, which often indicate repetitive or hallucinated text. - */ - compression_ratio_threshold?: number; - /** - * Threshold for filtering out segments with low average log probability, indicating low confidence. - */ - log_prob_threshold?: number; - /** - * Optional threshold (in seconds) to skip silent periods that may cause hallucinations. - */ - hallucination_silence_threshold?: number; -} -interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output { - transcription_info?: { - /** - * The language of the audio being transcribed or translated. - */ - language?: string; - /** - * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1. - */ - language_probability?: number; - /** - * The total duration of the original audio file, in seconds. - */ - duration?: number; - /** - * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds. - */ - duration_after_vad?: number; - }; - /** - * The complete transcription of the audio. - */ - text: string; - /** - * The total number of words in the transcription. - */ - word_count?: number; - segments?: { - /** - * The starting time of the segment within the audio, in seconds. - */ - start?: number; - /** - * The ending time of the segment within the audio, in seconds. - */ - end?: number; - /** - * The transcription of the segment. - */ - text?: string; - /** - * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs. - */ - temperature?: number; - /** - * The average log probability of the predictions for the words in this segment, indicating overall confidence. - */ - avg_logprob?: number; - /** - * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process. - */ - compression_ratio?: number; - /** - * The probability that the segment contains no speech, represented as a decimal between 0 and 1. - */ - no_speech_prob?: number; - words?: { - /** - * The individual word transcribed from the audio. - */ - word?: string; - /** - * The starting time of the word within the audio, in seconds. - */ - start?: number; - /** - * The ending time of the word within the audio, in seconds. - */ - end?: number; - }[]; - }[]; - /** - * The transcription in WebVTT format, which includes timing and text information for use in subtitles. - */ - vtt?: string; -} -declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo { - inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input; - postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output; -} -type Ai_Cf_Baai_Bge_M3_Input = Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts | Ai_Cf_Baai_Bge_M3_Input_Embedding | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: (Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 | Ai_Cf_Baai_Bge_M3_Input_Embedding_1)[]; -}; -interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts { - /** - * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts - */ - query?: string; - /** - * List of provided contexts. Note that the index in this array is important, as the response will refer to it. - */ - contexts: { - /** - * One of the provided context content - */ - text?: string; - }[]; - /** - * When provided with too long context should the model error out or truncate the context to fit? - */ - truncate_inputs?: boolean; -} -interface Ai_Cf_Baai_Bge_M3_Input_Embedding { - text: string | string[]; - /** - * When provided with too long context should the model error out or truncate the context to fit? - */ - truncate_inputs?: boolean; -} -interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 { - /** - * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts - */ - query?: string; - /** - * List of provided contexts. Note that the index in this array is important, as the response will refer to it. - */ - contexts: { - /** - * One of the provided context content - */ - text?: string; - }[]; - /** - * When provided with too long context should the model error out or truncate the context to fit? - */ - truncate_inputs?: boolean; -} -interface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 { - text: string | string[]; - /** - * When provided with too long context should the model error out or truncate the context to fit? - */ - truncate_inputs?: boolean; -} -type Ai_Cf_Baai_Bge_M3_Output = Ai_Cf_Baai_Bge_M3_Output_Query | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts | Ai_Cf_Baai_Bge_M3_Output_Embedding | Ai_Cf_Baai_Bge_M3_AsyncResponse; -interface Ai_Cf_Baai_Bge_M3_Output_Query { - response?: { - /** - * Index of the context in the request - */ - id?: number; - /** - * Score of the context under the index. - */ - score?: number; - }[]; -} -interface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts { - response?: number[][]; - shape?: number[]; - /** - * The pooling method used in the embedding process. - */ - pooling?: "mean" | "cls"; -} -interface Ai_Cf_Baai_Bge_M3_Output_Embedding { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: "mean" | "cls"; -} -interface Ai_Cf_Baai_Bge_M3_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Baai_Bge_M3 { - inputs: Ai_Cf_Baai_Bge_M3_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output; -} -interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input { - /** - * A text description of the image you want to generate. - */ - prompt: string; - /** - * The number of diffusion steps; higher values can improve quality but take longer. - */ - steps?: number; -} -interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output { - /** - * The generated image in Base64 format. - */ - image?: string; -} -declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell { - inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input; - postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output; -} -type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages; -interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - image?: number[] | (string & NonNullable); - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; -} -interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: string | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - image?: number[] | (string & NonNullable); - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - /** - * If true, the response will be streamed back incrementally. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = { - /** - * The generated text response from the model - */ - response?: string; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct { - inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output; -} -type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input = Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch; -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - content: string | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch { - requests?: { - /** - * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique. - */ - external_reference?: string; - /** - * Prompt for the text generation model - */ - prompt?: string; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; - response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2; - }[]; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -} | string | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse; -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast { - inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output; -} -interface Ai_Cf_Meta_Llama_Guard_3_8B_Input { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender must alternate between 'user' and 'assistant'. - */ - role: "user" | "assistant"; - /** - * The content of the message as a string. - */ - content: string; - }[]; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Dictate the output format of the generated response. - */ - response_format?: { - /** - * Set to json_object to process and output generated text as JSON. - */ - type?: string; - }; -} -interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { - response?: string | { - /** - * Whether the conversation is safe or not. - */ - safe?: boolean; - /** - * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. - */ - categories?: string[]; - }; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; -} -declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B { - inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output; -} -interface Ai_Cf_Baai_Bge_Reranker_Base_Input { - /** - * A query you wish to perform against the provided contexts. - */ - /** - * Number of returned results starting with the best score. - */ - top_k?: number; - /** - * List of provided contexts. Note that the index in this array is important, as the response will refer to it. - */ - contexts: { - /** - * One of the provided context content - */ - text?: string; - }[]; -} -interface Ai_Cf_Baai_Bge_Reranker_Base_Output { - response?: { - /** - * Index of the context in the request - */ - id?: number; - /** - * Score of the context under the index. - */ - score?: number; - }[]; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base { - inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output; -} -type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input = Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages; -interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - /** - * The content of the message as a string. - */ - content: string; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct { - inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input; - postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output; -} -type Ai_Cf_Qwen_Qwq_32B_Input = Ai_Cf_Qwen_Qwq_32B_Prompt | Ai_Cf_Qwen_Qwq_32B_Messages; -interface Ai_Cf_Qwen_Qwq_32B_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fulfilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwq_32B_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: string | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Qwen_Qwq_32B_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Qwen_Qwq_32B { - inputs: Ai_Cf_Qwen_Qwq_32B_Input; - postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output; -} -type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input = Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages; -interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fulfilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: string | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct { - inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input; - postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output; -} -type Ai_Cf_Google_Gemma_3_12B_It_Input = Ai_Cf_Google_Gemma_3_12B_It_Prompt | Ai_Cf_Google_Gemma_3_12B_It_Messages; -interface Ai_Cf_Google_Gemma_3_12B_It_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Google_Gemma_3_12B_It_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - content?: string | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[]; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Google_Gemma_3_12B_It_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It { - inputs: Ai_Cf_Google_Gemma_3_12B_It_Input; - postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output; -} -type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input = Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch; -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fulfilled for the response. - */ - guided_json?: object; - response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: string | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch { - requests: (Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner)[]; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fulfilled for the response. - */ - guided_json?: object; - response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: string | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The tool call id. - */ - id?: string; - /** - * Specifies the type of tool (e.g., 'function'). - */ - type?: string; - /** - * Details of the function tool. - */ - function?: { - /** - * The name of the tool to be called - */ - name?: string; - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - }; - }[]; -}; -declare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct { - inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output; -} -type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input = Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch; -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - content: string | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch { - requests: (Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1)[]; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - content: string | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output = Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response | string | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse; -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response { - /** - * Unique identifier for the completion - */ - id?: string; - /** - * Object type identifier - */ - object?: "chat.completion"; - /** - * Unix timestamp of when the completion was created - */ - created?: number; - /** - * Model used for the completion - */ - model?: string; - /** - * List of completion choices - */ - choices?: { - /** - * Index of the choice in the list - */ - index?: number; - /** - * The message generated by the model - */ - message?: { - /** - * Role of the message author - */ - role: string; - /** - * The content of the message - */ - content: string; - /** - * Internal reasoning content (if available) - */ - reasoning_content?: string; - /** - * Tool calls made by the assistant - */ - tool_calls?: { - /** - * Unique identifier for the tool call - */ - id: string; - /** - * Type of tool call - */ - type: "function"; - function: { - /** - * Name of the function to call - */ - name: string; - /** - * JSON string of arguments for the function - */ - arguments: string; - }; - }[]; - }; - /** - * Reason why the model stopped generating - */ - finish_reason?: string; - /** - * Stop reason (may be null) - */ - stop_reason?: string | null; - /** - * Log probabilities (if requested) - */ - logprobs?: {} | null; - }[]; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * Log probabilities for the prompt (if requested) - */ - prompt_logprobs?: {} | null; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response { - /** - * Unique identifier for the completion - */ - id?: string; - /** - * Object type identifier - */ - object?: "text_completion"; - /** - * Unix timestamp of when the completion was created - */ - created?: number; - /** - * Model used for the completion - */ - model?: string; - /** - * List of completion choices - */ - choices?: { - /** - * Index of the choice in the list - */ - index: number; - /** - * The generated text completion - */ - text: string; - /** - * Reason why the model stopped generating - */ - finish_reason: string; - /** - * Stop reason (may be null) - */ - stop_reason?: string | null; - /** - * Log probabilities (if requested) - */ - logprobs?: {} | null; - /** - * Log probabilities for the prompt (if requested) - */ - prompt_logprobs?: {} | null; - }[]; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8 { - inputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input; - postProcessedOutputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output; -} -interface Ai_Cf_Deepgram_Nova_3_Input { - audio: { - body: object; - contentType: string; - }; - /** - * Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param. - */ - custom_topic_mode?: "extended" | "strict"; - /** - * Custom topics you want the model to detect within your input audio or text if present Submit up to 100 - */ - custom_topic?: string; - /** - * Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in addition those submitted using the custom_intents param - */ - custom_intent_mode?: "extended" | "strict"; - /** - * Custom intents you want the model to detect within your input audio if present - */ - custom_intent?: string; - /** - * Identifies and extracts key entities from content in submitted audio - */ - detect_entities?: boolean; - /** - * Identifies the dominant language spoken in submitted audio - */ - detect_language?: boolean; - /** - * Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0 - */ - diarize?: boolean; - /** - * Identify and extract key entities from content in submitted audio - */ - dictation?: boolean; - /** - * Specify the expected encoding of your submitted audio - */ - encoding?: "linear16" | "flac" | "mulaw" | "amr-nb" | "amr-wb" | "opus" | "speex" | "g729"; - /** - * Arbitrary key-value pairs that are attached to the API response for usage in downstream processing - */ - extra?: string; - /** - * Filler Words can help transcribe interruptions in your audio, like 'uh' and 'um' - */ - filler_words?: boolean; - /** - * Key term prompting can boost or suppress specialized terminology and brands. - */ - keyterm?: string; - /** - * Keywords can boost or suppress specialized terminology and brands. - */ - keywords?: string; - /** - * The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available. - */ - language?: string; - /** - * Spoken measurements will be converted to their corresponding abbreviations. - */ - measurements?: boolean; - /** - * Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip. - */ - mip_opt_out?: boolean; - /** - * Mode of operation for the model representing broad area of topic that will be talked about in the supplied audio - */ - mode?: "general" | "medical" | "finance"; - /** - * Transcribe each audio channel independently. - */ - multichannel?: boolean; - /** - * Numerals converts numbers from written format to numerical format. - */ - numerals?: boolean; - /** - * Splits audio into paragraphs to improve transcript readability. - */ - paragraphs?: boolean; - /** - * Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely. - */ - profanity_filter?: boolean; - /** - * Add punctuation and capitalization to the transcript. - */ - punctuate?: boolean; - /** - * Redaction removes sensitive information from your transcripts. - */ - redact?: string; - /** - * Search for terms or phrases in submitted audio and replaces them. - */ - replace?: string; - /** - * Search for terms or phrases in submitted audio. - */ - search?: string; - /** - * Recognizes the sentiment throughout a transcript or text. - */ - sentiment?: boolean; - /** - * Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability. - */ - smart_format?: boolean; - /** - * Detect topics throughout a transcript or text. - */ - topics?: boolean; - /** - * Segments speech into meaningful semantic units. - */ - utterances?: boolean; - /** - * Seconds to wait before detecting a pause between words in submitted audio. - */ - utt_split?: number; - /** - * The number of channels in the submitted audio - */ - channels?: number; - /** - * Specifies whether the streaming endpoint should provide ongoing transcription updates as more audio is received. When set to true, the endpoint sends continuous updates, meaning transcription results may evolve over time. Note: Supported only for webosockets. - */ - interim_results?: boolean; - /** - * Indicates how long model will wait to detect whether a speaker has finished speaking or pauses for a significant period of time. When set to a value, the streaming endpoint immediately finalizes the transcription for the processed time range and returns the transcript with a speech_final parameter set to true. Can also be set to false to disable endpointing - */ - endpointing?: string; - /** - * Indicates that speech has started. You'll begin receiving Speech Started messages upon speech starting. Note: Supported only for webosockets. - */ - vad_events?: boolean; - /** - * Indicates how long model will wait to send an UtteranceEnd message after a word has been transcribed. Use with interim_results. Note: Supported only for webosockets. - */ - utterance_end_ms?: boolean; -} -interface Ai_Cf_Deepgram_Nova_3_Output { - results?: { - channels?: { - alternatives?: { - confidence?: number; - transcript?: string; - words?: { - confidence?: number; - end?: number; - start?: number; - word?: string; - }[]; - }[]; - }[]; - summary?: { - result?: string; - short?: string; - }; - sentiments?: { - segments?: { - text?: string; - start_word?: number; - end_word?: number; - sentiment?: string; - sentiment_score?: number; - }[]; - average?: { - sentiment?: string; - sentiment_score?: number; - }; - }; - }; -} -declare abstract class Base_Ai_Cf_Deepgram_Nova_3 { - inputs: Ai_Cf_Deepgram_Nova_3_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Nova_3_Output; -} -interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input { - queries?: string | string[]; - /** - * Optional instruction for the task - */ - instruction?: string; - documents?: string | string[]; - text?: string | string[]; -} -interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output { - data?: number[][]; - shape?: number[]; -} -declare abstract class Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B { - inputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input; - postProcessedOutputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output; -} -type Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input = { - /** - * readable stream with audio data and content-type specified for that data - */ - audio: { - body: object; - contentType: string; - }; - /** - * type of data PCM data that's sent to the inference server as raw array - */ - dtype?: "uint8" | "float32" | "float64"; -} | { - /** - * base64 encoded audio data - */ - audio: string; - /** - * type of data PCM data that's sent to the inference server as raw array - */ - dtype?: "uint8" | "float32" | "float64"; -}; -interface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output { - /** - * if true, end-of-turn was detected - */ - is_complete?: boolean; - /** - * probability of the end-of-turn detection - */ - probability?: number; -} -declare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 { - inputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input; - postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output; -} -declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B { - inputs: XOR; - postProcessedOutputs: XOR; -} -declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B { - inputs: XOR; - postProcessedOutputs: XOR; -} -interface Ai_Cf_Leonardo_Phoenix_1_0_Input { - /** - * A text description of the image you want to generate. - */ - prompt: string; - /** - * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt - */ - guidance?: number; - /** - * Random seed for reproducibility of the image generation - */ - seed?: number; - /** - * The height of the generated image in pixels - */ - height?: number; - /** - * The width of the generated image in pixels - */ - width?: number; - /** - * The number of diffusion steps; higher values can improve quality but take longer - */ - num_steps?: number; - /** - * Specify what to exclude from the generated images - */ - negative_prompt?: string; -} -/** - * The generated image in JPEG format - */ -type Ai_Cf_Leonardo_Phoenix_1_0_Output = string; -declare abstract class Base_Ai_Cf_Leonardo_Phoenix_1_0 { - inputs: Ai_Cf_Leonardo_Phoenix_1_0_Input; - postProcessedOutputs: Ai_Cf_Leonardo_Phoenix_1_0_Output; -} -interface Ai_Cf_Leonardo_Lucid_Origin_Input { - /** - * A text description of the image you want to generate. - */ - prompt: string; - /** - * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt - */ - guidance?: number; - /** - * Random seed for reproducibility of the image generation - */ - seed?: number; - /** - * The height of the generated image in pixels - */ - height?: number; - /** - * The width of the generated image in pixels - */ - width?: number; - /** - * The number of diffusion steps; higher values can improve quality but take longer - */ - num_steps?: number; - /** - * The number of diffusion steps; higher values can improve quality but take longer - */ - steps?: number; -} -interface Ai_Cf_Leonardo_Lucid_Origin_Output { - /** - * The generated image in Base64 format. - */ - image?: string; -} -declare abstract class Base_Ai_Cf_Leonardo_Lucid_Origin { - inputs: Ai_Cf_Leonardo_Lucid_Origin_Input; - postProcessedOutputs: Ai_Cf_Leonardo_Lucid_Origin_Output; -} -interface Ai_Cf_Deepgram_Aura_1_Input { - /** - * Speaker used to produce the audio. - */ - speaker?: "angus" | "asteria" | "arcas" | "orion" | "orpheus" | "athena" | "luna" | "zeus" | "perseus" | "helios" | "hera" | "stella"; - /** - * Encoding of the output audio. - */ - encoding?: "linear16" | "flac" | "mulaw" | "alaw" | "mp3" | "opus" | "aac"; - /** - * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type.. - */ - container?: "none" | "wav" | "ogg"; - /** - * The text content to be converted to speech - */ - text: string; - /** - * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable - */ - sample_rate?: number; - /** - * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type. - */ - bit_rate?: number; -} -/** - * The generated audio in MP3 format - */ -type Ai_Cf_Deepgram_Aura_1_Output = string; -declare abstract class Base_Ai_Cf_Deepgram_Aura_1 { - inputs: Ai_Cf_Deepgram_Aura_1_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Aura_1_Output; -} -interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input { - /** - * Input text to translate. Can be a single string or a list of strings. - */ - text: string | string[]; - /** - * Target langauge to translate to - */ - target_language: "asm_Beng" | "awa_Deva" | "ben_Beng" | "bho_Deva" | "brx_Deva" | "doi_Deva" | "eng_Latn" | "gom_Deva" | "gon_Deva" | "guj_Gujr" | "hin_Deva" | "hne_Deva" | "kan_Knda" | "kas_Arab" | "kas_Deva" | "kha_Latn" | "lus_Latn" | "mag_Deva" | "mai_Deva" | "mal_Mlym" | "mar_Deva" | "mni_Beng" | "mni_Mtei" | "npi_Deva" | "ory_Orya" | "pan_Guru" | "san_Deva" | "sat_Olck" | "snd_Arab" | "snd_Deva" | "tam_Taml" | "tel_Telu" | "urd_Arab" | "unr_Deva"; -} -interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output { - /** - * Translated texts - */ - translations: string[]; -} -declare abstract class Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B { - inputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input; - postProcessedOutputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output; -} -type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input = Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch; -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - content: string | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch { - requests: (Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1)[]; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - content: string | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ({ - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - })[]; - response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3 { - type?: "json_object" | "json_schema"; - json_schema?: unknown; -} -type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output = Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response | string | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse; -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response { - /** - * Unique identifier for the completion - */ - id?: string; - /** - * Object type identifier - */ - object?: "chat.completion"; - /** - * Unix timestamp of when the completion was created - */ - created?: number; - /** - * Model used for the completion - */ - model?: string; - /** - * List of completion choices - */ - choices?: { - /** - * Index of the choice in the list - */ - index?: number; - /** - * The message generated by the model - */ - message?: { - /** - * Role of the message author - */ - role: string; - /** - * The content of the message - */ - content: string; - /** - * Internal reasoning content (if available) - */ - reasoning_content?: string; - /** - * Tool calls made by the assistant - */ - tool_calls?: { - /** - * Unique identifier for the tool call - */ - id: string; - /** - * Type of tool call - */ - type: "function"; - function: { - /** - * Name of the function to call - */ - name: string; - /** - * JSON string of arguments for the function - */ - arguments: string; - }; - }[]; - }; - /** - * Reason why the model stopped generating - */ - finish_reason?: string; - /** - * Stop reason (may be null) - */ - stop_reason?: string | null; - /** - * Log probabilities (if requested) - */ - logprobs?: {} | null; - }[]; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * Log probabilities for the prompt (if requested) - */ - prompt_logprobs?: {} | null; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response { - /** - * Unique identifier for the completion - */ - id?: string; - /** - * Object type identifier - */ - object?: "text_completion"; - /** - * Unix timestamp of when the completion was created - */ - created?: number; - /** - * Model used for the completion - */ - model?: string; - /** - * List of completion choices - */ - choices?: { - /** - * Index of the choice in the list - */ - index: number; - /** - * The generated text completion - */ - text: string; - /** - * Reason why the model stopped generating - */ - finish_reason: string; - /** - * Stop reason (may be null) - */ - stop_reason?: string | null; - /** - * Log probabilities (if requested) - */ - logprobs?: {} | null; - /** - * Log probabilities for the prompt (if requested) - */ - prompt_logprobs?: {} | null; - }[]; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It { - inputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input; - postProcessedOutputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output; -} -interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Input { - /** - * Input text to embed. Can be a single string or a list of strings. - */ - text: string | string[]; -} -interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Output { - /** - * Embedding vectors, where each vector is a list of floats. - */ - data: number[][]; - /** - * Shape of the embedding data as [number_of_embeddings, embedding_dimension]. - * - * @minItems 2 - * @maxItems 2 - */ - shape: [ - number, - number - ]; -} -declare abstract class Base_Ai_Cf_Pfnet_Plamo_Embedding_1B { - inputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Input; - postProcessedOutputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Output; -} -interface Ai_Cf_Deepgram_Flux_Input { - /** - * Encoding of the audio stream. Currently only supports raw signed little-endian 16-bit PCM. - */ - encoding: "linear16"; - /** - * Sample rate of the audio stream in Hz. - */ - sample_rate: string; - /** - * End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid Values 0.3 - 0.9. - */ - eager_eot_threshold?: string; - /** - * End-of-turn confidence required to finish a turn. Valid Values 0.5 - 0.9. - */ - eot_threshold?: string; - /** - * A turn will be finished when this much time has passed after speech, regardless of EOT confidence. - */ - eot_timeout_ms?: string; - /** - * Keyterm prompting can improve recognition of specialized terminology. Pass multiple keyterm query parameters to boost multiple keyterms. - */ - keyterm?: string; - /** - * Opts out requests from the Deepgram Model Improvement Program. Refer to Deepgram Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip - */ - mip_opt_out?: "true" | "false"; - /** - * Label your requests for the purpose of identification during usage reporting - */ - tag?: string; -} -/** - * Output will be returned as websocket messages. - */ -interface Ai_Cf_Deepgram_Flux_Output { - /** - * The unique identifier of the request (uuid) - */ - request_id?: string; - /** - * Starts at 0 and increments for each message the server sends to the client. - */ - sequence_id?: number; - /** - * The type of event being reported. - */ - event?: "Update" | "StartOfTurn" | "EagerEndOfTurn" | "TurnResumed" | "EndOfTurn"; - /** - * The index of the current turn - */ - turn_index?: number; - /** - * Start time in seconds of the audio range that was transcribed - */ - audio_window_start?: number; - /** - * End time in seconds of the audio range that was transcribed - */ - audio_window_end?: number; - /** - * Text that was said over the course of the current turn - */ - transcript?: string; - /** - * The words in the transcript - */ - words?: { - /** - * The individual punctuated, properly-cased word from the transcript - */ - word: string; - /** - * Confidence that this word was transcribed correctly - */ - confidence: number; - }[]; - /** - * Confidence that no more speech is coming in this turn - */ - end_of_turn_confidence?: number; -} -declare abstract class Base_Ai_Cf_Deepgram_Flux { - inputs: Ai_Cf_Deepgram_Flux_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Flux_Output; -} -interface Ai_Cf_Deepgram_Aura_2_En_Input { - /** - * Speaker used to produce the audio. - */ - speaker?: "amalthea" | "andromeda" | "apollo" | "arcas" | "aries" | "asteria" | "athena" | "atlas" | "aurora" | "callista" | "cora" | "cordelia" | "delia" | "draco" | "electra" | "harmonia" | "helena" | "hera" | "hermes" | "hyperion" | "iris" | "janus" | "juno" | "jupiter" | "luna" | "mars" | "minerva" | "neptune" | "odysseus" | "ophelia" | "orion" | "orpheus" | "pandora" | "phoebe" | "pluto" | "saturn" | "thalia" | "theia" | "vesta" | "zeus"; - /** - * Encoding of the output audio. - */ - encoding?: "linear16" | "flac" | "mulaw" | "alaw" | "mp3" | "opus" | "aac"; - /** - * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type.. - */ - container?: "none" | "wav" | "ogg"; - /** - * The text content to be converted to speech - */ - text: string; - /** - * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable - */ - sample_rate?: number; - /** - * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type. - */ - bit_rate?: number; -} -/** - * The generated audio in MP3 format - */ -type Ai_Cf_Deepgram_Aura_2_En_Output = string; -declare abstract class Base_Ai_Cf_Deepgram_Aura_2_En { - inputs: Ai_Cf_Deepgram_Aura_2_En_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_En_Output; -} -interface Ai_Cf_Deepgram_Aura_2_Es_Input { - /** - * Speaker used to produce the audio. - */ - speaker?: "sirio" | "nestor" | "carina" | "celeste" | "alvaro" | "diana" | "aquila" | "selena" | "estrella" | "javier"; - /** - * Encoding of the output audio. - */ - encoding?: "linear16" | "flac" | "mulaw" | "alaw" | "mp3" | "opus" | "aac"; - /** - * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type.. - */ - container?: "none" | "wav" | "ogg"; - /** - * The text content to be converted to speech - */ - text: string; - /** - * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable - */ - sample_rate?: number; - /** - * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type. - */ - bit_rate?: number; -} -/** - * The generated audio in MP3 format - */ -type Ai_Cf_Deepgram_Aura_2_Es_Output = string; -declare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es { - inputs: Ai_Cf_Deepgram_Aura_2_Es_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output; -} -interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input { - multipart: { - body?: object; - contentType?: string; - }; -} -interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output { - /** - * Generated image as Base64 string. - */ - image?: string; -} -declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev { - inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input; - postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output; -} -interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input { - multipart: { - body?: object; - contentType?: string; - }; -} -interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output { - /** - * Generated image as Base64 string. - */ - image?: string; -} -declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B { - inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input; - postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output; -} -interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input { - multipart: { - body?: object; - contentType?: string; - }; -} -interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output { - /** - * Generated image as Base64 string. - */ - image?: string; -} -declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B { - inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input; - postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output; -} -declare abstract class Base_Ai_Cf_Zai_Org_Glm_4_7_Flash { - inputs: ChatCompletionsInput; - postProcessedOutputs: ChatCompletionsOutput; -} -declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_5 { - inputs: ChatCompletionsInput; - postProcessedOutputs: ChatCompletionsOutput; -} -declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_6 { - inputs: ChatCompletionsInput; - postProcessedOutputs: ChatCompletionsOutput; -} -declare abstract class Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B { - inputs: ChatCompletionsInput; - postProcessedOutputs: ChatCompletionsOutput; -} -declare abstract class Base_Ai_Cf_Google_Gemma_4_26B_A4B_IT { - inputs: ChatCompletionsInput; - postProcessedOutputs: ChatCompletionsOutput; -} -interface AiModels { - "@cf/huggingface/distilbert-sst-2-int8": BaseAiTextClassification; - "@cf/stabilityai/stable-diffusion-xl-base-1.0": BaseAiTextToImage; - "@cf/runwayml/stable-diffusion-v1-5-inpainting": BaseAiTextToImage; - "@cf/runwayml/stable-diffusion-v1-5-img2img": BaseAiTextToImage; - "@cf/lykon/dreamshaper-8-lcm": BaseAiTextToImage; - "@cf/bytedance/stable-diffusion-xl-lightning": BaseAiTextToImage; - "@cf/myshell-ai/melotts": BaseAiTextToSpeech; - "@cf/google/embeddinggemma-300m": BaseAiTextEmbeddings; - "@cf/microsoft/resnet-50": BaseAiImageClassification; - "@cf/meta/llama-2-7b-chat-int8": BaseAiTextGeneration; - "@cf/mistral/mistral-7b-instruct-v0.1": BaseAiTextGeneration; - "@cf/meta/llama-2-7b-chat-fp16": BaseAiTextGeneration; - "@hf/thebloke/llama-2-13b-chat-awq": BaseAiTextGeneration; - "@hf/thebloke/mistral-7b-instruct-v0.1-awq": BaseAiTextGeneration; - "@hf/thebloke/zephyr-7b-beta-awq": BaseAiTextGeneration; - "@hf/thebloke/openhermes-2.5-mistral-7b-awq": BaseAiTextGeneration; - "@hf/thebloke/neural-chat-7b-v3-1-awq": BaseAiTextGeneration; - "@hf/thebloke/deepseek-coder-6.7b-base-awq": BaseAiTextGeneration; - "@hf/thebloke/deepseek-coder-6.7b-instruct-awq": BaseAiTextGeneration; - "@cf/deepseek-ai/deepseek-math-7b-instruct": BaseAiTextGeneration; - "@cf/defog/sqlcoder-7b-2": BaseAiTextGeneration; - "@cf/openchat/openchat-3.5-0106": BaseAiTextGeneration; - "@cf/tiiuae/falcon-7b-instruct": BaseAiTextGeneration; - "@cf/thebloke/discolm-german-7b-v1-awq": BaseAiTextGeneration; - "@cf/qwen/qwen1.5-0.5b-chat": BaseAiTextGeneration; - "@cf/qwen/qwen1.5-7b-chat-awq": BaseAiTextGeneration; - "@cf/qwen/qwen1.5-14b-chat-awq": BaseAiTextGeneration; - "@cf/tinyllama/tinyllama-1.1b-chat-v1.0": BaseAiTextGeneration; - "@cf/microsoft/phi-2": BaseAiTextGeneration; - "@cf/qwen/qwen1.5-1.8b-chat": BaseAiTextGeneration; - "@cf/mistral/mistral-7b-instruct-v0.2-lora": BaseAiTextGeneration; - "@hf/nousresearch/hermes-2-pro-mistral-7b": BaseAiTextGeneration; - "@hf/nexusflow/starling-lm-7b-beta": BaseAiTextGeneration; - "@hf/google/gemma-7b-it": BaseAiTextGeneration; - "@cf/meta-llama/llama-2-7b-chat-hf-lora": BaseAiTextGeneration; - "@cf/google/gemma-2b-it-lora": BaseAiTextGeneration; - "@cf/google/gemma-7b-it-lora": BaseAiTextGeneration; - "@hf/mistral/mistral-7b-instruct-v0.2": BaseAiTextGeneration; - "@cf/meta/llama-3-8b-instruct": BaseAiTextGeneration; - "@cf/fblgit/una-cybertron-7b-v2-bf16": BaseAiTextGeneration; - "@cf/meta/llama-3-8b-instruct-awq": BaseAiTextGeneration; - "@cf/meta/llama-3.1-8b-instruct-fp8": BaseAiTextGeneration; - "@cf/meta/llama-3.1-8b-instruct-awq": BaseAiTextGeneration; - "@cf/meta/llama-3.2-3b-instruct": BaseAiTextGeneration; - "@cf/meta/llama-3.2-1b-instruct": BaseAiTextGeneration; - "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": BaseAiTextGeneration; - "@cf/ibm-granite/granite-4.0-h-micro": BaseAiTextGeneration; - "@cf/facebook/bart-large-cnn": BaseAiSummarization; - "@cf/llava-hf/llava-1.5-7b-hf": BaseAiImageToText; - "@cf/baai/bge-base-en-v1.5": Base_Ai_Cf_Baai_Bge_Base_En_V1_5; - "@cf/openai/whisper": Base_Ai_Cf_Openai_Whisper; - "@cf/meta/m2m100-1.2b": Base_Ai_Cf_Meta_M2M100_1_2B; - "@cf/baai/bge-small-en-v1.5": Base_Ai_Cf_Baai_Bge_Small_En_V1_5; - "@cf/baai/bge-large-en-v1.5": Base_Ai_Cf_Baai_Bge_Large_En_V1_5; - "@cf/unum/uform-gen2-qwen-500m": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M; - "@cf/openai/whisper-tiny-en": Base_Ai_Cf_Openai_Whisper_Tiny_En; - "@cf/openai/whisper-large-v3-turbo": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo; - "@cf/baai/bge-m3": Base_Ai_Cf_Baai_Bge_M3; - "@cf/black-forest-labs/flux-1-schnell": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell; - "@cf/meta/llama-3.2-11b-vision-instruct": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct; - "@cf/meta/llama-3.3-70b-instruct-fp8-fast": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast; - "@cf/meta/llama-guard-3-8b": Base_Ai_Cf_Meta_Llama_Guard_3_8B; - "@cf/baai/bge-reranker-base": Base_Ai_Cf_Baai_Bge_Reranker_Base; - "@cf/qwen/qwen2.5-coder-32b-instruct": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct; - "@cf/qwen/qwq-32b": Base_Ai_Cf_Qwen_Qwq_32B; - "@cf/mistralai/mistral-small-3.1-24b-instruct": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct; - "@cf/google/gemma-3-12b-it": Base_Ai_Cf_Google_Gemma_3_12B_It; - "@cf/meta/llama-4-scout-17b-16e-instruct": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct; - "@cf/qwen/qwen3-30b-a3b-fp8": Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8; - "@cf/deepgram/nova-3": Base_Ai_Cf_Deepgram_Nova_3; - "@cf/qwen/qwen3-embedding-0.6b": Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B; - "@cf/pipecat-ai/smart-turn-v2": Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2; - "@cf/openai/gpt-oss-120b": Base_Ai_Cf_Openai_Gpt_Oss_120B; - "@cf/openai/gpt-oss-20b": Base_Ai_Cf_Openai_Gpt_Oss_20B; - "@cf/leonardo/phoenix-1.0": Base_Ai_Cf_Leonardo_Phoenix_1_0; - "@cf/leonardo/lucid-origin": Base_Ai_Cf_Leonardo_Lucid_Origin; - "@cf/deepgram/aura-1": Base_Ai_Cf_Deepgram_Aura_1; - "@cf/ai4bharat/indictrans2-en-indic-1B": Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B; - "@cf/aisingapore/gemma-sea-lion-v4-27b-it": Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It; - "@cf/pfnet/plamo-embedding-1b": Base_Ai_Cf_Pfnet_Plamo_Embedding_1B; - "@cf/deepgram/flux": Base_Ai_Cf_Deepgram_Flux; - "@cf/deepgram/aura-2-en": Base_Ai_Cf_Deepgram_Aura_2_En; - "@cf/deepgram/aura-2-es": Base_Ai_Cf_Deepgram_Aura_2_Es; - "@cf/black-forest-labs/flux-2-dev": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev; - "@cf/black-forest-labs/flux-2-klein-4b": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B; - "@cf/black-forest-labs/flux-2-klein-9b": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B; - "@cf/zai-org/glm-4.7-flash": Base_Ai_Cf_Zai_Org_Glm_4_7_Flash; - "@cf/moonshotai/kimi-k2.5": Base_Ai_Cf_Moonshotai_Kimi_K2_5; - "@cf/moonshotai/kimi-k2.6": Base_Ai_Cf_Moonshotai_Kimi_K2_6; - "@cf/nvidia/nemotron-3-120b-a12b": Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B; - "@cf/google/gemma-4-26b-a4b-it": Base_Ai_Cf_Google_Gemma_4_26B_A4B_IT; -} -type AiOptions = { - /** - * Send requests as an asynchronous batch job, only works for supported models - * https://developers.cloudflare.com/workers-ai/features/batch-api - */ - queueRequest?: boolean; - /** - * Establish websocket connections, only works for supported models - */ - websocket?: boolean; - /** - * Tag your requests to group and view them in Cloudflare dashboard. - * - * Rules: - * Tags must only contain letters, numbers, and the symbols: : - . / @ - * Each tag can have maximum 50 characters. - * Maximum 5 tags are allowed each request. - * Duplicate tags will removed. - */ - tags?: string[]; - gateway?: GatewayOptions; - returnRawResponse?: boolean; - prefix?: string; - extraHeaders?: object; - signal?: AbortSignal; -}; -type AiModelsSearchParams = { - author?: string; - hide_experimental?: boolean; - page?: number; - per_page?: number; - search?: string; - source?: number; - task?: string; -}; -type AiModelsSearchObject = { - id: string; - source: number; - name: string; - description: string; - task: { - id: string; - name: string; - description: string; - }; - tags: string[]; - properties: { - property_id: string; - value: string; - }[]; -}; -type ChatCompletionsBase = ChatCompletionsMessagesInput; -type ChatCompletionsInput = ChatCompletionsMessagesInput; -interface InferenceUpstreamError extends Error { -} -interface AiInternalError extends Error { -} -type AiModelListType = Record; -type AiAsyncBatchResponse = { - request_id: string; -}; -declare abstract class Ai { - aiGatewayLogId: string | null; - gateway(gatewayId: string): AiGateway; - /** - * @deprecated Use the standalone `ai_search_namespaces` or `ai_search` Workers bindings instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ - aiSearch(): AiSearchNamespace; - /** - * @deprecated AutoRAG has been replaced by AI Search. - * Use the standalone `ai_search_namespaces` or `ai_search` Workers bindings instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - * - * @param autoragId Instance ID - */ - autorag(autoragId: string): AutoRAG; - // Batch request - run(model: Name, inputs: { - requests: AiModelList[Name]['inputs'][]; - }, options: AiOptions & { - queueRequest: true; - }): Promise; - // Raw response - run(model: Name, inputs: AiModelList[Name]['inputs'], options: AiOptions & { - returnRawResponse: true; - }): Promise; - // WebSocket - run(model: Name, inputs: AiModelList[Name]['inputs'], options: AiOptions & { - websocket: true; - }): Promise; - // Streaming - run(model: Name, inputs: AiModelList[Name]['inputs'] & { - stream: true; - }, options?: AiOptions): Promise; - // Normal (default) - known model - run(model: Name, inputs: AiModelList[Name]['inputs'], options?: AiOptions): Promise; - // Unknown model (fallback). - // - // The `Exclude<..., keyof AiModelList>` constraint forces TypeScript to - // route any model name that is a literal key of `AiModelList` to one of - // the known-model overloads above (so input/output mismatches surface as - // type errors rather than silently falling back to `Record`). - // Names that aren't in `AiModelList` — e.g. third-party gateway models - // like `"google/nano-banana"` — still hit this overload. - run(model: Model extends keyof AiModelList ? never : Model, inputs: Record, options?: AiOptions): Promise>; - models(params?: AiModelsSearchParams): Promise; - toMarkdown(): ToMarkdownService; - toMarkdown(files: MarkdownDocument[], options?: ConversionRequestOptions): Promise; - toMarkdown(files: MarkdownDocument, options?: ConversionRequestOptions): Promise; -} -type GatewayRetries = { - maxAttempts?: 1 | 2 | 3 | 4 | 5; - retryDelayMs?: number; - backoff?: 'constant' | 'linear' | 'exponential'; -}; -type GatewayOptions = { - id: string; - cacheKey?: string; - cacheTtl?: number; - skipCache?: boolean; - metadata?: Record; - collectLog?: boolean; - eventId?: string; - requestTimeoutMs?: number; - retries?: GatewayRetries; -}; -type UniversalGatewayOptions = Exclude & { - /** - ** @deprecated - */ - id?: string; -}; -type AiGatewayPatchLog = { - score?: number | null; - feedback?: -1 | 1 | null; - metadata?: Record | null; -}; -type AiGatewayLog = { - id: string; - provider: string; - model: string; - model_type?: string; - path: string; - duration: number; - request_type?: string; - request_content_type?: string; - status_code: number; - response_content_type?: string; - success: boolean; - cached: boolean; - tokens_in?: number; - tokens_out?: number; - metadata?: Record; - step?: number; - cost?: number; - custom_cost?: boolean; - request_size: number; - request_head?: string; - request_head_complete: boolean; - response_size: number; - response_head?: string; - response_head_complete: boolean; - created_at: Date; -}; -type AIGatewayProviders = 'workers-ai' | 'anthropic' | 'aws-bedrock' | 'azure-openai' | 'google-vertex-ai' | 'huggingface' | 'openai' | 'perplexity-ai' | 'replicate' | 'groq' | 'cohere' | 'google-ai-studio' | 'mistral' | 'grok' | 'openrouter' | 'deepseek' | 'cerebras' | 'cartesia' | 'elevenlabs' | 'adobe-firefly'; -type AIGatewayHeaders = { - 'cf-aig-metadata': Record | string; - 'cf-aig-custom-cost': { - per_token_in?: number; - per_token_out?: number; - } | { - total_cost?: number; - } | string; - 'cf-aig-cache-ttl': number | string; - 'cf-aig-skip-cache': boolean | string; - 'cf-aig-cache-key': string; - 'cf-aig-event-id': string; - 'cf-aig-request-timeout': number | string; - 'cf-aig-max-attempts': number | string; - 'cf-aig-retry-delay': number | string; - 'cf-aig-backoff': string; - 'cf-aig-collect-log': boolean | string; - Authorization: string; - 'Content-Type': string; - [key: string]: string | number | boolean | object; -}; -type AIGatewayUniversalRequest = { - provider: AIGatewayProviders | string; // eslint-disable-line - endpoint: string; - headers: Partial; - query: unknown; -}; -interface AiGatewayInternalError extends Error { -} -interface AiGatewayLogNotFound extends Error { -} -declare abstract class AiGateway { - patchLog(logId: string, data: AiGatewayPatchLog): Promise; - getLog(logId: string): Promise; - run(data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[], options?: { - gateway?: UniversalGatewayOptions; - extraHeaders?: object; - signal?: AbortSignal; - }): Promise; - getUrl(provider?: AIGatewayProviders | string): Promise; // eslint-disable-line -} -// Copyright (c) 2022-2025 Cloudflare, Inc. -// Licensed under the Apache 2.0 license found in the LICENSE file or at: -// https://opensource.org/licenses/Apache-2.0 -/** - * Artifacts — Git-compatible file storage on Cloudflare Workers. - * - * Provides programmatic access to create, manage, and fork repositories, - * and to issue and revoke scoped access tokens. - */ -/** Information about a repository. */ -interface ArtifactsRepoInfo { - /** Unique repository ID. */ - id: string; - /** Repository name. */ - name: string; - /** Repository description, or null if not set. */ - description: string | null; - /** Default branch name (e.g. "main"). */ - defaultBranch: string; - /** ISO 8601 creation timestamp. */ - createdAt: string; - /** ISO 8601 last-updated timestamp. */ - updatedAt: string; - /** ISO 8601 timestamp of the last push, or null if never pushed. */ - lastPushAt: string | null; - /** Fork source (e.g. "github:owner/repo", "artifacts:namespace/repo"), or null if not a fork. */ - source: string | null; - /** Whether the repository is read-only. */ - readOnly: boolean; - /** HTTPS git remote URL. */ - remote: string; -} -/** Result of creating a repository — includes the initial access token. */ -interface ArtifactsCreateRepoResult { - /** Unique repository ID. */ - id: string; - /** Repository name. */ - name: string; - /** Repository description, or null if not set. */ - description: string | null; - /** Default branch name. */ - defaultBranch: string; - /** HTTPS git remote URL. */ - remote: string; - /** Plaintext access token (only returned at creation time). */ - token: string; - /** ISO 8601 token expiry timestamp. */ - tokenExpiresAt: string; -} -/** Paginated list of repositories. */ -interface ArtifactsRepoListResult { - /** Repositories in this page (without the `remote` field). */ - repos: Omit[]; - /** Total number of repositories in the namespace. */ - total: number; - /** Cursor for the next page, if there are more results. */ - cursor?: string; -} -/** Result of creating an access token. */ -interface ArtifactsCreateTokenResult { - /** Unique token ID. */ - id: string; - /** Plaintext token (only returned at creation time). */ - plaintext: string; - /** Token scope: "read" or "write". */ - scope: 'read' | 'write'; - /** ISO 8601 token expiry timestamp. */ - expiresAt: string; -} -/** Token metadata (no plaintext). */ -interface ArtifactsTokenInfo { - /** Unique token ID. */ - id: string; - /** Token scope: "read" or "write". */ - scope: 'read' | 'write'; - /** Token state: "active", "expired", or "revoked". */ - state: 'active' | 'expired' | 'revoked'; - /** ISO 8601 creation timestamp. */ - createdAt: string; - /** ISO 8601 expiry timestamp. */ - expiresAt: string; -} -/** Paginated list of tokens for a repository. */ -interface ArtifactsTokenListResult { - /** Tokens in this page. */ - tokens: ArtifactsTokenInfo[]; - /** Total number of tokens for the repository. */ - total: number; -} -/** - * Handle for a single repository. Returned by Artifacts.get(). - * - * Methods may throw `ArtifactsError` with code `INTERNAL_ERROR` if an unexpected service error occurs. - */ -interface ArtifactsRepo extends ArtifactsRepoInfo { - /** - * Create an access token for this repo. - * @param scope Token scope: "write" (default) or "read". - * @param ttl Time-to-live in seconds (default 86400, min 60, max 31536000). - * @throws {ArtifactsError} with code `INVALID_TTL` if ttl is out of range. - */ - createToken(scope?: 'write' | 'read', ttl?: number): Promise; - /** List tokens for this repo (metadata only, no plaintext). */ - listTokens(): Promise; - /** - * Revoke a token by plaintext or ID. - * @param tokenOrId Plaintext token or token ID. - * @returns true if revoked, false if not found. - * @throws {ArtifactsError} with code `INVALID_INPUT` if tokenOrId is empty. - */ - revokeToken(tokenOrId: string): Promise; - // ── Fork ── - /** - * Fork this repo to a new repo. - * @param name Target repository name. - * @param opts Optional: description, readOnly flag, defaultBranchOnly (default true). - * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. - * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the target repo already exists. - * @throws {ArtifactsError} with code `FORK_IN_PROGRESS` if a fork is already running. - */ - fork(name: string, opts?: { - description?: string; - readOnly?: boolean; - defaultBranchOnly?: boolean; - }): Promise; -} -// ── Error types ────────────────────────────────────────────────────────────── -/** - * Error codes returned by Artifacts binding operations. - * - * Each code maps to a numeric code available on `ArtifactsError.numericCode`. - */ -type ArtifactsErrorCode = 'ALREADY_EXISTS' | 'NOT_FOUND' | 'IMPORT_IN_PROGRESS' | 'FORK_IN_PROGRESS' | 'INVALID_INPUT' | 'INVALID_REPO_NAME' | 'INVALID_TTL' | 'INVALID_URL' | 'REMOTE_AUTH_REQUIRED' | 'UPSTREAM_UNAVAILABLE' | 'MEMORY_LIMIT' | 'INTERNAL_ERROR'; -/** - * Error thrown by Artifacts binding operations. - * - * Uses a string `.code` discriminator following the Cloudflare platform - * convention (StreamError, ImagesError, etc.). The `.numericCode` matches - * the REST API `errors[].code` values. - */ -interface ArtifactsError extends Error { - readonly name: 'ArtifactsError'; - /** String error code for programmatic matching. */ - readonly code: ArtifactsErrorCode; - /** Numeric error code matching the REST API. */ - readonly numericCode: number; -} -// ── Binding ────────────────────────────────────────────────────────────────── -/** - * Artifacts binding — namespace-level operations. - * - * Methods may throw `ArtifactsError` with code `INTERNAL_ERROR` if an unexpected service error occurs. - */ -interface Artifacts { - /** - * Create a new repository with an initial access token. - * @param name Repository name (alphanumeric, dots, hyphens, underscores). - * @param opts Optional: readOnly flag, description, default branch name. - * @returns Repo metadata with initial token. - * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. - * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the repo already exists. - */ - create(name: string, opts?: { - readOnly?: boolean; - description?: string; - setDefaultBranch?: string; - }): Promise; - /** - * Get a handle to an existing repository. - * @param name Repository name. - * @returns Repo handle. - * @throws {ArtifactsError} with code `NOT_FOUND` if the repo does not exist. - * @throws {ArtifactsError} with code `IMPORT_IN_PROGRESS` if the repo is still importing. - * @throws {ArtifactsError} with code `FORK_IN_PROGRESS` if the repo is still forking. - */ - get(name: string): Promise; - /** - * Import a repository from an external git remote. - * @param params Source URL and optional branch/depth, plus target name and options. - * @returns Repo metadata with initial token. - * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if the target name is invalid. - * @throws {ArtifactsError} with code `INVALID_INPUT` if the source URL is not valid HTTPS. - * @throws {ArtifactsError} with code `INVALID_URL` if the source URL does not point to a git repository. - * @throws {ArtifactsError} with code `REMOTE_AUTH_REQUIRED` if the remote requires authentication. - * @throws {ArtifactsError} with code `NOT_FOUND` if the remote repository does not exist. - * @throws {ArtifactsError} with code `UPSTREAM_UNAVAILABLE` if the remote cannot be reached. - * @throws {ArtifactsError} with code `MEMORY_LIMIT` if the import exceeds service memory limits. - * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the target repo already exists. - */ - import(params: { - source: { - url: string; - branch?: string; - depth?: number; - }; - target: { - name: string; - opts?: { - description?: string; - readOnly?: boolean; - }; - }; - }): Promise; - /** - * List repositories with cursor-based pagination. - * @param opts Optional: limit (1–200, default 50), cursor for next page. - */ - list(opts?: { - limit?: number; - cursor?: string; - }): Promise; - /** - * Delete a repository and all associated tokens. - * @param name Repository name. - * @returns true if deleted, false if not found. - * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. - */ - delete(name: string): Promise; -} -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -interface AutoRAGInternalError extends Error { -} -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -interface AutoRAGNotFoundError extends Error { -} -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -interface AutoRAGUnauthorizedError extends Error { -} -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -interface AutoRAGNameNotSetError extends Error { -} -type ComparisonFilter = { - key: string; - type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'; - value: string | number | boolean; -}; -type CompoundFilter = { - type: 'and' | 'or'; - filters: ComparisonFilter[]; -}; -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -type AutoRagSearchRequest = { - query: string; - filters?: CompoundFilter | ComparisonFilter; - max_num_results?: number; - ranking_options?: { - ranker?: string; - score_threshold?: number; - }; - reranking?: { - enabled?: boolean; - model?: string; - }; - rewrite_query?: boolean; -}; -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -type AutoRagAiSearchRequest = AutoRagSearchRequest & { - stream?: boolean; - system_prompt?: string; -}; -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -type AutoRagAiSearchRequestStreaming = Omit & { - stream: true; -}; -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -type AutoRagSearchResponse = { - object: 'vector_store.search_results.page'; - search_query: string; - data: { - file_id: string; - filename: string; - score: number; - attributes: Record; - content: { - type: 'text'; - text: string; - }[]; - }[]; - has_more: boolean; - next_page: string | null; -}; -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -type AutoRagListResponse = { - id: string; - enable: boolean; - type: string; - source: string; - vectorize_name: string; - paused: boolean; - status: string; -}[]; -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -type AutoRagAiSearchResponse = AutoRagSearchResponse & { - response: string; -}; -/** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ -declare abstract class AutoRAG { - /** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ - list(): Promise; - /** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ - search(params: AutoRagSearchRequest): Promise; - /** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ - aiSearch(params: AutoRagAiSearchRequestStreaming): Promise; - /** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ - aiSearch(params: AutoRagAiSearchRequest): Promise; - /** - * @deprecated Use the standalone AI Search Workers binding instead. - * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ - */ - aiSearch(params: AutoRagAiSearchRequest): Promise; -} -type BrowserRunLifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'; -type BrowserRunResourceType = 'document' | 'stylesheet' | 'image' | 'media' | 'font' | 'script' | 'texttrack' | 'xhr' | 'fetch' | 'prefetch' | 'eventsource' | 'websocket' | 'manifest' | 'signedexchange' | 'ping' | 'cspviolationreport' | 'preflight' | 'other'; -/** Options fields shared by all quick actions. */ -interface BrowserRunBaseOptions { - /** Adds ` - CLI access · Cloudflarebase + CLI access · Frostbase @@ -66,9 +66,9 @@ Approve CLI access - The cloudflarebase CLI on this machine is asking to act as you. - Approving hands it your operator session - it shows up in the console's sessions list and can - be revoked there at any time. + The frostbase CLI on this machine is asking to act as you. Approving + hands it your operator session - it shows up in the console's sessions list and can be revoked + there at any time. @@ -85,8 +85,8 @@ {:else}

- This page is opened by cloudflarebase login and needs the port - and code it provides. Run the command again and use the link it prints. + This page is opened by frostbase login and needs the port and + code it provides. Run the command again and use the link it prints.

{/if}
diff --git a/src/routes/(app)/dashboard/(account)/+layout.svelte b/src/routes/(app)/dashboard/(account)/+layout.svelte index d711e0cd..703fed72 100644 --- a/src/routes/(app)/dashboard/(account)/+layout.svelte +++ b/src/routes/(app)/dashboard/(account)/+layout.svelte @@ -140,7 +140,7 @@ class="flex h-14 shrink-0 items-center gap-2 border-b border-border px-5 font-bold" > - Cloudflarebase + Frostbase {#if activeOrgEntry} @@ -218,7 +218,7 @@ - Cloudflarebase + Frostbase - +
@@ -92,7 +92,7 @@

Available now

- Complete Cloudflarebase primitives - each an isolated agent for this project. + Complete Frostbase primitives - each an isolated agent for this project.

diff --git a/src/routes/(app)/dashboard/[projectId]/api/+page.svelte b/src/routes/(app)/dashboard/[projectId]/api/+page.svelte index eedc8601..00bd304a 100644 --- a/src/routes/(app)/dashboard/[projectId]/api/+page.svelte +++ b/src/routes/(app)/dashboard/[projectId]/api/+page.svelte @@ -90,7 +90,7 @@ - API reference · {projectId} · Cloudflarebase + API reference · {projectId} · Frostbase
diff --git a/src/routes/(app)/dashboard/[projectId]/auth/[[tool=authtool]]/+page.svelte b/src/routes/(app)/dashboard/[projectId]/auth/[[tool=authtool]]/+page.svelte index 73814bf4..a6376f3b 100644 --- a/src/routes/(app)/dashboard/[projectId]/auth/[[tool=authtool]]/+page.svelte +++ b/src/routes/(app)/dashboard/[projectId]/auth/[[tool=authtool]]/+page.svelte @@ -807,7 +807,7 @@ - {data.projectId} · Authentication · Cloudflarebase + {data.projectId} · Authentication · Frostbase - {data.projectId} · Remote Config · Cloudflarebase + {data.projectId} · Remote Config · Frostbase console.log(JSON.parse(event.data)); - {data.projectId} · Database · Cloudflarebase + {data.projectId} · Database · Frostbase SELECT, INSERT, UPDATE, and DELETE against one declared table. DDL never runs here - declare and evolve schemas on the Tables page or with cloudflarebase schema applyfrostbase schema apply. diff --git a/src/routes/(app)/dashboard/[projectId]/settings/+page.svelte b/src/routes/(app)/dashboard/[projectId]/settings/+page.svelte index 400d91d9..416f38e0 100644 --- a/src/routes/(app)/dashboard/[projectId]/settings/+page.svelte +++ b/src/routes/(app)/dashboard/[projectId]/settings/+page.svelte @@ -151,7 +151,7 @@ - Settings · {data.projectId} · Cloudflarebase + Settings · {data.projectId} · Frostbase
diff --git a/src/routes/(app)/dashboard/[projectId]/storage/[[tool=storagetool]]/+page.svelte b/src/routes/(app)/dashboard/[projectId]/storage/[[tool=storagetool]]/+page.svelte index 50ea3651..5cd30e71 100644 --- a/src/routes/(app)/dashboard/[projectId]/storage/[[tool=storagetool]]/+page.svelte +++ b/src/routes/(app)/dashboard/[projectId]/storage/[[tool=storagetool]]/+page.svelte @@ -645,7 +645,7 @@ - {projectId} · Storage · Cloudflarebase + {projectId} · Storage · Frostbase {#snippet modeIcon(mode: StorageAccessMode)} diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte index 4f9362e5..c08eb886 100644 --- a/src/routes/(app)/login/+page.svelte +++ b/src/routes/(app)/login/+page.svelte @@ -289,7 +289,7 @@ ? 'Setup locked' : claiming ? 'Set up your console' - : 'Sign in'} · Cloudflarebase diff --git a/src/routes/(marketing)/(legal)/+layout.svelte b/src/routes/(marketing)/(legal)/+layout.svelte index 08e4ed83..a8b98be3 100644 --- a/src/routes/(marketing)/(legal)/+layout.svelte +++ b/src/routes/(marketing)/(legal)/+layout.svelte @@ -10,7 +10,7 @@
- Cloudflarebase + Frostbase
@@ -31,7 +31,7 @@
- © 2026 Cloudflarebase + © 2026 Frostbase