@dragonmastery/tamer 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/{StateManager-DTqtLLVX.mjs → StateManager-JLBtz9V-.mjs} +2 -2
- package/dist/{StateManager-DTqtLLVX.mjs.map → StateManager-JLBtz9V-.mjs.map} +1 -1
- package/dist/{apply-B0b_jjGv.mjs → apply-CWU3HY0P.mjs} +13 -13
- package/dist/{apply-B0b_jjGv.mjs.map → apply-CWU3HY0P.mjs.map} +1 -1
- package/dist/{applyTarget-BetDYdeS.mjs → applyTarget-D15T_q7G.mjs} +3 -3
- package/dist/{applyTarget-BetDYdeS.mjs.map → applyTarget-D15T_q7G.mjs.map} +1 -1
- package/dist/{bootstrap-CBzPilB1.mjs → bootstrap-BicPW44a.mjs} +3 -3
- package/dist/{bootstrap-CBzPilB1.mjs.map → bootstrap-BicPW44a.mjs.map} +1 -1
- package/dist/{cloudflareSnapshot-B4FOaNr0.mjs → cloudflareSnapshot-GBUHeg2m.mjs} +7 -7
- package/dist/{cloudflareSnapshot-B4FOaNr0.mjs.map → cloudflareSnapshot-GBUHeg2m.mjs.map} +1 -1
- package/dist/{deploy-gHEQxhmx.mjs → deploy-DAEjDjOm.mjs} +8 -8
- package/dist/{deploy-gHEQxhmx.mjs.map → deploy-DAEjDjOm.mjs.map} +1 -1
- package/dist/{destroy-B21f3wgq.mjs → destroy-DtgPD_bD.mjs} +11 -11
- package/dist/{destroy-B21f3wgq.mjs.map → destroy-DtgPD_bD.mjs.map} +1 -1
- package/dist/{destroy-tenant-BW2nasnK.mjs → destroy-tenant-B-VLKfc6.mjs} +3 -3
- package/dist/{destroy-tenant-BW2nasnK.mjs.map → destroy-tenant-B-VLKfc6.mjs.map} +1 -1
- package/dist/{dev-Dt26nzMJ.mjs → dev-BYItpt9U.mjs} +8 -8
- package/dist/{dev-Dt26nzMJ.mjs.map → dev-BYItpt9U.mjs.map} +1 -1
- package/dist/{dns-records.sync-Bpzz9H0s.mjs → dns-records.sync-CfI1mqXv.mjs} +2 -2
- package/dist/{dns-records.sync-Bpzz9H0s.mjs.map → dns-records.sync-CfI1mqXv.mjs.map} +1 -1
- package/dist/drift-DRnwTyZD.mjs +10 -0
- package/dist/{drift-D8ZrSgTn.mjs → drift-DncpkI2R.mjs} +8 -8
- package/dist/{drift-D8ZrSgTn.mjs.map → drift-DncpkI2R.mjs.map} +1 -1
- package/dist/{events-BSwGdkGj.mjs → events-B6oCdvSt.mjs} +3 -3
- package/dist/{events-BSwGdkGj.mjs.map → events-B6oCdvSt.mjs.map} +1 -1
- package/dist/{fetchStackImports-B4ZJahOt.mjs → fetchStackImports-ClUYZy_U.mjs} +294 -101
- package/dist/fetchStackImports-ClUYZy_U.mjs.map +1 -0
- package/dist/{generator-CIMbcPzv.mjs → generator-h_VG0Q5f.mjs} +6 -5
- package/dist/generator-h_VG0Q5f.mjs.map +1 -0
- package/dist/{import-BrduwA9Z.mjs → import-D8zaVvwK.mjs} +6 -4
- package/dist/import-D8zaVvwK.mjs.map +1 -0
- package/dist/index.d.mts +154 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{loader-DP7yXqT6.mjs → loader-DnT9iqz9.mjs} +17 -4
- package/dist/loader-DnT9iqz9.mjs.map +1 -0
- package/dist/{logpush-job-xS7270FZ.mjs → logpush-job-DsRkOORJ.mjs} +2 -2
- package/dist/{logpush-job-xS7270FZ.mjs.map → logpush-job-DsRkOORJ.mjs.map} +1 -1
- package/dist/{migrate-CahG6BYV.mjs → migrate-Bwl0w6XN.mjs} +5 -5
- package/dist/{migrate-CahG6BYV.mjs.map → migrate-Bwl0w6XN.mjs.map} +1 -1
- package/dist/{normalize-Bx0bpFop.mjs → normalize-DVSTRZhO.mjs} +18 -1
- package/dist/normalize-DVSTRZhO.mjs.map +1 -0
- package/dist/{plan-DWvsvy1U.mjs → plan-BNIAD--f.mjs} +11 -11
- package/dist/{plan-DWvsvy1U.mjs.map → plan-BNIAD--f.mjs.map} +1 -1
- package/dist/{provision-tenant-WTKo93Y0.mjs → provision-tenant-BcZocyyn.mjs} +4 -4
- package/dist/{provision-tenant-WTKo93Y0.mjs.map → provision-tenant-BcZocyyn.mjs.map} +1 -1
- package/dist/{stackOutputs-W9mnnJuj.mjs → stackOutputs-D33EmyfT.mjs} +3 -3
- package/dist/{stackOutputs-W9mnnJuj.mjs.map → stackOutputs-D33EmyfT.mjs.map} +1 -1
- package/dist/{status-DLwREPjb.mjs → status-BAPpi2Zt.mjs} +7 -7
- package/dist/{status-DLwREPjb.mjs.map → status-BAPpi2Zt.mjs.map} +1 -1
- package/dist/{sync-f2K2blwm.mjs → sync-BdJ43vO7.mjs} +8 -8
- package/dist/{sync-f2K2blwm.mjs.map → sync-BdJ43vO7.mjs.map} +1 -1
- package/dist/tamer.mjs +32 -18
- package/dist/tamer.mjs.map +1 -1
- package/dist/{types-CqxqYnrT.mjs → types-CN1BOr0U.mjs} +5 -5
- package/dist/{types-CqxqYnrT.mjs.map → types-CN1BOr0U.mjs.map} +1 -1
- package/dist/{verifyPlanFile-c16z1AMH.mjs → verifyPlanFile-BQ7GCDC2.mjs} +2 -2
- package/dist/{verifyPlanFile-c16z1AMH.mjs.map → verifyPlanFile-BQ7GCDC2.mjs.map} +1 -1
- package/dist/{wfp-delete-DysvX1u7.mjs → wfp-delete-BG9WBd7F.mjs} +2 -2
- package/dist/{wfp-delete-DysvX1u7.mjs.map → wfp-delete-BG9WBd7F.mjs.map} +1 -1
- package/dist/{wfp-put-jaVd_LjO.mjs → wfp-put-DjErqxFa.mjs} +2 -2
- package/dist/{wfp-put-jaVd_LjO.mjs.map → wfp-put-DjErqxFa.mjs.map} +1 -1
- package/dist/{worker-route-Be2IvOdr.mjs → worker-route-DY1onr-h.mjs} +3 -3
- package/dist/{worker-route-Be2IvOdr.mjs.map → worker-route-DY1onr-h.mjs.map} +1 -1
- package/dist/{workers-aGILs77X.mjs → workers-DNKsZOq4.mjs} +3 -3
- package/dist/{workers-aGILs77X.mjs.map → workers-DNKsZOq4.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/drift-D5qzCTft.mjs +0 -10
- package/dist/fetchStackImports-B4ZJahOt.mjs.map +0 -1
- package/dist/generator-CIMbcPzv.mjs.map +0 -1
- package/dist/import-BrduwA9Z.mjs.map +0 -1
- package/dist/loader-DP7yXqT6.mjs.map +0 -1
- package/dist/normalize-Bx0bpFop.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generator-CIMbcPzv.mjs","names":["out: GeneratedSecretsStoreSecretBinding[]","config: WranglerConfig"],"sources":["../src/features/secrets-store/secrets-store.generate.ts","../src/core/wrangler/generator.ts"],"sourcesContent":["import type { NamingEngine } from \"../../core/naming/NamingEngine.js\";\nimport type {\n StateEntry,\n SecretsStoreSecretBinding,\n SecretsStoreStateEntry,\n} from \"../../types.js\";\n\nexport interface SecretsStoreStateLike {\n get(key: string): StateEntry | undefined;\n getAll(): Record<string, StateEntry>;\n}\n\n/**\n * One row of wrangler's `secrets_store_secrets[]` array. Resolved from a\n * worker's declared `secretsStoreSecrets[]` by looking up the\n * Tamer-managed store's `store_id` in state. The named secret itself must\n * already exist inside the store (created out-of-band via wrangler\n * `secrets-store secret create`); Tamer never reads or writes secret\n * values.\n */\nexport interface GeneratedSecretsStoreSecretBinding {\n binding: string;\n store_id: string;\n secret_name: string;\n}\n\n/**\n * Resolve `secretsStoreSecrets[]` worker bindings into wrangler-shape\n * `secrets_store_secrets[]` entries by looking up the referenced\n * Tamer-managed store's `store_id` in state. Throws with an actionable\n * message if a referenced store has never been applied — the binding\n * would otherwise be deployed against a non-existent store.\n */\nexport function secretsStoreSecretsGenerate(\n bindings: SecretsStoreSecretBinding[],\n env: string,\n state: SecretsStoreStateLike,\n naming: NamingEngine,\n): GeneratedSecretsStoreSecretBinding[] {\n const out: GeneratedSecretsStoreSecretBinding[] = [];\n for (const b of bindings) {\n const derivedName = naming.secretsStoreName(b.store, env);\n const entry = state.get(`secrets_store:${derivedName}`) as\n | SecretsStoreStateEntry\n | undefined;\n if (!entry || entry.type !== \"secrets_store\") {\n throw new Error(\n `Secrets Store binding \"${b.binding}\" references store \"${b.store}\" — ` +\n `not in state. Declare it under \\`resources.secretsStores\\` and run ` +\n `'tamer apply --env ${env}' first.`,\n );\n }\n out.push({\n binding: b.binding,\n store_id: entry.cfId,\n secret_name: b.secretName,\n });\n }\n return out;\n}\n","import { writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport type { WranglerConfig } from \"../../generated/wrangler-types.js\";\nimport type { ResolvedWorkerConfig } from \"../config/resolver.js\";\nimport type { StateManager } from \"../state/StateManager.js\";\nimport type { NamingEngine } from \"../naming/NamingEngine.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport { secretsStoreSecretsGenerate } from \"../../features/secrets-store/secrets-store.generate.js\";\n\nexport class CfiError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CfiError\";\n }\n}\n\n/**\n * Build the worker's `wrangler.json` by asking every registered resource\n * module for its bindings fragment, then merging on top of the\n * passthrough config (`resolved.wranglerConfig`). Adding a new resource\n * kind requires zero edits here — register the module, done.\n */\nexport function generateWranglerConfig(\n resolved: ResolvedWorkerConfig,\n state: StateManager,\n naming: NamingEngine,\n): WranglerConfig {\n let config: WranglerConfig = {\n ...resolved.wranglerConfig,\n $schema: \"node_modules/wrangler/config-schema.json\",\n };\n\n const worker = {\n workerKey: resolved.workerKey,\n deployedName: resolved.workerName,\n };\n for (const mod of resourceModules) {\n const resources = mod.pickResources(resolved.resources);\n const fragment = mod.generate({\n resources,\n env: resolved.env,\n state,\n naming,\n passthrough: resolved.wranglerConfig,\n worker,\n });\n config = { ...config, ...fragment };\n }\n\n const secretBindings = resolved.resources?.secretsStoreSecrets ?? [];\n if (secretBindings.length > 0) {\n const passthroughSecrets =\n resolved.wranglerConfig.secrets_store_secrets ?? [];\n const generated = secretsStoreSecretsGenerate(\n secretBindings,\n resolved.env,\n state,\n naming,\n );\n config = {\n ...config,\n secrets_store_secrets: [...passthroughSecrets, ...generated],\n };\n }\n\n return config;\n}\n\nexport function writeWranglerJson(\n workerDir: string,\n config: WranglerConfig,\n outFile: string = \"wrangler.json\",\n): void {\n const path = join(workerDir, outFile);\n writeFileSync(path, JSON.stringify(config, null, 2), \"utf-8\");\n}\n"],"mappings":";;;;;;;;;;;;AAiCA,SAAgB,4BACd,UACA,KACA,OACA,QACsC;CACtC,MAAMA,MAA4C,EAAE;AACpD,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,cAAc,OAAO,iBAAiB,EAAE,OAAO,IAAI;EACzD,MAAM,QAAQ,MAAM,IAAI,iBAAiB,cAAc;AAGvD,MAAI,CAAC,SAAS,MAAM,SAAS,gBAC3B,OAAM,IAAI,MACR,0BAA0B,EAAE,QAAQ,sBAAsB,EAAE,MAAM,4FAE1C,IAAI,UAC7B;AAEH,MAAI,KAAK;GACP,SAAS,EAAE;GACX,UAAU,MAAM;GAChB,aAAa,EAAE;GAChB,CAAC;;AAEJ,QAAO;;;;;;;;;;;ACpCT,SAAgB,uBACd,UACA,OACA,QACgB;CAChB,IAAIC,SAAyB;EAC3B,GAAG,SAAS;EACZ,SAAS;EACV;CAED,MAAM,SAAS;EACb,WAAW,SAAS;EACpB,cAAc,SAAS;EACxB;AACD,MAAK,MAAM,OAAO,iBAAiB;EACjC,MAAM,YAAY,IAAI,cAAc,SAAS,UAAU;EACvD,MAAM,WAAW,IAAI,SAAS;GAC5B;GACA,KAAK,SAAS;GACd;GACA;GACA,aAAa,SAAS;GACtB;GACD,CAAC;AACF,WAAS;GAAE,GAAG;GAAQ,GAAG;GAAU;;CAGrC,MAAM,iBAAiB,SAAS,WAAW,uBAAuB,EAAE;AACpE,KAAI,eAAe,SAAS,GAAG;EAC7B,MAAM,qBACJ,SAAS,eAAe,yBAAyB,EAAE;EACrD,MAAM,YAAY,4BAChB,gBACA,SAAS,KACT,OACA,OACD;AACD,WAAS;GACP,GAAG;GACH,uBAAuB,CAAC,GAAG,oBAAoB,GAAG,UAAU;GAC7D;;AAGH,QAAO;;AAGT,SAAgB,kBACd,WACA,QACA,UAAkB,iBACZ;AAEN,eADa,KAAK,WAAW,QAAQ,EACjB,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"import-BrduwA9Z.mjs","names":[],"sources":["../src/cli/commands/import.ts"],"sourcesContent":["import { loadConfig, getWorkers } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { resolveWorkerConfig } from \"../../core/config/resolver.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\nimport { workerRouteStateKey } from \"../../features/worker-route/worker-route.stateKey.js\";\nimport { findZoneIdByName } from \"../../core/routes/zoneResolver.js\";\nimport {\n getResourceModule,\n resourceModules,\n type ResourceKind,\n} from \"../../core/registry/registry.js\";\n\n/** All registered resource kinds plus the non-registry imports. */\nexport type ImportKind =\n | ResourceKind\n | \"dispatch_namespace\"\n | \"dns_record\"\n | \"worker_route\";\n\nexport interface ImportOptions {\n env: string;\n kind: ImportKind;\n /** Logical name from `tamer.config.ts` (or worker key, for `worker_route`). */\n logical: string;\n /** Cloudflare resource id (D1 uuid, KV id, R2 bucket name, or namespace name). */\n cfId?: string;\n /** Required for sharded D1 imports (YYYY-MM-DD). */\n shardDate?: string;\n /** R2 buckets only: creation date (YYYY-MM-DD) used in the binding key. */\n createdDate?: string;\n /** worker_route only: the route id from `/zones/{id}/workers/routes`. */\n routeId?: string;\n /** worker_route only: zone name from `tamerRoutes`. */\n zoneName?: string;\n configPath?: string;\n}\n\nconst REGISTRY_KINDS = new Set<string>(resourceModules.map((m) => m.kind));\n\n/**\n * `tamer import` — register an existing Cloudflare resource into Tamer state\n * by logical name (CloudFormation `import_resources` analogue). For all\n * resource-registry kinds the work is delegated to the module's\n * `importOne` hook; `dispatch_namespace` and `worker_route` remain inline\n * because they aren't registry resources today.\n */\nexport async function runImport(options: ImportOptions): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\"import: --env local is not supported (no persisted state)\");\n }\n const baseDir = process.cwd();\n const config = await loadConfig(options.configPath, { env });\n const accountId = config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n const api = new CFApiClient(accountId);\n const naming = namingFromConfig(config);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n state.beginOperation(\"import\", `${options.kind}:${options.logical}`);\n\n const ts = new Date().toISOString();\n\n try {\n if (REGISTRY_KINDS.has(options.kind)) {\n const mod = getResourceModule(options.kind as ResourceKind);\n if (!mod) throw new Error(`unknown import kind \"${options.kind}\"`);\n await mod.importOne({\n options: {\n env,\n kind: options.kind,\n logical: options.logical,\n cfId: options.cfId,\n shardDate: options.shardDate,\n createdDate: options.createdDate,\n routeId: options.routeId,\n zoneName: options.zoneName,\n },\n env,\n api,\n state,\n naming,\n ts,\n });\n } else if (options.kind === \"dispatch_namespace\") {\n await importDispatchNamespace({ options, env, api, state, ts });\n } else if (options.kind === \"dns_record\") {\n await importDnsRecord({ options, env, api, state, config, ts });\n } else if (options.kind === \"worker_route\") {\n await importWorkerRoute({\n options,\n env,\n baseDir,\n accountId,\n api,\n naming,\n state,\n config,\n ts,\n });\n } else {\n throw new Error(`unknown import kind \"${options.kind}\"`);\n }\n\n state.finishOperation(\"imported\");\n await state.persist(api);\n console.log(`Imported ${options.kind} \"${options.logical}\" into ${env} state.`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n state.failOperation(msg);\n try {\n await state.persist(api);\n } catch {\n /* swallow secondary persist failure */\n }\n throw err;\n }\n}\n\nasync function importDispatchNamespace(ctx: {\n options: ImportOptions;\n env: string;\n api: CFApiClient;\n state: StateManager;\n ts: string;\n}): Promise<void> {\n const { options, api, state, ts } = ctx;\n const namespaces = await api.dispatchNamespaceListAll();\n const hit = namespaces.find((n) => n.namespace_name === options.cfId);\n if (!hit) {\n throw new Error(\n `import dispatch_namespace: namespace \"${options.cfId}\" not found`,\n );\n }\n const derivedName = hit.namespace_name;\n const key = `dispatch_ns:${derivedName}`;\n const existing = state.get(key);\n state.set(key, {\n type: \"dispatch_namespace\",\n logicalName: options.logical,\n derivedName,\n createdAt:\n existing?.type === \"dispatch_namespace\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n\nasync function importDnsRecord(ctx: {\n options: ImportOptions;\n env: string;\n api: CFApiClient;\n state: StateManager;\n config: Awaited<ReturnType<typeof loadConfig>>;\n ts: string;\n}): Promise<void> {\n const { options, api, state, config, ts } = ctx;\n if (!options.cfId) {\n throw new Error(\n \"import dns_record: --cf-id <recordId> is required (the record id from `tamer drift` or the Cloudflare dashboard URL)\",\n );\n }\n const declared = (config.dnsRecords ?? []).find(\n (r) => r.logicalName === options.logical,\n );\n if (!declared) {\n throw new Error(\n `import dns_record: no dnsRecords entry with logicalName \"${options.logical}\" in the Tamer project config`,\n );\n }\n const list = await api.zoneDnsRecordListAll(declared.zoneId);\n const hit = list.find((r) => r.id === options.cfId);\n if (!hit) {\n throw new Error(\n `import dns_record: record id \"${options.cfId}\" not found in zone \"${declared.zoneId}\"`,\n );\n }\n if (hit.type !== declared.type) {\n throw new Error(\n `import dns_record: cf record type \"${hit.type}\" does not match declared type \"${declared.type}\"`,\n );\n }\n const { dnsRecordStateKey } = await import(\n \"../../features/dns-records/dns-records.resolve.js\"\n );\n const key = dnsRecordStateKey(declared.zoneId, declared.type, declared.name);\n const existing = state.get(key);\n state.set(key, {\n type: \"dns_record\",\n logicalName: options.logical,\n zoneId: declared.zoneId,\n recordType: declared.type,\n name: hit.name,\n content: hit.content,\n ttl: hit.ttl ?? 1,\n proxied: hit.proxied ?? false,\n priority: hit.priority,\n comment: hit.comment ?? \"\",\n recordId: hit.id,\n createdAt: existing?.type === \"dns_record\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n\nasync function importWorkerRoute(ctx: {\n options: ImportOptions;\n env: string;\n baseDir: string;\n accountId: string;\n api: CFApiClient;\n naming: ReturnType<typeof namingFromConfig>;\n state: StateManager;\n config: Awaited<ReturnType<typeof loadConfig>>;\n ts: string;\n}): Promise<void> {\n const { options, env, baseDir, accountId, api, naming, state, config, ts } =\n ctx;\n if (!options.routeId) {\n throw new Error(\"import worker_route: --route-id <id> is required\");\n }\n const workers = await getWorkers(config, baseDir);\n const wc = workers.find(([k]) => k === options.logical)?.[1];\n if (!wc) {\n throw new Error(\n `import worker_route: no worker key \"${options.logical}\" in the Tamer project config`,\n );\n }\n const imports = await fetchStackImports(api, config, env);\n const resolved = await resolveWorkerConfig(\n config,\n options.logical,\n wc,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { imports },\n );\n if (resolved.apiManagedRoutes.length === 0) {\n throw new Error(\n `import worker_route: worker \"${options.logical}\" declares no api-managed tamerRoutes for env ${env}`,\n );\n }\n\n const route = options.zoneName\n ? resolved.apiManagedRoutes.find((r) => r.zone_name === options.zoneName)\n : resolved.apiManagedRoutes[0];\n if (!route) {\n throw new Error(\n `import worker_route: no route for zone \"${options.zoneName}\" in resolved config`,\n );\n }\n const zoneId = await findZoneIdByName(api, route.zone_name);\n if (!zoneId) {\n throw new Error(`import worker_route: zone \"${route.zone_name}\" not found`);\n }\n const list = await api.zoneWorkerRoutesList(zoneId);\n const hit = list.find((r) => r.id === options.routeId);\n if (!hit) {\n throw new Error(\n `import worker_route: route id \"${options.routeId}\" not found in zone \"${route.zone_name}\"`,\n );\n }\n if (hit.pattern !== route.pattern) {\n throw new Error(\n `import worker_route: cf pattern \"${hit.pattern}\" does not match resolved \"${route.pattern}\"`,\n );\n }\n const key = workerRouteStateKey(zoneId, options.routeId);\n const existing = state.get(key);\n state.set(key, {\n type: \"worker_route\",\n workerKey: options.logical,\n workerName: resolved.workerName,\n zoneId,\n zoneName: route.zone_name,\n routeId: options.routeId,\n pattern: route.pattern,\n createdAt: existing?.type === \"worker_route\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n"],"mappings":";;;;;;;;AAyCA,MAAM,iBAAiB,IAAI,IAAY,gBAAgB,KAAK,MAAM,EAAE,KAAK,CAAC;;;;;;;;AAS1E,eAAsB,UAAU,SAAuC;CACrE,MAAM,MAAM,QAAQ;AACpB,KAAI,QAAQ,QACV,OAAM,IAAI,MAAM,4DAA4D;CAE9E,MAAM,UAAU,QAAQ,KAAK;CAC7B,MAAM,SAAS,MAAM,WAAW,QAAQ,YAAY,EAAE,KAAK,CAAC;CAC5D,MAAM,YAAY,OAAO,cAAc,4BAA4B;AACnE,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAEH,MAAM,MAAM,IAAI,YAAY,UAAU;CACtC,MAAM,SAAS,iBAAiB,OAAO;CACvC,MAAM,QAAQ,IAAI,aAChB,OAAO,OAAO,IACd,KACA,mBAAmB,OAAO,CAC3B;AACD,OAAM,MAAM,QAAQ,IAAI;AACxB,OAAM,eAAe,UAAU,GAAG,QAAQ,KAAK,GAAG,QAAQ,UAAU;CAEpE,MAAM,sBAAK,IAAI,MAAM,EAAC,aAAa;AAEnC,KAAI;AACF,MAAI,eAAe,IAAI,QAAQ,KAAK,EAAE;GACpC,MAAM,MAAM,kBAAkB,QAAQ,KAAqB;AAC3D,OAAI,CAAC,IAAK,OAAM,IAAI,MAAM,wBAAwB,QAAQ,KAAK,GAAG;AAClE,SAAM,IAAI,UAAU;IAClB,SAAS;KACP;KACA,MAAM,QAAQ;KACd,SAAS,QAAQ;KACjB,MAAM,QAAQ;KACd,WAAW,QAAQ;KACnB,aAAa,QAAQ;KACrB,SAAS,QAAQ;KACjB,UAAU,QAAQ;KACnB;IACD;IACA;IACA;IACA;IACA;IACD,CAAC;aACO,QAAQ,SAAS,qBAC1B,OAAM,wBAAwB;GAAE;GAAS;GAAK;GAAK;GAAO;GAAI,CAAC;WACtD,QAAQ,SAAS,aAC1B,OAAM,gBAAgB;GAAE;GAAS;GAAK;GAAK;GAAO;GAAQ;GAAI,CAAC;WACtD,QAAQ,SAAS,eAC1B,OAAM,kBAAkB;GACtB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;MAEF,OAAM,IAAI,MAAM,wBAAwB,QAAQ,KAAK,GAAG;AAG1D,QAAM,gBAAgB,WAAW;AACjC,QAAM,MAAM,QAAQ,IAAI;AACxB,UAAQ,IAAI,YAAY,QAAQ,KAAK,IAAI,QAAQ,QAAQ,SAAS,IAAI,SAAS;UACxE,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,QAAM,cAAc,IAAI;AACxB,MAAI;AACF,SAAM,MAAM,QAAQ,IAAI;UAClB;AAGR,QAAM;;;AAIV,eAAe,wBAAwB,KAMrB;CAChB,MAAM,EAAE,SAAS,KAAK,OAAO,OAAO;CAEpC,MAAM,OADa,MAAM,IAAI,0BAA0B,EAChC,MAAM,MAAM,EAAE,mBAAmB,QAAQ,KAAK;AACrE,KAAI,CAAC,IACH,OAAM,IAAI,MACR,yCAAyC,QAAQ,KAAK,aACvD;CAEH,MAAM,cAAc,IAAI;CACxB,MAAM,MAAM,eAAe;CAC3B,MAAM,WAAW,MAAM,IAAI,IAAI;AAC/B,OAAM,IAAI,KAAK;EACb,MAAM;EACN,aAAa,QAAQ;EACrB;EACA,WACE,UAAU,SAAS,uBAAuB,SAAS,YAAY;EACjE,WAAW;EACZ,CAAC;;AAGJ,eAAe,gBAAgB,KAOb;CAChB,MAAM,EAAE,SAAS,KAAK,OAAO,QAAQ,OAAO;AAC5C,KAAI,CAAC,QAAQ,KACX,OAAM,IAAI,MACR,uHACD;CAEH,MAAM,YAAY,OAAO,cAAc,EAAE,EAAE,MACxC,MAAM,EAAE,gBAAgB,QAAQ,QAClC;AACD,KAAI,CAAC,SACH,OAAM,IAAI,MACR,4DAA4D,QAAQ,QAAQ,+BAC7E;CAGH,MAAM,OADO,MAAM,IAAI,qBAAqB,SAAS,OAAO,EAC3C,MAAM,MAAM,EAAE,OAAO,QAAQ,KAAK;AACnD,KAAI,CAAC,IACH,OAAM,IAAI,MACR,iCAAiC,QAAQ,KAAK,uBAAuB,SAAS,OAAO,GACtF;AAEH,KAAI,IAAI,SAAS,SAAS,KACxB,OAAM,IAAI,MACR,sCAAsC,IAAI,KAAK,kCAAkC,SAAS,KAAK,GAChG;CAEH,MAAM,EAAE,sBAAsB,MAAM,OAClC;CAEF,MAAM,MAAM,kBAAkB,SAAS,QAAQ,SAAS,MAAM,SAAS,KAAK;CAC5E,MAAM,WAAW,MAAM,IAAI,IAAI;AAC/B,OAAM,IAAI,KAAK;EACb,MAAM;EACN,aAAa,QAAQ;EACrB,QAAQ,SAAS;EACjB,YAAY,SAAS;EACrB,MAAM,IAAI;EACV,SAAS,IAAI;EACb,KAAK,IAAI,OAAO;EAChB,SAAS,IAAI,WAAW;EACxB,UAAU,IAAI;EACd,SAAS,IAAI,WAAW;EACxB,UAAU,IAAI;EACd,WAAW,UAAU,SAAS,eAAe,SAAS,YAAY;EAClE,WAAW;EACZ,CAAC;;AAGJ,eAAe,kBAAkB,KAUf;CAChB,MAAM,EAAE,SAAS,KAAK,SAAS,WAAW,KAAK,QAAQ,OAAO,QAAQ,OACpE;AACF,KAAI,CAAC,QAAQ,QACX,OAAM,IAAI,MAAM,mDAAmD;CAGrE,MAAM,MADU,MAAM,WAAW,QAAQ,QAAQ,EAC9B,MAAM,CAAC,OAAO,MAAM,QAAQ,QAAQ,GAAG;AAC1D,KAAI,CAAC,GACH,OAAM,IAAI,MACR,uCAAuC,QAAQ,QAAQ,+BACxD;CAEH,MAAM,UAAU,MAAM,kBAAkB,KAAK,QAAQ,IAAI;CACzD,MAAM,WAAW,MAAM,oBACrB,QACA,QAAQ,SACR,IACA,KACA,SACA,WACA,QACA,OACA,EAAE,SAAS,CACZ;AACD,KAAI,SAAS,iBAAiB,WAAW,EACvC,OAAM,IAAI,MACR,gCAAgC,QAAQ,QAAQ,gDAAgD,MACjG;CAGH,MAAM,QAAQ,QAAQ,WAClB,SAAS,iBAAiB,MAAM,MAAM,EAAE,cAAc,QAAQ,SAAS,GACvE,SAAS,iBAAiB;AAC9B,KAAI,CAAC,MACH,OAAM,IAAI,MACR,2CAA2C,QAAQ,SAAS,sBAC7D;CAEH,MAAM,SAAS,MAAM,iBAAiB,KAAK,MAAM,UAAU;AAC3D,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,8BAA8B,MAAM,UAAU,aAAa;CAG7E,MAAM,OADO,MAAM,IAAI,qBAAqB,OAAO,EAClC,MAAM,MAAM,EAAE,OAAO,QAAQ,QAAQ;AACtD,KAAI,CAAC,IACH,OAAM,IAAI,MACR,kCAAkC,QAAQ,QAAQ,uBAAuB,MAAM,UAAU,GAC1F;AAEH,KAAI,IAAI,YAAY,MAAM,QACxB,OAAM,IAAI,MACR,oCAAoC,IAAI,QAAQ,6BAA6B,MAAM,QAAQ,GAC5F;CAEH,MAAM,MAAM,oBAAoB,QAAQ,QAAQ,QAAQ;CACxD,MAAM,WAAW,MAAM,IAAI,IAAI;AAC/B,OAAM,IAAI,KAAK;EACb,MAAM;EACN,WAAW,QAAQ;EACnB,YAAY,SAAS;EACrB;EACA,UAAU,MAAM;EAChB,SAAS,QAAQ;EACjB,SAAS,MAAM;EACf,WAAW,UAAU,SAAS,iBAAiB,SAAS,YAAY;EACpE,WAAW;EACZ,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loader-DP7yXqT6.mjs","names":["out: Record<string, unknown>","overlay: string | null","z.object","z.string","z\n .array","z\n .string","z.array","z\n .string","z\n .object","z.enum","z.boolean","z.union","z.number","z\n .object","z.literal","z\n .enum","z.record","z\n .union","z.any","z\n .object","z\n .string","z\n .record","raw: unknown","entries: [string, WorkerConfig][]"],"sources":["../src/core/config/merge-project-overlay.ts","../src/core/config/resolve-config-sources.ts","../src/core/config/loader.ts"],"sourcesContent":["/**\n * Deep-merge `tamer/env/<env>.config.ts` onto `tamer/project.config.ts`.\n * See docs/design-tamer-project-config.md.\n */\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return (\n v !== null &&\n typeof v === \"object\" &&\n !Array.isArray(v) &&\n !(v instanceof Date)\n );\n}\n\nfunction validateOverlayWorkers(\n projectWorkers: Record<string, unknown>,\n overlayWorkers: Record<string, unknown>,\n): void {\n for (const k of Object.keys(overlayWorkers)) {\n if (!(k in projectWorkers)) {\n throw new Error(\n `Tamer env overlay: unknown worker key \"${k}\". ` +\n `Declare \"${k}\" in tamer/project.config.ts first — overlays may only patch existing workers.`,\n );\n }\n }\n}\n\nfunction mergeWorkerResources(\n base: Record<string, unknown>,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const out = { ...base };\n for (const k of Object.keys(patch)) {\n const pk = patch[k];\n if (pk !== undefined) {\n out[k] = pk;\n }\n }\n return out;\n}\n\nfunction mergeEnvMap(\n base: Record<string, unknown>,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const out = { ...base };\n for (const k of Object.keys(patch)) {\n const b = base[k];\n const p = patch[k];\n if (isPlainObject(b) && isPlainObject(p)) {\n out[k] = mergeWorkerConfig(b, p);\n } else {\n out[k] = p;\n }\n }\n return out;\n}\n\nfunction mergeWorkerConfig(\n base: Record<string, unknown>,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = { ...base };\n for (const key of Object.keys(patch)) {\n const pv = patch[key];\n if (pv === undefined) continue;\n\n if (key === \"vars\") {\n const bv = isPlainObject(base.vars) ? base.vars : {};\n const ov = isPlainObject(patch.vars) ? patch.vars : {};\n out.vars = { ...bv, ...ov };\n continue;\n }\n if (key === \"tamerRoutes\") {\n out.tamerRoutes = pv;\n continue;\n }\n if (key === \"tamerStaleRouteSweepZones\") {\n out.tamerStaleRouteSweepZones = pv;\n continue;\n }\n if (key === \"resources\") {\n if (isPlainObject(base.resources) && isPlainObject(pv)) {\n out.resources = mergeWorkerResources(base.resources, pv);\n } else {\n out.resources = pv;\n }\n continue;\n }\n if (key === \"env\") {\n if (isPlainObject(base.env) && isPlainObject(pv)) {\n out.env = mergeEnvMap(base.env, pv);\n } else {\n out.env = pv;\n }\n continue;\n }\n if (key === \"local\") {\n if (isPlainObject(base.local) && isPlainObject(pv)) {\n out.local = mergeWorkerConfig(\n base.local as Record<string, unknown>,\n pv as Record<string, unknown>,\n );\n } else {\n out.local = pv;\n }\n continue;\n }\n out[key] = pv;\n }\n return out;\n}\n\nfunction mergeWorkersRecord(\n base: Record<string, unknown>,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const out = { ...base };\n for (const k of Object.keys(patch)) {\n const b = base[k];\n const p = patch[k];\n if (isPlainObject(b) && isPlainObject(p)) {\n out[k] = mergeWorkerConfig(b, p);\n } else {\n out[k] = p;\n }\n }\n return out;\n}\n\n/**\n * Merge env overlay onto project config (both plain objects from `default` exports).\n * Does not run {@link materializeCloudflareBindings} — caller does that on the result.\n */\nexport function mergeProjectOverlay(\n project: Record<string, unknown>,\n overlay: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = { ...project };\n for (const key of Object.keys(overlay)) {\n const ov = overlay[key];\n if (ov === undefined) continue;\n const pv = project[key];\n\n switch (key) {\n case \"tenant\":\n if (isPlainObject(pv) && isPlainObject(ov)) {\n out[key] = { ...pv, ...ov };\n } else {\n out[key] = ov;\n }\n break;\n case \"naming\":\n out[key] = ov;\n break;\n case \"workers\":\n if (!isPlainObject(pv) || !isPlainObject(ov)) {\n out[key] = ov;\n break;\n }\n validateOverlayWorkers(pv, ov);\n out[key] = mergeWorkersRecord(pv, ov);\n break;\n case \"worker\":\n if (isPlainObject(pv) && isPlainObject(ov)) {\n out[key] = mergeWorkerConfig(pv, ov);\n } else {\n out[key] = ov;\n }\n break;\n case \"outputs\":\n if (isPlainObject(pv) && isPlainObject(ov)) {\n out[key] = { ...pv, ...ov };\n } else {\n out[key] = ov;\n }\n break;\n case \"logpushJobs\":\n case \"dispatchNamespaces\":\n case \"dnsRecords\":\n out[key] = ov;\n break;\n default:\n out[key] = ov;\n }\n }\n return out;\n}\n","import { existsSync } from \"fs\";\nimport { basename, dirname, join, resolve } from \"path\";\n\nexport type ResolvedConfigSources =\n | { mode: \"single\"; path: string }\n | { mode: \"merged\"; projectPath: string; overlayPath: string | null };\n\nfunction isProjectFileName(file: string): boolean {\n return file === \"project.config.ts\" || file === \"tamer.project.config.ts\";\n}\n\nfunction projectDirForMergedEntry(absProject: string): string {\n const dir = dirname(absProject);\n const file = basename(absProject);\n if (file === \"project.config.ts\") {\n return dir;\n }\n return dir;\n}\n\nfunction overlayPathForNestedLayout(projectDir: string, env: string): string {\n return resolve(projectDir, \"env\", `${env}.config.ts`);\n}\n\nfunction overlayPathForFlatLayout(projectRoot: string, env: string): string {\n return resolve(projectRoot, `tamer.env.${env}.ts`);\n}\n\nfunction rejectLegacyTamerConfigFile(context: string): never {\n throw new Error(\n `${context}: tamer.config.ts is not supported. ` +\n `Move the default export to tamer/project.config.ts and optional per-env overlays to tamer/env/<env>.config.ts ` +\n `(or use tamer.project.config.ts at the repo root with optional tamer.env.<env>.ts).`,\n );\n}\n\n/**\n * Resolve which config file(s) to load. See docs/design-tamer-project-config.md.\n *\n * - **Merged:** `tamer/project.config.ts` or `tamer.project.config.ts`, plus optional env overlay.\n * - **Single:** explicit `--config` path to any `.ts` file **except** `tamer.config.ts` (e.g. alternate snapshots in tests).\n */\nexport function resolveConfigSources(\n cwd: string,\n explicitConfigPath: string | undefined,\n env: string | undefined,\n): ResolvedConfigSources {\n if (explicitConfigPath) {\n const abs = resolve(cwd, explicitConfigPath);\n const file = basename(abs);\n if (file === \"tamer.config.ts\") {\n rejectLegacyTamerConfigFile(\"--config\");\n }\n if (isProjectFileName(file)) {\n const projectDir = projectDirForMergedEntry(abs);\n let overlay: string | null = null;\n if (env) {\n if (file === \"project.config.ts\") {\n const candidate = overlayPathForNestedLayout(projectDir, env);\n if (existsSync(candidate)) overlay = candidate;\n } else {\n const candidate = overlayPathForFlatLayout(dirname(abs), env);\n if (existsSync(candidate)) overlay = candidate;\n }\n }\n return { mode: \"merged\", projectPath: abs, overlayPath: overlay };\n }\n return { mode: \"single\", path: abs };\n }\n\n const nestedProject = resolve(cwd, \"tamer\", \"project.config.ts\");\n if (existsSync(nestedProject)) {\n let overlay: string | null = null;\n if (env) {\n const candidate = overlayPathForNestedLayout(\n dirname(nestedProject),\n env,\n );\n if (existsSync(candidate)) overlay = candidate;\n }\n return {\n mode: \"merged\",\n projectPath: nestedProject,\n overlayPath: overlay,\n };\n }\n\n const flatProject = resolve(cwd, \"tamer.project.config.ts\");\n if (existsSync(flatProject)) {\n let overlay: string | null = null;\n if (env) {\n const candidate = overlayPathForFlatLayout(cwd, env);\n if (existsSync(candidate)) overlay = candidate;\n }\n return {\n mode: \"merged\",\n projectPath: flatProject,\n overlayPath: overlay,\n };\n }\n\n if (existsSync(resolve(cwd, \"tamer.config.ts\"))) {\n rejectLegacyTamerConfigFile(\"Config discovery\");\n }\n\n throw new Error(\n `No Tamer project config under ${cwd}. ` +\n `Create tamer/project.config.ts (or tamer.project.config.ts at the repo root).`,\n );\n}\n","import { resolve } from \"path\";\nimport { z } from \"zod\";\nimport { materializeCloudflareBindings } from \"../../dx/normalize.js\";\nimport type { CfiConfig, WorkerConfig } from \"../../types.js\";\nimport { TAMER_OVERLAY_ENV_KEY } from \"../../types.js\";\nimport { mergeProjectOverlay } from \"./merge-project-overlay.js\";\nimport { resolveConfigSources } from \"./resolve-config-sources.js\";\n\nconst TENANT_SHARD_ROLE_RE = /^[a-z][a-z0-9_-]*$/;\n\nconst TenantMetaSchema = z.object({\n id: z.string().min(1),\n name: z.string().min(1),\n slug: z.string().min(1),\n d1Shards: z\n .array(\n z\n .string()\n .min(1)\n .refine((s) => TENANT_SHARD_ROLE_RE.test(s), {\n // Roles flow into D1 database names (`db_{role}_…`); D1 names\n // accept `[A-Za-z0-9_-]` and Cloudflare lowercases on read,\n // so we lock roles to the same lowercase subset to avoid the\n // \"config says `System` but Cloudflare returns `system`\"\n // adoption drift.\n error:\n \"tenant.d1Shards entries must be lowercase ASCII (letters, digits, `_`, `-`) and start with a letter\",\n }),\n )\n .optional()\n .refine(\n (arr) => !arr || new Set(arr).size === arr.length,\n \"tenant.d1Shards must not contain duplicate roles\",\n ),\n protectedEnvs: z.array(z.string().min(1)).optional(),\n ephemeralEnvPattern: z\n .string()\n .min(1)\n .optional()\n .refine(\n (s) => {\n if (s == null) return true;\n try {\n new RegExp(s);\n return true;\n } catch {\n return false;\n }\n },\n // Compile-fail at config-load time so a typo in\n // `ephemeralEnvPattern` surfaces here instead of later when\n // `apply` / `provision-tenant` tries to resolve a namespace.\n \"tenant.ephemeralEnvPattern must be a valid JavaScript RegExp source string\",\n ),\n});\n\nconst D1ResourceConfigSchema = z\n .object({\n logicalName: z.string().min(1),\n type: z.enum([\"single\", \"sharded\"]),\n ownership: z.enum([\"managed\", \"external\"]).optional(),\n databaseName: z.string().optional(),\n binding: z.string().optional(),\n migrationsDir: z.string().optional(),\n migrationsTable: z.string().optional(),\n preserveOnDestroy: z.boolean().optional(),\n })\n .refine(\n (d) =>\n d.ownership !== \"external\" ||\n (typeof d.databaseName === \"string\" && d.databaseName.length > 0),\n { error: \"resources.d1: ownership 'external' requires non-empty databaseName\" },\n )\n .refine(\n (d) => d.ownership !== \"external\" || d.type === \"single\",\n { error: \"resources.d1: ownership 'external' only supports type 'single'\" },\n );\n\nconst R2ResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n binding: z.string().optional(),\n});\n\nconst KVResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n binding: z.string().optional(),\n});\n\nconst QueueResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n binding: z.string().optional(),\n consumerOnly: z.boolean().optional(),\n});\n\nconst HyperdriveSecretSchema = z.union([\n z.string(),\n z.object({ fromEnv: z.string().min(1) }),\n]);\n\nconst HyperdriveOriginSchema = z.object({\n scheme: z.enum([\"postgres\", \"postgresql\", \"mysql\"]),\n host: z.string().min(1),\n port: z.number().optional(),\n database: z.string().min(1),\n user: z.string().min(1),\n password: HyperdriveSecretSchema,\n access_client_id: z.string().optional(),\n access_client_secret: HyperdriveSecretSchema.optional(),\n});\n\nconst HyperdriveResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n binding: z.string().optional(),\n origin: HyperdriveOriginSchema,\n caching: z\n .object({\n disabled: z.boolean().optional(),\n max_age: z.number().optional(),\n stale_while_revalidate: z.number().optional(),\n })\n .optional(),\n mtls: z\n .object({\n ca_certificate_id: z.string().optional(),\n mtls_certificate_id: z.string().optional(),\n })\n .optional(),\n localConnectionString: z.string().optional(),\n});\n\nconst VectorizeResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n binding: z.string().optional(),\n dimensions: z.number().int().positive(),\n metric: z.enum([\"cosine\", \"euclidean\", \"dot-product\"]),\n description: z.string().optional(),\n});\n\nconst AIGatewayResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n cacheTtl: z.number().int().nonnegative().optional(),\n cacheInvalidateOnUpdate: z.boolean().optional(),\n collectLogs: z.boolean().optional(),\n authentication: z.boolean().optional(),\n rateLimitingInterval: z.number().int().nonnegative().optional(),\n rateLimitingLimit: z.number().int().nonnegative().optional(),\n rateLimitingTechnique: z.enum([\"fixed\", \"sliding\"]).optional(),\n});\n\nconst DispatchNamespaceResourceSchema = z.object({\n logicalName: z.string().min(1),\n namespace: z.string().min(1),\n envSuffix: z.boolean().optional(),\n});\n\nconst LogpushJobR2DestinationSchema = z.object({\n bucketLogicalName: z.string().min(1),\n pathPrefix: z.string().optional(),\n accessKeyIdEnv: z.string().min(1),\n secretAccessKeyEnv: z.string().min(1),\n});\n\nconst LogpushJobPipelinesIngestSchema = z.object({\n streamId: z.string().min(1),\n pipelineId: z.string().min(1),\n bearerTokenEnv: z.string().min(1),\n});\n\nconst LogpushJobPipelinesAutoSchema = z.object({\n catalogBucketLogicalName: z.string().min(1),\n tableName: z.string().min(1),\n namespace: z.string().min(1).optional(),\n /** When not `false`, new sinks get `${tableName}_${Date.now()}` (default). */\n tableNameAppendTimestamp: z.boolean().optional(),\n sinkRowGroupBytes: z.number().int().positive().optional(),\n sinkRollingFileSizeBytes: z.number().int().positive().optional(),\n sinkRollingIntervalSeconds: z.number().int().positive().optional(),\n});\n\nconst LogpushJobResourceConfigSchema = z\n .object({\n logicalName: z.string().min(1),\n dataset: z.literal(\"workers_trace_events\"),\n jobName: z.string().optional(),\n r2: LogpushJobR2DestinationSchema.optional(),\n pipelinesIngest: LogpushJobPipelinesIngestSchema.optional(),\n pipelinesAuto: LogpushJobPipelinesAutoSchema.optional(),\n destinationConfEnv: z.string().min(1).optional(),\n destinationConfFromJobId: z.number().int().positive().optional(),\n destinationConfFromJobIdEnv: z.string().min(1).optional(),\n filter: z.string().min(1).optional(),\n fieldNames: z.array(z.string()).optional(),\n enabled: z.boolean().optional(),\n })\n .refine(\n (d) => {\n const hasR2 = Boolean(d.r2);\n const n =\n (d.destinationConfEnv ? 1 : 0) +\n (d.destinationConfFromJobId != null ? 1 : 0) +\n (d.destinationConfFromJobIdEnv ? 1 : 0) +\n (d.pipelinesIngest ? 1 : 0) +\n (d.pipelinesAuto ? 1 : 0);\n if (hasR2) return n === 0;\n return n === 1;\n },\n {\n error:\n \"logpushJobs: set exactly one of r2 | pipelinesIngest | pipelinesAuto | destinationConfEnv | destinationConfFromJobId | destinationConfFromJobIdEnv\",\n },\n )\n .refine(\n (d) =>\n !(\n d.destinationConfFromJobId != null && d.destinationConfFromJobIdEnv\n ),\n {\n error:\n \"logpushJobs: use only one of destinationConfFromJobId or destinationConfFromJobIdEnv\",\n },\n )\n .refine((d) => !(d.pipelinesIngest && d.pipelinesAuto), {\n error: \"logpushJobs: use only one of pipelinesIngest or pipelinesAuto\",\n });\n\nconst DnsRecordResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n zoneId: z.string().min(1),\n type: z.enum([\n \"A\",\n \"AAAA\",\n \"CNAME\",\n \"TXT\",\n \"MX\",\n \"NS\",\n \"CAA\",\n \"SRV\",\n \"PTR\",\n \"HTTPS\",\n \"SVCB\",\n ]),\n name: z.string().min(1),\n content: z.string().min(1),\n ttl: z.number().int().positive().optional(),\n proxied: z.boolean().optional(),\n priority: z.number().int().nonnegative().optional(),\n comment: z.string().optional(),\n skipEnvs: z.array(z.string()).optional(),\n preserveOnDestroy: z.boolean().optional(),\n});\n\nconst PipelineResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n sql: z.string().min(1),\n binding: z.string().min(1).optional(),\n});\n\nconst WorkflowResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n className: z.string().min(1),\n scriptName: z.string().min(1).optional(),\n binding: z.string().min(1).optional(),\n limits: z\n .object({ steps: z.number().int().positive().optional() })\n .optional(),\n});\n\nconst SecretsStoreResourceConfigSchema = z.object({\n logicalName: z.string().min(1),\n});\n\nconst SecretsStoreSecretBindingSchema = z.object({\n binding: z.string().min(1),\n store: z.string().min(1),\n secretName: z.string().min(1),\n});\n\nconst WorkerResourcesSchema = z.object({\n d1: z.array(D1ResourceConfigSchema).optional(),\n r2: z.array(R2ResourceConfigSchema).optional(),\n kv: z.array(KVResourceConfigSchema).optional(),\n queues: z.array(QueueResourceConfigSchema).optional(),\n hyperdrive: z.array(HyperdriveResourceConfigSchema).optional(),\n vectorize: z.array(VectorizeResourceConfigSchema).optional(),\n aiGateway: z.array(AIGatewayResourceConfigSchema).optional(),\n pipelines: z.array(PipelineResourceConfigSchema).optional(),\n workflows: z.array(WorkflowResourceConfigSchema).optional(),\n secretsStores: z.array(SecretsStoreResourceConfigSchema).optional(),\n secretsStoreSecrets: z.array(SecretsStoreSecretBindingSchema).optional(),\n});\n\nconst WranglerRouteSchema = z.object({\n pattern: z.string(),\n custom_domain: z.boolean().optional(),\n zone_name: z.string().optional(),\n zone_id: z.string().optional(),\n});\n\nconst WranglerAssetsSchema = z.object({\n directory: z.string(),\n binding: z.string().optional(),\n not_found_handling: z\n .enum([\"single-page-application\", \"return-404\"])\n .optional(),\n});\n\nconst EnvOverrideSchema = z\n .object({\n vars: z.record(z.string(), z.string()).optional(),\n scriptName: z.string().optional(),\n wranglerOutFile: z.string().optional(),\n route: z\n .union([WranglerRouteSchema, z.array(WranglerRouteSchema)])\n .optional(),\n routes: z.array(WranglerRouteSchema).optional(),\n })\n .passthrough();\n\nconst WorkerConfigSchema = z\n .object({\n path: z.string().optional(),\n config: z.string().optional(),\n scriptName: z.string().optional(),\n wranglerOutFile: z.string().optional(),\n dispatchNamespace: z.string().optional(),\n main: z.string().optional(),\n compatibility_date: z.string().optional(),\n compatibility_flags: z.array(z.string()).optional(),\n limits: z.record(z.string(), z.union([z.number(), z.string()])).optional(),\n workers_dev: z.boolean().optional(),\n preview_urls: z.boolean().optional(),\n resources: WorkerResourcesSchema.optional(),\n alias: z.record(z.string(), z.string()).optional(),\n vars: z.record(z.string(), z.string()).optional(),\n local: EnvOverrideSchema.optional(),\n env: z.record(z.string(), EnvOverrideSchema).optional(),\n assets: WranglerAssetsSchema.optional(),\n route: z\n .union([WranglerRouteSchema, z.array(WranglerRouteSchema)])\n .optional(),\n routes: z.array(WranglerRouteSchema).optional(),\n })\n .passthrough();\n\nconst CfiConfigSchema = z\n .object({\n tenant: TenantMetaSchema,\n account_id: z.string().optional(),\n compatibility_date: z.string().optional(),\n naming: z.any().optional(),\n dispatchNamespaces: z.array(DispatchNamespaceResourceSchema).optional(),\n dnsRecords: z.array(DnsRecordResourceConfigSchema).optional(),\n logpushJobs: z.array(LogpushJobResourceConfigSchema).optional(),\n stack: z\n .object({\n // Stack name flows into the D1 row key (`cfi_state:{name}`) and the\n // `${tamer:import:<name>.<output>}` parser, so the same identifier\n // shape as output names keeps both surfaces consistent.\n name: z\n .string()\n .regex(/^[a-zA-Z][a-zA-Z0-9_-]*$/, {\n error:\n \"stack.name must match /^[a-zA-Z][a-zA-Z0-9_-]*$/ (CloudFormation-style identifier)\",\n })\n .optional(),\n description: z.string().min(1).optional(),\n })\n .optional(),\n outputs: z\n .record(\n // CloudFormation-ish output names: must start with a letter, then\n // letters/digits/`_`/`-`. Keeps them safe for filenames, env-var\n // exports, and `${tamer:import:stack.<name>}` parsing.\n z.string().regex(/^[a-zA-Z][a-zA-Z0-9_-]*$/, {\n error:\n \"outputs key must match /^[a-zA-Z][a-zA-Z0-9_-]*$/ (CloudFormation-style identifier)\",\n }),\n z.string().min(1),\n )\n .optional(),\n worker: WorkerConfigSchema.optional(),\n workers: z.record(z.string(), WorkerConfigSchema).optional(),\n })\n .refine(\n (data: { worker?: unknown; workers?: unknown }) =>\n (data.worker ?? data.workers) && !(data.worker && data.workers),\n { error: \"Must have either worker or workers, not both\" },\n );\n\nexport interface LoadConfigOptions {\n /** Selects `tamer/env/<env>.config.ts` when present (merged project layout). */\n env?: string;\n cwd?: string;\n}\n\nexport async function loadConfig(\n configPath?: string,\n options: LoadConfigOptions = {},\n): Promise<CfiConfig> {\n const cwd = options.cwd ?? process.cwd();\n const sources = resolveConfigSources(cwd, configPath, options.env);\n\n let raw: unknown;\n\n if (sources.mode === \"single\") {\n const mod = await import(sources.path);\n raw = mod.default;\n if (!raw) {\n throw new Error(`No default export in ${sources.path}`);\n }\n } else {\n const pMod = await import(sources.projectPath);\n const projectRaw = pMod.default;\n if (!projectRaw || typeof projectRaw !== \"object\") {\n throw new Error(`No default export in ${sources.projectPath}`);\n }\n let merged = projectRaw as Record<string, unknown>;\n if (sources.overlayPath) {\n const oMod = await import(sources.overlayPath);\n const overlayRaw = oMod.default;\n if (!overlayRaw || typeof overlayRaw !== \"object\") {\n throw new Error(\n `Env overlay must default-export an object: ${sources.overlayPath}`,\n );\n }\n const overlayObj = overlayRaw as Record<string, unknown>;\n const declaredEnv = overlayObj[TAMER_OVERLAY_ENV_KEY];\n if (declaredEnv !== undefined) {\n if (typeof declaredEnv !== \"string\") {\n throw new Error(\n `Env overlay ${sources.overlayPath}: ${TAMER_OVERLAY_ENV_KEY} must be a string`,\n );\n }\n if (options.env !== declaredEnv) {\n throw new Error(\n `Env overlay ${sources.overlayPath} sets ${TAMER_OVERLAY_ENV_KEY} \"${declaredEnv}\" but this load uses --env \"${options.env ?? \"(none)\"}\".`,\n );\n }\n }\n const { [TAMER_OVERLAY_ENV_KEY]: _strip, ...overlayRest } = overlayObj;\n merged = mergeProjectOverlay(merged, overlayRest);\n }\n raw = merged;\n }\n\n const parsed = CfiConfigSchema.safeParse(materializeCloudflareBindings(raw));\n if (!parsed.success) {\n throw new Error(`Invalid Tamer project config: ${parsed.error.message}`);\n }\n return parsed.data as CfiConfig;\n}\n\nexport async function getWorkers(\n config: CfiConfig,\n baseDir?: string,\n): Promise<[string, WorkerConfig][]> {\n const cwd = baseDir ?? process.cwd();\n\n if (\"worker\" in config && config.worker) {\n const w = await resolveWorkerConfigRef(config.worker, cwd);\n return [[\"default\", w]];\n }\n if (\"workers\" in config && config.workers) {\n const entries: [string, WorkerConfig][] = [];\n for (const [key, wc] of Object.entries(config.workers)) {\n entries.push([key, await resolveWorkerConfigRef(wc, cwd)]);\n }\n return entries;\n }\n return [];\n}\n\nasync function resolveWorkerConfigRef(\n wc: WorkerConfig,\n baseDir: string,\n): Promise<WorkerConfig> {\n if (!wc.config) return wc;\n const configPath = resolve(baseDir, wc.config);\n const mod = await import(configPath);\n const loaded = mod.default;\n if (!loaded) {\n throw new Error(`No default export in ${configPath}`);\n }\n return {\n ...loaded,\n path: wc.path,\n config: wc.config,\n } as WorkerConfig;\n}\n"],"mappings":";;;;;;;;;;AAKA,SAAS,cAAc,GAA0C;AAC/D,QACE,MAAM,QACN,OAAO,MAAM,YACb,CAAC,MAAM,QAAQ,EAAE,IACjB,EAAE,aAAa;;AAInB,SAAS,uBACP,gBACA,gBACM;AACN,MAAK,MAAM,KAAK,OAAO,KAAK,eAAe,CACzC,KAAI,EAAE,KAAK,gBACT,OAAM,IAAI,MACR,0CAA0C,EAAE,cAC9B,EAAE,gFACjB;;AAKP,SAAS,qBACP,MACA,OACyB;CACzB,MAAM,MAAM,EAAE,GAAG,MAAM;AACvB,MAAK,MAAM,KAAK,OAAO,KAAK,MAAM,EAAE;EAClC,MAAM,KAAK,MAAM;AACjB,MAAI,OAAO,OACT,KAAI,KAAK;;AAGb,QAAO;;AAGT,SAAS,YACP,MACA,OACyB;CACzB,MAAM,MAAM,EAAE,GAAG,MAAM;AACvB,MAAK,MAAM,KAAK,OAAO,KAAK,MAAM,EAAE;EAClC,MAAM,IAAI,KAAK;EACf,MAAM,IAAI,MAAM;AAChB,MAAI,cAAc,EAAE,IAAI,cAAc,EAAE,CACtC,KAAI,KAAK,kBAAkB,GAAG,EAAE;MAEhC,KAAI,KAAK;;AAGb,QAAO;;AAGT,SAAS,kBACP,MACA,OACyB;CACzB,MAAMA,MAA+B,EAAE,GAAG,MAAM;AAChD,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE;EACpC,MAAM,KAAK,MAAM;AACjB,MAAI,OAAO,OAAW;AAEtB,MAAI,QAAQ,QAAQ;GAClB,MAAM,KAAK,cAAc,KAAK,KAAK,GAAG,KAAK,OAAO,EAAE;GACpD,MAAM,KAAK,cAAc,MAAM,KAAK,GAAG,MAAM,OAAO,EAAE;AACtD,OAAI,OAAO;IAAE,GAAG;IAAI,GAAG;IAAI;AAC3B;;AAEF,MAAI,QAAQ,eAAe;AACzB,OAAI,cAAc;AAClB;;AAEF,MAAI,QAAQ,6BAA6B;AACvC,OAAI,4BAA4B;AAChC;;AAEF,MAAI,QAAQ,aAAa;AACvB,OAAI,cAAc,KAAK,UAAU,IAAI,cAAc,GAAG,CACpD,KAAI,YAAY,qBAAqB,KAAK,WAAW,GAAG;OAExD,KAAI,YAAY;AAElB;;AAEF,MAAI,QAAQ,OAAO;AACjB,OAAI,cAAc,KAAK,IAAI,IAAI,cAAc,GAAG,CAC9C,KAAI,MAAM,YAAY,KAAK,KAAK,GAAG;OAEnC,KAAI,MAAM;AAEZ;;AAEF,MAAI,QAAQ,SAAS;AACnB,OAAI,cAAc,KAAK,MAAM,IAAI,cAAc,GAAG,CAChD,KAAI,QAAQ,kBACV,KAAK,OACL,GACD;OAED,KAAI,QAAQ;AAEd;;AAEF,MAAI,OAAO;;AAEb,QAAO;;AAGT,SAAS,mBACP,MACA,OACyB;CACzB,MAAM,MAAM,EAAE,GAAG,MAAM;AACvB,MAAK,MAAM,KAAK,OAAO,KAAK,MAAM,EAAE;EAClC,MAAM,IAAI,KAAK;EACf,MAAM,IAAI,MAAM;AAChB,MAAI,cAAc,EAAE,IAAI,cAAc,EAAE,CACtC,KAAI,KAAK,kBAAkB,GAAG,EAAE;MAEhC,KAAI,KAAK;;AAGb,QAAO;;;;;;AAOT,SAAgB,oBACd,SACA,SACyB;CACzB,MAAMA,MAA+B,EAAE,GAAG,SAAS;AACnD,MAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,EAAE;EACtC,MAAM,KAAK,QAAQ;AACnB,MAAI,OAAO,OAAW;EACtB,MAAM,KAAK,QAAQ;AAEnB,UAAQ,KAAR;GACE,KAAK;AACH,QAAI,cAAc,GAAG,IAAI,cAAc,GAAG,CACxC,KAAI,OAAO;KAAE,GAAG;KAAI,GAAG;KAAI;QAE3B,KAAI,OAAO;AAEb;GACF,KAAK;AACH,QAAI,OAAO;AACX;GACF,KAAK;AACH,QAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,GAAG,EAAE;AAC5C,SAAI,OAAO;AACX;;AAEF,2BAAuB,IAAI,GAAG;AAC9B,QAAI,OAAO,mBAAmB,IAAI,GAAG;AACrC;GACF,KAAK;AACH,QAAI,cAAc,GAAG,IAAI,cAAc,GAAG,CACxC,KAAI,OAAO,kBAAkB,IAAI,GAAG;QAEpC,KAAI,OAAO;AAEb;GACF,KAAK;AACH,QAAI,cAAc,GAAG,IAAI,cAAc,GAAG,CACxC,KAAI,OAAO;KAAE,GAAG;KAAI,GAAG;KAAI;QAE3B,KAAI,OAAO;AAEb;GACF,KAAK;GACL,KAAK;GACL,KAAK;AACH,QAAI,OAAO;AACX;GACF,QACE,KAAI,OAAO;;;AAGjB,QAAO;;;;;ACpLT,SAAS,kBAAkB,MAAuB;AAChD,QAAO,SAAS,uBAAuB,SAAS;;AAGlD,SAAS,yBAAyB,YAA4B;CAC5D,MAAM,MAAM,QAAQ,WAAW;AAE/B,KADa,SAAS,WAAW,KACpB,oBACX,QAAO;AAET,QAAO;;AAGT,SAAS,2BAA2B,YAAoB,KAAqB;AAC3E,QAAO,QAAQ,YAAY,OAAO,GAAG,IAAI,YAAY;;AAGvD,SAAS,yBAAyB,aAAqB,KAAqB;AAC1E,QAAO,QAAQ,aAAa,aAAa,IAAI,KAAK;;AAGpD,SAAS,4BAA4B,SAAwB;AAC3D,OAAM,IAAI,MACR,GAAG,QAAQ,uOAGZ;;;;;;;;AASH,SAAgB,qBACd,KACA,oBACA,KACuB;AACvB,KAAI,oBAAoB;EACtB,MAAM,MAAM,QAAQ,KAAK,mBAAmB;EAC5C,MAAM,OAAO,SAAS,IAAI;AAC1B,MAAI,SAAS,kBACX,6BAA4B,WAAW;AAEzC,MAAI,kBAAkB,KAAK,EAAE;GAC3B,MAAM,aAAa,yBAAyB,IAAI;GAChD,IAAIC,UAAyB;AAC7B,OAAI,IACF,KAAI,SAAS,qBAAqB;IAChC,MAAM,YAAY,2BAA2B,YAAY,IAAI;AAC7D,QAAI,WAAW,UAAU,CAAE,WAAU;UAChC;IACL,MAAM,YAAY,yBAAyB,QAAQ,IAAI,EAAE,IAAI;AAC7D,QAAI,WAAW,UAAU,CAAE,WAAU;;AAGzC,UAAO;IAAE,MAAM;IAAU,aAAa;IAAK,aAAa;IAAS;;AAEnE,SAAO;GAAE,MAAM;GAAU,MAAM;GAAK;;CAGtC,MAAM,gBAAgB,QAAQ,KAAK,SAAS,oBAAoB;AAChE,KAAI,WAAW,cAAc,EAAE;EAC7B,IAAIA,UAAyB;AAC7B,MAAI,KAAK;GACP,MAAM,YAAY,2BAChB,QAAQ,cAAc,EACtB,IACD;AACD,OAAI,WAAW,UAAU,CAAE,WAAU;;AAEvC,SAAO;GACL,MAAM;GACN,aAAa;GACb,aAAa;GACd;;CAGH,MAAM,cAAc,QAAQ,KAAK,0BAA0B;AAC3D,KAAI,WAAW,YAAY,EAAE;EAC3B,IAAIA,UAAyB;AAC7B,MAAI,KAAK;GACP,MAAM,YAAY,yBAAyB,KAAK,IAAI;AACpD,OAAI,WAAW,UAAU,CAAE,WAAU;;AAEvC,SAAO;GACL,MAAM;GACN,aAAa;GACb,aAAa;GACd;;AAGH,KAAI,WAAW,QAAQ,KAAK,kBAAkB,CAAC,CAC7C,6BAA4B,mBAAmB;AAGjD,OAAM,IAAI,MACR,iCAAiC,IAAI,iFAEtC;;;;;ACpGH,MAAM,uBAAuB;AAE7B,MAAM,mBAAmBC,OAAS;CAChC,IAAIC,QAAU,CAAC,IAAI,EAAE;CACrB,MAAMA,QAAU,CAAC,IAAI,EAAE;CACvB,MAAMA,QAAU,CAAC,IAAI,EAAE;CACvB,UAAUC,MAENC,QACW,CACR,IAAI,EAAE,CACN,QAAQ,MAAM,qBAAqB,KAAK,EAAE,EAAE,EAM3C,OACE,uGACH,CAAC,CACL,CACA,UAAU,CACV,QACE,QAAQ,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,SAAS,IAAI,QAC3C,mDACD;CACH,eAAeC,MAAQH,QAAU,CAAC,IAAI,EAAE,CAAC,CAAC,UAAU;CACpD,qBAAqBI,QACV,CACR,IAAI,EAAE,CACN,UAAU,CACV,QACE,MAAM;AACL,MAAI,KAAK,KAAM,QAAO;AACtB,MAAI;AACF,OAAI,OAAO,EAAE;AACb,UAAO;UACD;AACN,UAAO;;IAMX,6EACD;CACJ,CAAC;AAEF,MAAM,yBAAyBC,OACrB;CACN,aAAaL,QAAU,CAAC,IAAI,EAAE;CAC9B,MAAMM,MAAO,CAAC,UAAU,UAAU,CAAC;CACnC,WAAWA,MAAO,CAAC,WAAW,WAAW,CAAC,CAAC,UAAU;CACrD,cAAcN,QAAU,CAAC,UAAU;CACnC,SAASA,QAAU,CAAC,UAAU;CAC9B,eAAeA,QAAU,CAAC,UAAU;CACpC,iBAAiBA,QAAU,CAAC,UAAU;CACtC,mBAAmBO,SAAW,CAAC,UAAU;CAC1C,CAAC,CACD,QACE,MACC,EAAE,cAAc,cACf,OAAO,EAAE,iBAAiB,YAAY,EAAE,aAAa,SAAS,GACjE,EAAE,OAAO,sEAAsE,CAChF,CACA,QACE,MAAM,EAAE,cAAc,cAAc,EAAE,SAAS,UAChD,EAAE,OAAO,kEAAkE,CAC5E;AAEH,MAAM,yBAAyBR,OAAS;CACtC,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,SAASA,QAAU,CAAC,UAAU;CAC/B,CAAC;AAEF,MAAM,yBAAyBD,OAAS;CACtC,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,SAASA,QAAU,CAAC,UAAU;CAC/B,CAAC;AAEF,MAAM,4BAA4BD,OAAS;CACzC,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,SAASA,QAAU,CAAC,UAAU;CAC9B,cAAcO,SAAW,CAAC,UAAU;CACrC,CAAC;AAEF,MAAM,yBAAyBC,MAAQ,CACrCR,QAAU,EACVD,OAAS,EAAE,SAASC,QAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CACzC,CAAC;AAEF,MAAM,yBAAyBD,OAAS;CACtC,QAAQO,MAAO;EAAC;EAAY;EAAc;EAAQ,CAAC;CACnD,MAAMN,QAAU,CAAC,IAAI,EAAE;CACvB,MAAMS,QAAU,CAAC,UAAU;CAC3B,UAAUT,QAAU,CAAC,IAAI,EAAE;CAC3B,MAAMA,QAAU,CAAC,IAAI,EAAE;CACvB,UAAU;CACV,kBAAkBA,QAAU,CAAC,UAAU;CACvC,sBAAsB,uBAAuB,UAAU;CACxD,CAAC;AAEF,MAAM,iCAAiCD,OAAS;CAC9C,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,SAASA,QAAU,CAAC,UAAU;CAC9B,QAAQ;CACR,SAASU,OACC;EACN,UAAUH,SAAW,CAAC,UAAU;EAChC,SAASE,QAAU,CAAC,UAAU;EAC9B,wBAAwBA,QAAU,CAAC,UAAU;EAC9C,CAAC,CACD,UAAU;CACb,MAAMC,OACI;EACN,mBAAmBV,QAAU,CAAC,UAAU;EACxC,qBAAqBA,QAAU,CAAC,UAAU;EAC3C,CAAC,CACD,UAAU;CACb,uBAAuBA,QAAU,CAAC,UAAU;CAC7C,CAAC;AAEF,MAAM,gCAAgCD,OAAS;CAC7C,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,SAASA,QAAU,CAAC,UAAU;CAC9B,YAAYS,QAAU,CAAC,KAAK,CAAC,UAAU;CACvC,QAAQH,MAAO;EAAC;EAAU;EAAa;EAAc,CAAC;CACtD,aAAaN,QAAU,CAAC,UAAU;CACnC,CAAC;AAEF,MAAM,gCAAgCD,OAAS;CAC7C,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,UAAUS,QAAU,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU;CACnD,yBAAyBF,SAAW,CAAC,UAAU;CAC/C,aAAaA,SAAW,CAAC,UAAU;CACnC,gBAAgBA,SAAW,CAAC,UAAU;CACtC,sBAAsBE,QAAU,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU;CAC/D,mBAAmBA,QAAU,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU;CAC5D,uBAAuBH,MAAO,CAAC,SAAS,UAAU,CAAC,CAAC,UAAU;CAC/D,CAAC;AAEF,MAAM,kCAAkCP,OAAS;CAC/C,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,WAAWA,QAAU,CAAC,IAAI,EAAE;CAC5B,WAAWO,SAAW,CAAC,UAAU;CAClC,CAAC;AAEF,MAAM,gCAAgCR,OAAS;CAC7C,mBAAmBC,QAAU,CAAC,IAAI,EAAE;CACpC,YAAYA,QAAU,CAAC,UAAU;CACjC,gBAAgBA,QAAU,CAAC,IAAI,EAAE;CACjC,oBAAoBA,QAAU,CAAC,IAAI,EAAE;CACtC,CAAC;AAEF,MAAM,kCAAkCD,OAAS;CAC/C,UAAUC,QAAU,CAAC,IAAI,EAAE;CAC3B,YAAYA,QAAU,CAAC,IAAI,EAAE;CAC7B,gBAAgBA,QAAU,CAAC,IAAI,EAAE;CAClC,CAAC;AAEF,MAAM,gCAAgCD,OAAS;CAC7C,0BAA0BC,QAAU,CAAC,IAAI,EAAE;CAC3C,WAAWA,QAAU,CAAC,IAAI,EAAE;CAC5B,WAAWA,QAAU,CAAC,IAAI,EAAE,CAAC,UAAU;CAEvC,0BAA0BO,SAAW,CAAC,UAAU;CAChD,mBAAmBE,QAAU,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU;CACzD,0BAA0BA,QAAU,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU;CAChE,4BAA4BA,QAAU,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU;CACnE,CAAC;AAEF,MAAM,iCAAiCJ,OAC7B;CACN,aAAaL,QAAU,CAAC,IAAI,EAAE;CAC9B,SAASW,QAAU,uBAAuB;CAC1C,SAASX,QAAU,CAAC,UAAU;CAC9B,IAAI,8BAA8B,UAAU;CAC5C,iBAAiB,gCAAgC,UAAU;CAC3D,eAAe,8BAA8B,UAAU;CACvD,oBAAoBA,QAAU,CAAC,IAAI,EAAE,CAAC,UAAU;CAChD,0BAA0BS,QAAU,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU;CAChE,6BAA6BT,QAAU,CAAC,IAAI,EAAE,CAAC,UAAU;CACzD,QAAQA,QAAU,CAAC,IAAI,EAAE,CAAC,UAAU;CACpC,YAAYG,MAAQH,QAAU,CAAC,CAAC,UAAU;CAC1C,SAASO,SAAW,CAAC,UAAU;CAChC,CAAC,CACD,QACE,MAAM;CACL,MAAM,QAAQ,QAAQ,EAAE,GAAG;CAC3B,MAAM,KACH,EAAE,qBAAqB,IAAI,MAC3B,EAAE,4BAA4B,OAAO,IAAI,MACzC,EAAE,8BAA8B,IAAI,MACpC,EAAE,kBAAkB,IAAI,MACxB,EAAE,gBAAgB,IAAI;AACzB,KAAI,MAAO,QAAO,MAAM;AACxB,QAAO,MAAM;GAEf,EACE,OACE,sJACH,CACF,CACA,QACE,MACC,EACE,EAAE,4BAA4B,QAAQ,EAAE,8BAE5C,EACE,OACE,wFACH,CACF,CACA,QAAQ,MAAM,EAAE,EAAE,mBAAmB,EAAE,gBAAgB,EACtD,OAAO,iEACR,CAAC;AAEJ,MAAM,gCAAgCR,OAAS;CAC7C,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,QAAQA,QAAU,CAAC,IAAI,EAAE;CACzB,MAAMM,MAAO;EACX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,MAAMN,QAAU,CAAC,IAAI,EAAE;CACvB,SAASA,QAAU,CAAC,IAAI,EAAE;CAC1B,KAAKS,QAAU,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU;CAC3C,SAASF,SAAW,CAAC,UAAU;CAC/B,UAAUE,QAAU,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU;CACnD,SAAST,QAAU,CAAC,UAAU;CAC9B,UAAUG,MAAQH,QAAU,CAAC,CAAC,UAAU;CACxC,mBAAmBO,SAAW,CAAC,UAAU;CAC1C,CAAC;AAEF,MAAM,+BAA+BR,OAAS;CAC5C,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,KAAKA,QAAU,CAAC,IAAI,EAAE;CACtB,SAASA,QAAU,CAAC,IAAI,EAAE,CAAC,UAAU;CACtC,CAAC;AAEF,MAAM,+BAA+BD,OAAS;CAC5C,aAAaC,QAAU,CAAC,IAAI,EAAE;CAC9B,WAAWA,QAAU,CAAC,IAAI,EAAE;CAC5B,YAAYA,QAAU,CAAC,IAAI,EAAE,CAAC,UAAU;CACxC,SAASA,QAAU,CAAC,IAAI,EAAE,CAAC,UAAU;CACrC,QAAQU,OACE,EAAE,OAAOD,QAAU,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,CACzD,UAAU;CACd,CAAC;AAEF,MAAM,mCAAmCV,OAAS,EAChD,aAAaC,QAAU,CAAC,IAAI,EAAE,EAC/B,CAAC;AAEF,MAAM,kCAAkCD,OAAS;CAC/C,SAASC,QAAU,CAAC,IAAI,EAAE;CAC1B,OAAOA,QAAU,CAAC,IAAI,EAAE;CACxB,YAAYA,QAAU,CAAC,IAAI,EAAE;CAC9B,CAAC;AAEF,MAAM,wBAAwBD,OAAS;CACrC,IAAII,MAAQ,uBAAuB,CAAC,UAAU;CAC9C,IAAIA,MAAQ,uBAAuB,CAAC,UAAU;CAC9C,IAAIA,MAAQ,uBAAuB,CAAC,UAAU;CAC9C,QAAQA,MAAQ,0BAA0B,CAAC,UAAU;CACrD,YAAYA,MAAQ,+BAA+B,CAAC,UAAU;CAC9D,WAAWA,MAAQ,8BAA8B,CAAC,UAAU;CAC5D,WAAWA,MAAQ,8BAA8B,CAAC,UAAU;CAC5D,WAAWA,MAAQ,6BAA6B,CAAC,UAAU;CAC3D,WAAWA,MAAQ,6BAA6B,CAAC,UAAU;CAC3D,eAAeA,MAAQ,iCAAiC,CAAC,UAAU;CACnE,qBAAqBA,MAAQ,gCAAgC,CAAC,UAAU;CACzE,CAAC;AAEF,MAAM,sBAAsBJ,OAAS;CACnC,SAASC,QAAU;CACnB,eAAeO,SAAW,CAAC,UAAU;CACrC,WAAWP,QAAU,CAAC,UAAU;CAChC,SAASA,QAAU,CAAC,UAAU;CAC/B,CAAC;AAEF,MAAM,uBAAuBD,OAAS;CACpC,WAAWC,QAAU;CACrB,SAASA,QAAU,CAAC,UAAU;CAC9B,oBAAoBY,MACZ,CAAC,2BAA2B,aAAa,CAAC,CAC/C,UAAU;CACd,CAAC;AAEF,MAAM,oBAAoBP,OAChB;CACN,MAAMQ,OAASb,QAAU,EAAEA,QAAU,CAAC,CAAC,UAAU;CACjD,YAAYA,QAAU,CAAC,UAAU;CACjC,iBAAiBA,QAAU,CAAC,UAAU;CACtC,OAAOc,MACE,CAAC,qBAAqBX,MAAQ,oBAAoB,CAAC,CAAC,CAC1D,UAAU;CACb,QAAQA,MAAQ,oBAAoB,CAAC,UAAU;CAChD,CAAC,CACD,aAAa;AAEhB,MAAM,qBAAqBE,OACjB;CACN,MAAML,QAAU,CAAC,UAAU;CAC3B,QAAQA,QAAU,CAAC,UAAU;CAC7B,YAAYA,QAAU,CAAC,UAAU;CACjC,iBAAiBA,QAAU,CAAC,UAAU;CACtC,mBAAmBA,QAAU,CAAC,UAAU;CACxC,MAAMA,QAAU,CAAC,UAAU;CAC3B,oBAAoBA,QAAU,CAAC,UAAU;CACzC,qBAAqBG,MAAQH,QAAU,CAAC,CAAC,UAAU;CACnD,QAAQa,OAASb,QAAU,EAAEQ,MAAQ,CAACC,QAAU,EAAET,QAAU,CAAC,CAAC,CAAC,CAAC,UAAU;CAC1E,aAAaO,SAAW,CAAC,UAAU;CACnC,cAAcA,SAAW,CAAC,UAAU;CACpC,WAAW,sBAAsB,UAAU;CAC3C,OAAOM,OAASb,QAAU,EAAEA,QAAU,CAAC,CAAC,UAAU;CAClD,MAAMa,OAASb,QAAU,EAAEA,QAAU,CAAC,CAAC,UAAU;CACjD,OAAO,kBAAkB,UAAU;CACnC,KAAKa,OAASb,QAAU,EAAE,kBAAkB,CAAC,UAAU;CACvD,QAAQ,qBAAqB,UAAU;CACvC,OAAOc,MACE,CAAC,qBAAqBX,MAAQ,oBAAoB,CAAC,CAAC,CAC1D,UAAU;CACb,QAAQA,MAAQ,oBAAoB,CAAC,UAAU;CAChD,CAAC,CACD,aAAa;AAEhB,MAAM,kBAAkBE,OACd;CACN,QAAQ;CACR,YAAYL,QAAU,CAAC,UAAU;CACjC,oBAAoBA,QAAU,CAAC,UAAU;CACzC,QAAQe,KAAO,CAAC,UAAU;CAC1B,oBAAoBZ,MAAQ,gCAAgC,CAAC,UAAU;CACvE,YAAYA,MAAQ,8BAA8B,CAAC,UAAU;CAC7D,aAAaA,MAAQ,+BAA+B,CAAC,UAAU;CAC/D,OAAOa,OACG;EAIN,MAAMC,QACK,CACR,MAAM,4BAA4B,EACjC,OACE,sFACH,CAAC,CACD,UAAU;EACb,aAAajB,QAAU,CAAC,IAAI,EAAE,CAAC,UAAU;EAC1C,CAAC,CACD,UAAU;CACb,SAASkB,OAKLlB,QAAU,CAAC,MAAM,4BAA4B,EAC3C,OACE,uFACH,CAAC,EACFA,QAAU,CAAC,IAAI,EAAE,CAClB,CACA,UAAU;CACb,QAAQ,mBAAmB,UAAU;CACrC,SAASa,OAASb,QAAU,EAAE,mBAAmB,CAAC,UAAU;CAC7D,CAAC,CACD,QACE,UACE,KAAK,UAAU,KAAK,YAAY,EAAE,KAAK,UAAU,KAAK,UACzD,EAAE,OAAO,gDAAgD,CAC1D;AAQH,eAAsB,WACpB,YACA,UAA6B,EAAE,EACX;CAEpB,MAAM,UAAU,qBADJ,QAAQ,OAAO,QAAQ,KAAK,EACE,YAAY,QAAQ,IAAI;CAElE,IAAImB;AAEJ,KAAI,QAAQ,SAAS,UAAU;AAE7B,SADY,MAAM,OAAO,QAAQ,OACvB;AACV,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,wBAAwB,QAAQ,OAAO;QAEpD;EAEL,MAAM,cADO,MAAM,OAAO,QAAQ,cACV;AACxB,MAAI,CAAC,cAAc,OAAO,eAAe,SACvC,OAAM,IAAI,MAAM,wBAAwB,QAAQ,cAAc;EAEhE,IAAI,SAAS;AACb,MAAI,QAAQ,aAAa;GAEvB,MAAM,cADO,MAAM,OAAO,QAAQ,cACV;AACxB,OAAI,CAAC,cAAc,OAAO,eAAe,SACvC,OAAM,IAAI,MACR,8CAA8C,QAAQ,cACvD;GAEH,MAAM,aAAa;GACnB,MAAM,cAAc,WAAW;AAC/B,OAAI,gBAAgB,QAAW;AAC7B,QAAI,OAAO,gBAAgB,SACzB,OAAM,IAAI,MACR,eAAe,QAAQ,YAAY,IAAI,sBAAsB,mBAC9D;AAEH,QAAI,QAAQ,QAAQ,YAClB,OAAM,IAAI,MACR,eAAe,QAAQ,YAAY,QAAQ,sBAAsB,IAAI,YAAY,8BAA8B,QAAQ,OAAO,SAAS,IACxI;;GAGL,MAAM,GAAG,wBAAwB,QAAQ,GAAG,gBAAgB;AAC5D,YAAS,oBAAoB,QAAQ,YAAY;;AAEnD,QAAM;;CAGR,MAAM,SAAS,gBAAgB,UAAU,8BAA8B,IAAI,CAAC;AAC5E,KAAI,CAAC,OAAO,QACV,OAAM,IAAI,MAAM,iCAAiC,OAAO,MAAM,UAAU;AAE1E,QAAO,OAAO;;AAGhB,eAAsB,WACpB,QACA,SACmC;CACnC,MAAM,MAAM,WAAW,QAAQ,KAAK;AAEpC,KAAI,YAAY,UAAU,OAAO,OAE/B,QAAO,CAAC,CAAC,WADC,MAAM,uBAAuB,OAAO,QAAQ,IAAI,CACpC,CAAC;AAEzB,KAAI,aAAa,UAAU,OAAO,SAAS;EACzC,MAAMC,UAAoC,EAAE;AAC5C,OAAK,MAAM,CAAC,KAAK,OAAO,OAAO,QAAQ,OAAO,QAAQ,CACpD,SAAQ,KAAK,CAAC,KAAK,MAAM,uBAAuB,IAAI,IAAI,CAAC,CAAC;AAE5D,SAAO;;AAET,QAAO,EAAE;;AAGX,eAAe,uBACb,IACA,SACuB;AACvB,KAAI,CAAC,GAAG,OAAQ,QAAO;CACvB,MAAM,aAAa,QAAQ,SAAS,GAAG,OAAO;CAE9C,MAAM,UADM,MAAM,OAAO,aACN;AACnB,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,wBAAwB,aAAa;AAEvD,QAAO;EACL,GAAG;EACH,MAAM,GAAG;EACT,QAAQ,GAAG;EACZ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-Bx0bpFop.mjs","names":["spec: CfBindingSpec","out: Record<string, unknown>"],"sources":["../src/types.ts","../src/dx/cloudflare-bindings.ts","../src/dx/normalize.ts"],"sourcesContent":["import type { CfBinding } from \"./dx/cloudflare-bindings.js\";\nimport type {\n WranglerConfig,\n WranglerR2Bucket,\n} from \"./generated/wrangler-types.js\";\n\nexport type { WranglerConfig };\n\n/**\n * Worker `vars`, `outputs`, and similar fields accept plain strings or\n * {@link CfBinding} values; `loadConfig` materializes bindings to `${tamer:…}`\n * before validation.\n */\nexport type TamerResolvableString = string | CfBinding;\n\n/** Wrangler R2 binding with optional {@link TamerResolvableString} `bucket_name`. */\nexport type WranglerR2BucketResolvable = Omit<\n WranglerR2Bucket,\n \"bucket_name\"\n> & {\n bucket_name?: TamerResolvableString;\n};\n\n// ── Tenant ───────────────────────────────────────────────────────────────────\n\nexport interface TenantMeta {\n id: string;\n name: string;\n slug: string;\n /**\n * Optional per-stack tenant D1 shard layout. Each entry is a free-form\n * role name (lowercase letters, digits, `_`, `-`) that becomes part of\n * the per-tenant D1 database name `db_{role}_{w}_{p}_t_{tid}_{env}`\n * when `tamer provision-tenant` runs.\n *\n * Tamer is opinion-free about how many shards a product wants and what\n * they're called — a Dragoncore-style product might use `[\"system\",\n * \"app\", \"history\"]`, a single-DB tenant `[\"main\"]`, a billing-style\n * product `[\"billing\", \"content\"]`, or omit `d1Shards` entirely (in\n * which case `provision-tenant` only uploads the dispatch script — no\n * per-tenant D1 fan-out).\n *\n * The order here is **canonical provisioning order**: shards are\n * created left-to-right and listed in plan/status output in the same\n * order, so a partial failure leaves a deterministic recoverable\n * state. `--shards <subset>` on the CLI must be a subset of this list\n * — the config is the source of truth, the flag only trims.\n */\n d1Shards?: string[];\n /**\n * Envs that require an explicit `--confirm-tenant <workspace>` (or\n * `--force`) before `destroy-tenant` will run. Defaults to\n * `[\"prod\", \"production\"]`. Add e.g. `\"production-eu\"`,\n * `\"production-us\"`, `\"qa\"`, `\"uat\"`, `\"canary\"` here for any env\n * whose accidental teardown would be a real-world outage.\n *\n * `local` is never protected (it's a wrangler-dev concept, not a\n * deployed env). Override with `[]` to disable the prompt entirely\n * (only sensible for personal accounts).\n */\n protectedEnvs?: string[];\n /**\n * Optional regex (passed as a string) that decides which env names\n * are \"ephemeral\" — i.e. share **one** dispatch namespace\n * (`{ns}-ephemeral`) instead of getting their own (`{ns}-{env}`),\n * include the env in their dispatch-script name so multiple\n * ephemeral previews can coexist, and get a `BRANCH_SUFFIX`\n * environment variable injected at resolve time.\n *\n * Examples: `\"^pr-\"` (PR previews), `\"^(pr|feature|branch)-\"`,\n * `\"^canary-\"`. When omitted (the default), no env is ephemeral —\n * every env owns its own dispatch namespace. Compiled once at\n * config-load time; an invalid regex fails at parse, not at apply.\n */\n ephemeralEnvPattern?: string;\n}\n\n// ── Resources ────────────────────────────────────────────────────────────────\n\nexport interface D1ResourceConfig {\n logicalName: string;\n type: \"single\" | \"sharded\";\n /**\n * `managed` (default): Tamer creates the database; the Cloudflare name follows\n * `naming.d1Single` / shard rules.\n *\n * `external`: owned by another stack. Requires {@link databaseName} resolving\n * to the live D1 name (e.g. `${tamer:import:platform.platform_db_name}`). Skips\n * create, migrate, and destroy on Cloudflare for this binding.\n */\n ownership?: \"managed\" | \"external\";\n /**\n * Required when `ownership` is `external`. May contain `${tamer:import:…}`; must\n * be resolved (via mergeWorkerConfigWithResolvedRefs / resolveWorkerConfig)\n * before apply / sync / wrangler generation.\n */\n databaseName?: TamerResolvableString;\n /** When set, used as the Wrangler D1 `binding` instead of the generated name. Only applies to `type: \"single\"`. */\n binding?: string;\n migrationsDir?: string;\n migrationsTable?: string;\n /**\n * When true, `tamer destroy` will not delete this database (e.g. created by\n * another stack but bound read-only here). Default false.\n *\n * Legacy cross-stack mode without {@link ownership} `external`: same Cloudflare\n * name as the owning stack ({@link NamingConventions.d1Single} + logicalName).\n * Prefer `ownership: \"external\"` and a sibling-stack output for the name.\n */\n preserveOnDestroy?: boolean;\n}\n\nexport interface R2ResourceConfig {\n logicalName: string;\n /** When set, used as the Wrangler R2 `binding` instead of the generated stable name. */\n binding?: string;\n}\n\nexport interface KVResourceConfig {\n logicalName: string;\n /** When set, used as the Wrangler KV `binding` instead of the generated name. */\n binding?: string;\n}\n\n/**\n * Cloudflare Queue (producer binding) managed by Tamer.\n *\n * Tamer creates the queue itself on `apply` and emits a `queues.producers[]`\n * binding for the worker. Consumer subscriptions are wrangler-side only today\n * (set them via `queues.consumers` on the worker config).\n */\nexport interface QueueResourceConfig {\n logicalName: string;\n /** When set, used as the Wrangler queue `binding` instead of the generated name. */\n binding?: string;\n /**\n * When true, this entry is consumer-only (no producer binding). The queue is\n * still provisioned by Tamer if absent. Default false (producer binding emitted).\n */\n consumerOnly?: boolean;\n}\n\n/**\n * Cloudflare Hyperdrive config managed by Tamer.\n *\n * Tamer creates the Hyperdrive config on `apply` (the origin connection\n * string is sent to the API once, never persisted in state) and emits a\n * `hyperdrive[]` binding for the worker.\n */\nexport interface HyperdriveResourceConfig {\n logicalName: string;\n /** When set, used as the Wrangler hyperdrive `binding` instead of the generated name. */\n binding?: string;\n /** Origin database connection. Sent to Cloudflare on create; not stored in Tamer state. */\n origin: HyperdriveOriginSpec;\n /** Optional caching tweaks passed to Cloudflare. */\n caching?: {\n disabled?: boolean;\n max_age?: number;\n stale_while_revalidate?: number;\n };\n /** Optional `mtls` block passed to Cloudflare. */\n mtls?: { ca_certificate_id?: string; mtls_certificate_id?: string };\n /** `wrangler dev`-time connection string written to the generated config. */\n localConnectionString?: string;\n}\n\n/**\n * Cloudflare Vectorize index managed by Tamer.\n *\n * Tamer creates the index on `apply` (the v2 storage subsystem; legacy v1 is\n * unsupported) and emits a `vectorize[]` binding for the worker. Index\n * configuration (`dimensions`, `metric`) is immutable per Cloudflare's API,\n * so changes after creation are rejected — drop and recreate via `tamer\n * destroy --resource <logicalName>` then `tamer apply`.\n */\nexport interface VectorizeResourceConfig {\n logicalName: string;\n /** When set, used as the Wrangler vectorize `binding` instead of the generated name. */\n binding?: string;\n /** Vector dimensionality (e.g. 768 for `@cf/baai/bge-base-en-v1.5`). Immutable. */\n dimensions: number;\n /** Distance metric. Immutable after creation. */\n metric: \"cosine\" | \"euclidean\" | \"dot-product\";\n /** Free-form description sent to the Vectorize API on create. */\n description?: string;\n}\n\n/**\n * Cloudflare AI Gateway managed by Tamer.\n *\n * Tamer creates the gateway on `apply` against `/accounts/{id}/ai-gateway/\n * gateways`. AI Gateways have no Wrangler binding kind — Workers reference\n * them per-request via `env.AI.run(model, opts, { gateway: { id } })` (or\n * the OpenAI-compatible endpoint URL). Use cross-resource refs like\n * `${tamer:ai_gateway:my_gw.name}` in worker `vars` to inject the derived\n * gateway slug at deploy time.\n */\nexport interface AIGatewayResourceConfig {\n logicalName: string;\n /** Cache TTL in seconds. Default 0 (caching disabled). */\n cacheTtl?: number;\n /** Invalidate cached entries when upstream model output changes. Default false. */\n cacheInvalidateOnUpdate?: boolean;\n /** Persist request/response logs in the AI Gateway dashboard. Default true. */\n collectLogs?: boolean;\n /** Require an `Authorization: Bearer <token>` header on all gateway requests. Default false. */\n authentication?: boolean;\n /** Rate-limit window in seconds. Default 0 (rate limiting disabled). */\n rateLimitingInterval?: number;\n /** Max requests per window. Default 0 (rate limiting disabled). */\n rateLimitingLimit?: number;\n /** \"fixed\" or \"sliding\" window. Default \"fixed\". */\n rateLimitingTechnique?: \"fixed\" | \"sliding\";\n}\n\n/**\n * Cloudflare Pipeline (V1, SQL-based) managed by Tamer.\n *\n * Tamer creates the pipeline on `apply` against\n * `/accounts/{id}/pipelines/v1/pipelines` with the user-supplied derived\n * name and SQL. The server returns a unique `id` that Tamer stores in\n * state and emits as `pipelines[].pipeline` in generated wrangler config.\n *\n * Pipelines reference streams (sources) and sinks (destinations) by name\n * inside the SQL — those upstream/downstream resources are **not** managed\n * by Tamer in this iteration. Create them via the Cloudflare dashboard or\n * Wrangler before applying, otherwise the pipeline will exist in a\n * non-running status until they are. Drift / status surface this as the\n * pipeline's `status` field.\n */\nexport interface PipelineResourceConfig {\n logicalName: string;\n /**\n * Arroyo SQL describing the processing flow, e.g.\n * `insert into my_sink select * from my_stream;`. Stream and sink names\n * must already exist on Cloudflare (see {@link PipelineResourceConfig}).\n */\n sql: string;\n /**\n * Override the wrangler binding key. Defaults to\n * `PIPE_{LOGICAL}_T_{TENANT}` — uppercased logical with the tenant id\n * appended for cross-stack uniqueness.\n */\n binding?: string;\n}\n\n/** Origin connection for Hyperdrive (postgres / mysql). */\nexport interface HyperdriveOriginSpec {\n scheme: \"postgres\" | \"postgresql\" | \"mysql\";\n host: string;\n port?: number;\n database: string;\n user: string;\n /**\n * Origin password. Either an inline string OR `{ fromEnv: \"VAR\" }` to read\n * from `process.env` at apply time (recommended). Never persisted in state.\n */\n password: string | { fromEnv: string };\n /** Optional access client id (Cloudflare Access protected origins). */\n access_client_id?: string;\n access_client_secret?: string | { fromEnv: string };\n}\n\n/**\n * Declares a Cloudflare Workflow registration for a worker. The workflow\n * **class** itself lives in the worker's source code (extends\n * `WorkflowEntrypoint`); this config binds that class to a stable\n * Cloudflare-side workflow name and emits the `workflows[]` wrangler\n * binding so the worker can `env.{BINDING}.create(...)` instances.\n *\n * Tamer issues `PUT /accounts/{id}/workflows/{name}` on apply (idempotent;\n * Cloudflare treats PUT as upsert with `class_name` + `script_name`) and\n * `DELETE` on destroy. `script_name` defaults to the owning worker's\n * deployed name for `env`; override with {@link scriptName} when the class\n * is implemented in a sibling worker.\n */\nexport interface WorkflowResourceConfig {\n logicalName: string;\n /**\n * Class name exported by the worker that implements this workflow\n * (`export class BillingWorkflow extends WorkflowEntrypoint {...}` →\n * `className: \"BillingWorkflow\"`). Required by Cloudflare so it knows\n * which class in the bound script to instantiate per workflow run.\n */\n className: string;\n /**\n * Override the script that hosts the workflow class. Defaults to the\n * deployed name of the worker this resource is declared on (so dropping\n * the workflow into a different worker only needs moving the entry).\n * When set, written verbatim into the wrangler binding and the upsert\n * payload — Tamer does not env-suffix it.\n */\n scriptName?: string;\n /**\n * Override the wrangler binding key. Defaults to\n * `WF_{LOGICAL}_T_{TENANT}` — uppercased logical with the tenant id\n * appended for cross-stack uniqueness.\n */\n binding?: string;\n /**\n * Optional per-workflow execution limits. Mirrors Cloudflare's\n * `limits.steps` (max number of `step.do` calls per instance).\n */\n limits?: { steps?: number };\n}\n\n/**\n * Tamer-managed Cloudflare DNS record (zone-scoped).\n *\n * Declared at the **stack root** (`CfiConfigBase.dnsRecords`) rather than\n * per worker, because zone records have global identity (`zone + type +\n * name + content`) and shouldn't be redeclared per-worker. Tamer:\n *\n * - On `apply` for a new entry: `POST /zones/{zone_id}/dns_records` and\n * stores the assigned `recordId`.\n * - On `apply` for an existing entry whose mutable fields drifted from\n * state (`content`, `ttl`, `proxied`, `priority`, `comment`):\n * `PATCH /zones/{zone_id}/dns_records/{record_id}` (Cloudflare's\n * in-place update — works for everything except `type`/`name`).\n * - On `apply` for a `type` change: delete + recreate, since Cloudflare\n * rejects type changes on PATCH (and `name`-only changes are usually\n * semantic deletes anyway).\n * - On `destroy` of the stack (or when the entry is removed from config):\n * `DELETE /zones/{zone_id}/dns_records/{record_id}` and drops the row\n * from state.\n *\n * Tamer attaches a stable comment marker\n * (`tamer:<tenantId>:<env>:<logicalName>`) to every record it creates so\n * `tamer sync` and `tamer import` can rediscover orphaned rows after a\n * state loss.\n */\n/** Declared dataset for account Logpush — only `workers_trace_events` is implemented. */\nexport type LogpushWorkersTraceDataset = \"workers_trace_events\";\n\n/**\n * R2 destination for a Workers trace Logpush job. Requires S3-compatible\n * credentials (R2 API token) via the named environment variables at apply time.\n */\nexport interface LogpushJobR2Destination {\n /** Logical name of a Tamer-managed {@link R2ResourceConfig} in this stack. */\n bucketLogicalName: string;\n /** Path prefix inside the bucket before `{DATE}`; default `workers-trace-events`. */\n pathPrefix?: string;\n /** `process.env[name]` — R2 access key id for Logpush. */\n accessKeyIdEnv: string;\n /** `process.env[name]` — R2 secret access key for Logpush. */\n secretAccessKeyEnv: string;\n}\n\n/**\n * Logpush destination for **Pipelines HTTP ingest** (Workers trace → stream →\n * pipeline → sink). Tamer builds `destination_conf` as:\n * `https://{streamId}.ingest.cloudflare.com?pipeline_id={pipelineId}&header_Authorization=Bearer%20{token}`.\n *\n * The **stream**, **pipeline**, and **sink** must already exist (dashboard\n * wizard or Pipelines API). This block only encodes the Logpush wire format;\n * it does not create Pipelines resources.\n */\nexport interface LogpushJobPipelinesIngestDestination {\n /**\n * Pipelines **stream** id (32 hex characters, with or without UUID dashes).\n * Must match the stream that backs `https://…ingest.cloudflare.com`.\n */\n streamId: string;\n /** Pipelines **pipeline** id (32 hex characters, with or without dashes). */\n pipelineId: string;\n /**\n * `process.env[name]` — **ingest** Bearer token (dashboard “send token” /\n * stream HTTP auth). Becomes Logpush query param `header_Authorization`.\n */\n bearerTokenEnv: string;\n}\n\n/**\n * Fully automated **Workers trace → Pipelines → R2 Data Catalog (Iceberg)** path:\n * `tamer apply` creates the stream (trace schema), enables the catalog on the\n * bucket, mints two **account** API tokens (R2+Data Catalog + Pipelines Send),\n * stores the catalog credential, creates the `r2_data_catalog` sink, the SQL\n * pipeline, then the Logpush job. Mutually exclusive with\n * {@link LogpushJobPipelinesIngestDestination} and the other hand-supplied\n * `destination_conf` options.\n */\nexport interface LogpushJobPipelinesAutoDestination {\n /**\n * Tamer `resources.r2` logical name for the **catalog** bucket used as an\n * Apache Iceberg / **R2 Data Catalog** warehouse (not a separate R2 object\n * bucket used for raw NDJSON Logpush). That bucket must have **R2 Data Catalog** enabled. With\n * `pipelinesAuto`, Tamer calls `POST …/r2-catalog/{bucket}/enable` and\n * `…/credential` for you; if you provision the graph manually instead, enable\n * the catalog on this bucket in the R2 dashboard (or\n * `wrangler r2 bucket catalog enable`) before the `r2_data_catalog` sink can\n * work.\n */\n catalogBucketLogicalName: string;\n /** R2 Data Catalog / Iceberg namespace (default `default`). */\n namespace?: string;\n /**\n * Base Iceberg `table_name` in the catalog. On **new** `r2_data_catalog` sink\n * create, Tamer appends `_${Date.now()}` by default so the create path does\n * not collide (HTTP 422 / 1012) with a leftover table. Set\n * {@link tableNameAppendTimestamp} to `false` to use this string verbatim.\n */\n tableName: string;\n /**\n * When not `false`, new sinks use `${tableName}_${Date.now()}` (default). When\n * `false`, the sink uses `tableName` as-is. Existing sinks are unchanged.\n */\n tableNameAppendTimestamp?: boolean;\n /** Parquet `row_group_bytes` for the sink. Default 134217728. */\n sinkRowGroupBytes?: number;\n /** `rolling_policy.file_size_bytes` for the sink. Default 104857600. */\n sinkRollingFileSizeBytes?: number;\n /** `rolling_policy.interval_seconds` for the sink. Default 300. */\n sinkRollingIntervalSeconds?: number;\n}\n\nexport interface LogpushJobResourceConfig {\n logicalName: string;\n dataset: LogpushWorkersTraceDataset;\n /**\n * Cloudflare Logpush job `name` field. Defaults to\n * `tamer-{tenant.slug}-{logicalName}-{env}`.\n */\n jobName?: string;\n /** Build `destination_conf` for an R2 bucket in this stack. */\n r2?: LogpushJobR2Destination;\n /**\n * Build Logpush `destination_conf` for **Pipelines stream ingest** from stream\n * id, pipeline id, and ingest token (see {@link LogpushJobPipelinesIngestDestination}).\n * Mutually exclusive with {@link r2}, {@link destinationConfEnv},\n * {@link destinationConfFromJobId}, and {@link destinationConfFromJobIdEnv}.\n */\n pipelinesIngest?: LogpushJobPipelinesIngestDestination;\n /**\n * Create stream, R2 Data Catalog sink, and SQL pipeline via API, then Logpush\n * (see {@link LogpushJobPipelinesAutoDestination}).\n */\n pipelinesAuto?: LogpushJobPipelinesAutoDestination;\n /**\n * `process.env[name]` must hold the full Logpush `destination_conf` string.\n * Use for destinations where you paste the exact API value (escape hatch if\n * Cloudflare changes the Pipelines ingest URL shape).\n * Mutually exclusive with {@link r2}, {@link pipelinesIngest}, {@link pipelinesAuto},\n * {@link destinationConfFromJobId}, and {@link destinationConfFromJobIdEnv}.\n */\n destinationConfEnv?: string;\n /**\n * Bootstrap **Pipelines** (or any) `destination_conf` by reading an existing\n * account Logpush job via `GET …/logpush/jobs/{id}` — useful after creating a\n * template job in the dashboard. Tamer creates the managed job (canonical\n * name) with the same `destination_conf`. Mutually exclusive with {@link r2},\n * {@link pipelinesIngest}, {@link pipelinesAuto}, {@link destinationConfEnv}, and {@link destinationConfFromJobIdEnv}.\n */\n destinationConfFromJobId?: number;\n /**\n * `process.env[name]` must hold a positive integer job id; same behavior as\n * {@link destinationConfFromJobId} without hard-coding id in config.\n * Mutually exclusive with {@link r2}, {@link pipelinesIngest}, {@link pipelinesAuto},\n * {@link destinationConfEnv}, and {@link destinationConfFromJobId}.\n */\n destinationConfFromJobIdEnv?: string;\n /**\n * Optional Logpush **`filter`** (escaped JSON string with a top-level `where`\n * key). See [Logpush filters](https://developers.cloudflare.com/logs/logpush/logpush-job/filters/).\n * When using {@link destinationConfFromJobId} / {@link destinationConfFromJobIdEnv},\n * if unset, Tamer copies `filter` from the source job when present.\n */\n filter?: string;\n /**\n * Overrides **`output_options.field_names`** on the Logpush job. When\n * bootstrapping from {@link destinationConfFromJobId} / {@link destinationConfFromJobIdEnv},\n * other template **`output_options`** (e.g. `sample_rate`) are preserved unless\n * you replace them out-of-band on Cloudflare.\n */\n fieldNames?: string[];\n /** Default true. */\n enabled?: boolean;\n}\n\nexport interface DnsRecordResourceConfig {\n /** Stable identifier inside this stack (used in state keys and the comment marker). */\n logicalName: string;\n /** Cloudflare zone id this record lives in (e.g. `0123456789abcdef`). */\n zoneId: string;\n /**\n * Record type. Tamer supports the common edge / app types out of the\n * box. Anything else can still be expressed via the wrangler-side\n * config, but won't have lifecycle management here.\n */\n type:\n | \"A\"\n | \"AAAA\"\n | \"CNAME\"\n | \"TXT\"\n | \"MX\"\n | \"NS\"\n | \"CAA\"\n | \"SRV\"\n | \"PTR\"\n | \"HTTPS\"\n | \"SVCB\";\n /** DNS name (e.g. `app.example.com` or `@` for the apex). */\n name: string;\n /**\n * Record content (RDATA). Format depends on `type`:\n * - A → IPv4 address (`192.0.2.1`)\n * - AAAA → IPv6 address (`2001:db8::1`)\n * - CNAME / NS / PTR → hostname (`origin.example.com`)\n * - TXT → quoted text body (Tamer does not auto-quote — pass the\n * exact value Cloudflare should serve, e.g. `v=spf1 -all`)\n * - MX → mail server hostname (use {@link priority} for the preference)\n * - CAA → flags + tag + value as a single string\n * (`0 issue \"letsencrypt.org\"`)\n * - SRV → `priority weight port target` (e.g. `10 5 5223 server.example.com`)\n */\n content: string;\n /**\n * TTL in seconds. `1` means \"Auto\" (the Cloudflare default). Defaults to\n * `1` when unset.\n */\n ttl?: number;\n /**\n * Whether the record is proxied through Cloudflare (orange cloud).\n * Only meaningful for `A`, `AAAA`, and `CNAME`. Defaults to `false`.\n */\n proxied?: boolean;\n /**\n * Required for `MX`, `SRV`, and `URI` records. Ignored for other types.\n */\n priority?: number;\n /**\n * Free-form user comment appended to Tamer's attribution comment. Useful\n * for runbooks / \"why is this here\". Tamer always prefixes the comment\n * with `tamer:<tenantId>:<env>:<logicalName>` so live records can be\n * matched back to config even after state loss.\n */\n comment?: string;\n /**\n * Skip these envs (e.g. only create the record in `prod`). Default `[]`.\n * `local` is always implicitly skipped — DNS is a real-world side effect.\n */\n skipEnvs?: string[];\n /**\n * When true, `tamer destroy` does not delete this record (e.g. an apex\n * NS that should outlive the stack). Default false.\n */\n preserveOnDestroy?: boolean;\n}\n\n/** Declares a Workers for Platforms dispatch namespace to provision via `tamer apply`. */\nexport interface DispatchNamespaceResourceConfig {\n logicalName: string;\n /** Cloudflare dispatch namespace name (e.g. `workspace-workers`). */\n namespace: string;\n /**\n * When true, the real namespace name is `${namespace}-${env}` for non-`local`\n * envs. Skipped entirely for `local`.\n */\n envSuffix?: boolean;\n}\n\nexport interface WorkerResources {\n d1?: D1ResourceConfig[];\n r2?: R2ResourceConfig[];\n kv?: KVResourceConfig[];\n queues?: QueueResourceConfig[];\n hyperdrive?: HyperdriveResourceConfig[];\n vectorize?: VectorizeResourceConfig[];\n aiGateway?: AIGatewayResourceConfig[];\n pipelines?: PipelineResourceConfig[];\n workflows?: WorkflowResourceConfig[];\n /**\n * Cloudflare Secrets Store **stores** (account-scoped containers) managed by\n * Tamer. The secret values inside a store are intentionally **not** managed\n * here — they are written via Wrangler / dashboard / CI so secret material\n * never enters `tamer.config.ts` or `tamer-state-*`. Wire the actual\n * bindings via {@link secretsStoreSecrets} (which references stores by\n * logical name and resolves `store_id` from state at deploy time).\n */\n secretsStores?: SecretsStoreResourceConfig[];\n /**\n * Wrangler `secrets_store_secrets[]` bindings. **Not** Tamer-managed (no\n * state row, no apply/destroy lifecycle) — these are pure deploy-time\n * config that resolves the store reference (`store: <logicalName>`) into a\n * concrete `store_id` from a {@link SecretsStoreStateEntry} so wrangler\n * receives a stable id per env. The named secret must already exist in the\n * store (created out-of-band via wrangler `secrets-store secret create`).\n */\n secretsStoreSecrets?: SecretsStoreSecretBinding[];\n}\n\n/**\n * Cloudflare Secrets Store store managed by Tamer.\n *\n * Tamer creates the store on `apply` against\n * `POST /accounts/{id}/secrets_store/stores` (idempotent — a matching name\n * found via list short-circuits) and tracks the assigned `store_id` in\n * state. Cross-resource refs (`${tamer:secret_store:<n>.id|name}`) inject\n * the id into worker `vars` or other bindings at deploy time.\n *\n * Secret **values** are deliberately out of scope (rotation belongs in CI\n * / wrangler), so this resource has no `value` / `fromEnv` field — see\n * {@link SecretsStoreSecretBinding} for how to wire a secret into a worker.\n */\nexport interface SecretsStoreResourceConfig {\n logicalName: string;\n}\n\n/**\n * One row in the worker's wrangler `secrets_store_secrets[]` array. Pure\n * deploy-time config — Tamer resolves `store` (a {@link\n * SecretsStoreResourceConfig.logicalName}) to the recorded `store_id` and\n * passes `secret_name` through verbatim. The secret itself must already\n * exist in the referenced store.\n */\nexport interface SecretsStoreSecretBinding {\n /** Wrangler binding name (e.g. `API_KEY`). Available on `env.API_KEY` at runtime. */\n binding: string;\n /** Logical name of a {@link SecretsStoreResourceConfig} declared on this worker. */\n store: string;\n /** Name of the secret inside the referenced store (created out-of-band). */\n secretName: string;\n}\n\n/**\n * Declarative HTTP route managed by Tamer.\n *\n * Per `docs/handoff.md` §6, hostnames are env-asymmetric: prod uses the bare\n * apex (`todo.com`, `admin.platform.com`); every other env prefixes\n * (`staging.todo.com`, `dev.todo.com`, `pr-1234.todo.com`). Resource names\n * remain `-{env}` suffixed regardless.\n *\n * Tamer expands one `RouteResourceConfig` into a wrangler `Route` per env at\n * `resolveWorkerConfig` time. `local` (and any env in `skipEnvs`) yields no\n * route so `wrangler dev` is unaffected.\n *\n * For routes wrangler should pass through verbatim, set `routes` on the\n * worker (inherited from `WranglerConfig`) instead of `tamerRoutes`.\n */\nexport interface RouteResourceConfig {\n /**\n * Apex hostname. Prod uses this bare; other envs prefix it with `{env}.`.\n */\n host: string;\n /**\n * Cloudflare zone name for wrangler's `ZoneNameRoute`. Defaults to {@link host}.\n * Set explicitly when the apex differs from the zone (e.g. multi-tenant\n * subdomain on a parent zone).\n */\n zone?: string;\n /**\n * Path glob appended after the resolved host. Default `/*`.\n */\n path?: string;\n /**\n * When true, register as a Cloudflare `custom_domain` route (wrangler's\n * `CustomDomainRoute`). The path glob is ignored for custom domains.\n */\n customDomain?: boolean;\n /**\n * Envs that should yield no route. Defaults to `[\"local\"]`.\n */\n skipEnvs?: string[];\n /**\n * Envs treated as \"prod\" (bare host). Defaults to `[\"prod\", \"production\"]`.\n */\n prodEnvs?: string[];\n}\n\n// ── Worker Config ─────────────────────────────────────────────────────────────\n\ntype ManagedFields =\n | \"name\"\n | \"account_id\"\n | \"d1_databases\"\n | \"r2_buckets\"\n | \"kv_namespaces\"\n | \"queues\"\n | \"hyperdrive\"\n | \"vectorize\";\ntype BaseWranglerFields = Omit<WranglerConfig, ManagedFields>;\n\nexport interface EnvOverride extends Omit<\n BaseWranglerFields,\n \"vars\" | \"r2_buckets\"\n> {\n vars?: Record<string, TamerResolvableString>;\n /** Optional R2 buckets (passthrough to Wrangler; cross-stack imports). */\n r2_buckets?: WranglerR2BucketResolvable[];\n /** Overrides default generated Wrangler worker `name` for this environment. */\n scriptName?: string;\n /** Generated Wrangler config filename (default `wrangler.json`). */\n wranglerOutFile?: string;\n /** Per-env Tamer-managed routes (replaces base `tamerRoutes` for this env). */\n tamerRoutes?: RouteResourceConfig[];\n /**\n * Zone names (`route.zone_name`) where **`tamer deploy`** should delete stale\n * API-managed Workers routes still bound to this script but absent from resolved\n * `tamerRoutes`. Omit or leave empty for no pruning.\n */\n tamerStaleRouteSweepZones?: string[];\n}\n\nexport interface WorkerConfig extends Omit<\n BaseWranglerFields,\n \"vars\" | \"r2_buckets\" | \"env\" | \"local\"\n> {\n path?: string;\n config?: string;\n resources?: WorkerResources;\n /** Optional R2 buckets (passthrough to Wrangler; cross-stack imports). */\n r2_buckets?: WranglerR2BucketResolvable[];\n /** Tamer-managed routes; expanded per env into wrangler `routes[]`. */\n tamerRoutes?: RouteResourceConfig[];\n /**\n * Zone names where **`tamer deploy`** deletes orphaned Workers zone routes for\n * this script (patterns not in resolved `apiManagedRoutes`). Optional.\n */\n tamerStaleRouteSweepZones?: string[];\n alias?: Record<string, string>;\n vars?: Record<string, TamerResolvableString>;\n /**\n * When set, used as the Wrangler worker `name` (script name) instead of the default\n * `{slug}-{workerKey}-{env}-{tenantId}` pattern. Required for stable `services` targets.\n */\n scriptName?: string;\n /**\n * Basename of the generated Wrangler JSON file (default `wrangler.json`).\n * Use when multiple logical workers share one `path` (e.g. `wrangler.notes.json`).\n */\n wranglerOutFile?: string;\n /**\n * When set, `tamer deploy` passes `--dispatch-namespace` to Wrangler (Workers for Platforms user Worker upload).\n */\n dispatchNamespace?: string;\n local?: EnvOverride;\n env?: Record<string, EnvOverride>;\n}\n\n// ── Top-level Config ──────────────────────────────────────────────────────────\n\n/**\n * CloudFormation-style stack identity. Multiple stacks can coexist in the\n * same `tamer-state-{env}` D1 — each writes to its own `cfi_state:{name}`\n * row — so cross-stack `${tamer:import:<stackName>.<output>}` references\n * can find sibling stacks. When omitted, the stack name defaults to\n * `tenant.slug`, which preserves existing single-stack semantics: a fresh\n * single-stack codebase writes to `cfi_state:{tenant.slug}` (was\n * `cfi_state` pre-0.26 — greenfield rename, no migration).\n */\nexport interface CfiStackConfig {\n /**\n * Stable stack identifier — used as the D1 row-key suffix\n * (`cfi_state:{name}`) and the namespace siblings address with\n * `${tamer:import:<name>.<output>}`. Must match\n * `^[a-zA-Z][a-zA-Z0-9_-]*$` (CloudFormation-style identifier).\n * Defaults to `tenant.slug` when unset.\n */\n name?: string;\n /** Free-form human description; surfaced by `tamer status`. */\n description?: string;\n}\n\ninterface CfiConfigBase {\n tenant: TenantMeta;\n account_id?: string;\n compatibility_date?: string;\n /** Optional overrides for generated D1/R2/KV/worker names (see `NamingConventions`). */\n naming?: NamingConventions;\n /** CloudFormation-style stack identity. See {@link CfiStackConfig}. */\n stack?: CfiStackConfig;\n /** Workers for Platforms dispatch namespaces to create on `tamer apply` / track in state. */\n dispatchNamespaces?: DispatchNamespaceResourceConfig[];\n /**\n * Cloudflare DNS records to create / track / destroy at the **tenant\n * scope** (zone-scoped, but declared once per stack rather than per\n * worker, so the same record isn't accidentally duplicated across\n * workers). Tamer matches against the live zone via the recorded\n * `recordId` (and a Tamer-attribution comment for sync rediscovery).\n * Updates issue `PATCH /zones/{id}/dns_records/{record_id}` for any\n * mutable field that drifted from state; type changes follow\n * Cloudflare's delete-and-recreate convention (see\n * https://developers.cloudflare.com/fundamentals/api/reference/deprecations/).\n */\n dnsRecords?: DnsRecordResourceConfig[];\n /**\n * Account-scoped [Workers Trace Events Logpush](https://developers.cloudflare.com/workers/observability/logs/logpush/)\n * jobs (`workers_trace_events`). Created on `tamer apply` after worker-bound\n * resources (e.g. R2) exist in state. Set `logpush: true` on **each** Worker\n * script whose traces should export (not only dispatch).\n */\n logpushJobs?: LogpushJobResourceConfig[];\n /**\n * Named exports this stack publishes — Tamer's CloudFormation `Outputs`\n * analogue. Each value is a single `${tamer:<kind>:<logical>.<field>}`\n * reference (interpolation also works) resolved against this stack's own\n * state at apply time and persisted into `CfiState.stackOutputs` so it\n * survives across runs and is visible to `tamer status` / external tooling.\n *\n * Cross-stack consumption (a sibling stack reading these via\n * `${tamer:import:<stackName>.<outputName>}`) requires that the producing\n * stack has already run `tamer apply` against the same env/account.\n *\n * Example:\n * ```ts\n * outputs: {\n * userDbId: cf.d1(\"users\").id,\n * queueName: cf.queue(\"events\").name,\n * }\n * ```\n *\n * Output names must match `^[a-zA-Z][a-zA-Z0-9_-]*$` (CloudFormation-ish).\n * Resolution is **strict**: an unresolved reference fails the apply\n * (rolled back if `--rollback-on-failure`). Read-only commands (`plan`,\n * `drift`, `status`) tolerate unresolved refs and show the placeholder\n * verbatim.\n */\n outputs?: Record<string, TamerResolvableString>;\n}\n\nexport interface CfiConfigSingle extends CfiConfigBase {\n worker: WorkerConfig;\n workers?: never;\n}\n\nexport interface CfiConfigMulti extends CfiConfigBase {\n workers: Record<string, WorkerConfig>;\n worker?: never;\n}\n\nexport type CfiConfig = CfiConfigSingle | CfiConfigMulti;\n\n// ── Helper functions ──────────────────────────────────────────────────────────\n\nexport function defineConfig(config: CfiConfig): CfiConfig {\n return config;\n}\n\n/**\n * Optional metadata on env overlay objects. Stripped before merge; when set, it must\n * equal the CLI `--env` that selected this overlay (guards wrong file / copy-paste).\n */\nexport const TAMER_OVERLAY_ENV_KEY = \"tamerOverlayEnv\" as const;\n\n/**\n * Fragment merged onto {@link defineConfig} output from `tamer/env/<env>.config.ts`\n * (or flat `tamer.env.<env>.ts`). Typing is intentionally loose — shape is validated\n * after merge via the usual `CfiConfig` schema.\n *\n * Prefer the two-argument form so {@link TAMER_OVERLAY_ENV_KEY} is set and checked\n * against `--env`:\n *\n * ```ts\n * export default defineProjectOverlay(\"dev\", { account_id: \"…\" });\n * ```\n */\nexport function defineProjectOverlay<\n E extends string,\n T extends Record<string, unknown>,\n>(forEnv: E, fragment: T): T & { [K in typeof TAMER_OVERLAY_ENV_KEY]: E };\nexport function defineProjectOverlay<T extends Record<string, unknown>>(\n fragment: T,\n): T;\nexport function defineProjectOverlay(\n forEnvOrFragment: string | Record<string, unknown>,\n fragment?: Record<string, unknown>,\n): Record<string, unknown> {\n if (typeof forEnvOrFragment === \"string\") {\n if (\n fragment === undefined ||\n typeof fragment !== \"object\" ||\n fragment === null ||\n Array.isArray(fragment)\n ) {\n throw new Error(\n \"defineProjectOverlay(forEnv, fragment): fragment must be a plain object\",\n );\n }\n return {\n [TAMER_OVERLAY_ENV_KEY]: forEnvOrFragment,\n ...(fragment as Record<string, unknown>),\n };\n }\n return forEnvOrFragment as Record<string, unknown>;\n}\n\nexport function defineWorker(config: WorkerConfig): WorkerConfig {\n return config;\n}\n\nexport function getDispatchNamespaces(\n config: CfiConfig,\n): DispatchNamespaceResourceConfig[] {\n return config.dispatchNamespaces ?? [];\n}\n\nexport function getDnsRecords(config: CfiConfig): DnsRecordResourceConfig[] {\n return config.dnsRecords ?? [];\n}\n\nexport function getLogpushJobs(config: CfiConfig): LogpushJobResourceConfig[] {\n return config.logpushJobs ?? [];\n}\n\n// ── State ─────────────────────────────────────────────────────────────────────\n\nexport interface D1StateEntry {\n type: \"d1_database\";\n logicalName: string;\n shardDate?: string;\n derivedName: string;\n bindingKey: string;\n cfId: string;\n migrationsDir?: string;\n /** When true, `tamer destroy` skips deleting this D1. */\n preserveOnDestroy?: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface R2StateEntry {\n type: \"r2_bucket\";\n logicalName: string;\n createdDate: string;\n derivedName: string;\n bindingKey: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface KVStateEntry {\n type: \"kv_namespace\";\n logicalName: string;\n derivedName: string;\n bindingKey: string;\n cfId: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface QueueStateEntry {\n type: \"queue\";\n logicalName: string;\n derivedName: string;\n bindingKey: string;\n /** Cloudflare queue id (uuid). */\n cfId: string;\n /** Whether Tamer emits a producer binding for this queue (false for consumer-only). */\n producerBinding: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface VectorizeStateEntry {\n type: \"vectorize\";\n logicalName: string;\n derivedName: string;\n bindingKey: string;\n /** Vectorize index id (uuid) returned by the v2 create endpoint. */\n cfId: string;\n /** Immutable vector dimension. */\n dimensions: number;\n /** Immutable distance metric. */\n metric: \"cosine\" | \"euclidean\" | \"dot-product\";\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface HyperdriveStateEntry {\n type: \"hyperdrive\";\n logicalName: string;\n derivedName: string;\n bindingKey: string;\n /** Cloudflare hyperdrive config id. */\n cfId: string;\n /** Origin database engine (purely informational). */\n scheme: \"postgres\" | \"postgresql\" | \"mysql\";\n /** Origin host (purely informational). */\n originHost: string;\n /** Origin database name (purely informational). */\n originDatabase: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface AIGatewayStateEntry {\n type: \"ai_gateway\";\n logicalName: string;\n /** Cloudflare gateway id (== derived slug). */\n derivedName: string;\n /** Stable cross-reference binding key (no Wrangler binding emitted). */\n bindingKey: string;\n cfId: string;\n cacheTtl: number;\n cacheInvalidateOnUpdate: boolean;\n collectLogs: boolean;\n authentication: boolean;\n rateLimitingInterval: number;\n rateLimitingLimit: number;\n rateLimitingTechnique: \"fixed\" | \"sliding\";\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface PipelineStateEntry {\n type: \"pipeline\";\n logicalName: string;\n /** Tamer-derived pipeline name sent to Cloudflare on create. */\n derivedName: string;\n /** Wrangler binding key emitted in `pipelines[]`. */\n bindingKey: string;\n /** Server-assigned pipeline id (referenced from wrangler `pipeline`). */\n cfId: string;\n /** Pipeline SQL as last applied. Tracked for drift detection. */\n sql: string;\n /** Cloudflare-reported lifecycle status (e.g. \"running\", \"stopped\"). */\n status?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface WorkflowStateEntry {\n type: \"workflow\";\n logicalName: string;\n /** Tamer-derived workflow name sent to Cloudflare on PUT. */\n derivedName: string;\n /** Wrangler binding key emitted in `workflows[]`. */\n bindingKey: string;\n /** Server-assigned workflow id returned by PUT. */\n cfId: string;\n /** Class name of the `WorkflowEntrypoint` subclass (drift target). */\n className: string;\n /**\n * Worker script that hosts the class — either the owning worker's\n * deployed name, or `WorkflowResourceConfig.scriptName` verbatim.\n */\n scriptName: string;\n /** Optional execution-limit override last applied. */\n limits?: { steps?: number };\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface SecretsStoreStateEntry {\n type: \"secrets_store\";\n logicalName: string;\n /** Tamer-derived store name sent to Cloudflare on create. */\n derivedName: string;\n /** Stable cross-reference key (no Wrangler binding emitted directly). */\n bindingKey: string;\n /** Server-assigned store id (referenced from wrangler `secrets_store_secrets[].store_id`). */\n cfId: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DnsRecordStateEntry {\n type: \"dns_record\";\n logicalName: string;\n zoneId: string;\n recordType:\n | \"A\"\n | \"AAAA\"\n | \"CNAME\"\n | \"TXT\"\n | \"MX\"\n | \"NS\"\n | \"CAA\"\n | \"SRV\"\n | \"PTR\"\n | \"HTTPS\"\n | \"SVCB\";\n /** DNS name as recorded by Cloudflare (always FQDN; `@` is expanded to the zone apex). */\n name: string;\n /** Last-applied content, kept for drift comparison. */\n content: string;\n /** Last-applied TTL (seconds; `1` means \"Auto\"). */\n ttl: number;\n /** Last-applied proxied flag. */\n proxied: boolean;\n /** Last-applied priority for MX/SRV/URI; `undefined` for record types without one. */\n priority?: number;\n /** Full comment as written to Cloudflare (includes the Tamer attribution prefix). */\n comment: string;\n /** Cloudflare record id (`0123…`). */\n recordId: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DispatchNamespaceStateEntry {\n type: \"dispatch_namespace\";\n logicalName: string;\n /** Cloudflare namespace name. */\n derivedName: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface LogpushJobStateEntry {\n type: \"logpush_job\";\n logicalName: string;\n /** Cloudflare Logpush job display name (matches API `name`). */\n derivedName: string;\n /** Numeric job id from `POST /accounts/{id}/logpush/jobs`. */\n cfJobId: number;\n dataset: string;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Tamer-owned Pipelines graph for {@link LogpushJobPipelinesAutoDestination}\n * (stream, sink, SQL pipeline) once `ensurePipelinesLogpushProvision` runs.\n */\nexport interface LogpushPipelinesStateEntry {\n type: \"logpush_pipelines\";\n logicalName: string;\n streamId: string;\n /**\n * Ingest origin from Pipelines stream `endpoint` when the API returns it\n * (e.g. `https://….ingest.cloudflare.com`). Otherwise Logpush URL is built from {@link streamId}.\n */\n streamIngestBaseUrl?: string;\n sinkId: string;\n pipelineId: string;\n streamName: string;\n sinkName: string;\n pipelineName: string;\n /**\n * Table identifier to use in **R2 SQL** (`SHOW TABLES`, `SELECT`), after\n * Tamer normalizes Pipelines’ suffixed Iceberg name to the catalog/SQL name.\n */\n r2DataCatalogTableName?: string;\n /**\n * Table name **Pipelines / sink GET** reported before normalization (e.g.\n * `worker_trace_events_173…`). For display and debugging; queries use\n * {@link r2DataCatalogTableName}.\n */\n r2DataCatalogTableNamePipelines?: string;\n r2DataCatalogNamespace?: string;\n catalogBucketDerivedName: string;\n /**\n * Account API token id + secret minted for R2 + R2 Data Catalog (catalog\n * credential + `r2_data_catalog` sink). Secrets live only in Tamer state\n * (like dashboard-stored values, but file-local).\n */\n mintedR2CatalogTokenId?: string;\n mintedR2CatalogTokenValue?: string;\n /**\n * Account API token id + secret for [Workers Pipelines\n * Send](https://developers.cloudflare.com/pipelines/streams/writing-to-streams/)\n * (Logpush `destination_conf` stream ingest).\n */\n mintedPipelinesSendTokenId?: string;\n mintedPipelinesSendTokenValue?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface WorkerRouteStateEntry {\n type: \"worker_route\";\n workerKey: string;\n /** Deployed Worker script name (same as wrangler `name`). */\n workerName: string;\n zoneId: string;\n zoneName: string;\n routeId: string;\n pattern: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport type StateEntry =\n | D1StateEntry\n | R2StateEntry\n | KVStateEntry\n | QueueStateEntry\n | HyperdriveStateEntry\n | VectorizeStateEntry\n | AIGatewayStateEntry\n | PipelineStateEntry\n | WorkflowStateEntry\n | SecretsStoreStateEntry\n | DnsRecordStateEntry\n | DispatchNamespaceStateEntry\n | LogpushJobStateEntry\n | LogpushPipelinesStateEntry\n | WorkerRouteStateEntry;\n\n/** Provisioning lifecycle for a workspace tenant (`product` + `workspace`). */\nexport type ProvisioningStatus =\n | \"pending\"\n | \"d1_created\"\n | \"migrations_applied\"\n | \"script_uploaded\"\n | \"ready\"\n | \"tombstoned\";\n\nexport interface TenantD1ShardRef {\n role: string;\n derivedName: string;\n cfId: string;\n}\n\n/**\n * One runtime-provisioned tenant (dispatch script + optional D1 shards).\n * Keyed in {@link CfiState.tenants} by `product:workspace`.\n */\nexport interface TenantStateEntry {\n product: string;\n workspace: string;\n provisioningStatus: ProvisioningStatus;\n dispatchNamespaceName: string;\n scriptName: string;\n d1Shards?: TenantD1ShardRef[];\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Lightweight CloudFormation-style metadata about the deployment \"stack\" that\n * owns this state row. Optional everywhere — schema-version bump aware, but\n * commands set them when they have the information.\n */\nexport interface CfiStackMeta {\n /**\n * Human label for the stack (e.g. `external`, `internal`). Defaults to the\n * tenant slug when unset.\n */\n name?: string;\n /** Free-form owner string (team / pipeline). */\n owner?: string;\n}\n\nexport type CfiOperationName =\n | \"bootstrap\"\n | \"apply\"\n | \"deploy\"\n | \"destroy\"\n | \"provision-tenant\"\n | \"destroy-tenant\"\n | \"import\"\n | \"sync\";\n\nexport type CfiOperationStatus = \"in_progress\" | \"succeeded\" | \"failed\";\n\nexport interface CfiOperationRecord {\n command: CfiOperationName;\n status: CfiOperationStatus;\n startedAt: string;\n completedAt?: string;\n errorMessage?: string;\n /** Optional free-form context (target worker, tenant, etc.). */\n detail?: string;\n}\n\nexport interface CfiState {\n tenantId: string;\n env: string;\n schemaVersion: number;\n syncedAt: string;\n resources: Record<string, StateEntry>;\n /** Optimistic concurrency for D1 `cfi_state` row (see `StateManager.persist`). */\n revision?: number;\n /** Workspace tenants provisioned at runtime (not in `tamer.config.ts`). */\n tenants?: Record<string, TenantStateEntry>;\n /** Stack metadata (CloudFormation-style). Optional. */\n stack?: CfiStackMeta;\n /**\n * Resolved + persisted values for every entry in `tamer.config.ts > outputs`.\n * Written at the end of a successful `apply`; surfaced by `tamer status`\n * and consumed by sibling stacks via `${tamer:import:<stackName>.<key>}`.\n * Keys mirror `outputs` keys 1:1; entries are dropped when removed from\n * config or when the stack is destroyed.\n */\n stackOutputs?: Record<string, CfiStackOutputValue>;\n /** Last operation that touched this state row. */\n lastOperation?: CfiOperationRecord;\n /**\n * Completed operations only (`succeeded` / `failed`), newest first. Capped\n * at 50 on write when an operation finishes; surfaced by `tamer events`.\n */\n operationHistory?: CfiOperationRecord[];\n}\n\n/**\n * One persisted entry in {@link CfiState.stackOutputs}. Stores the resolved\n * literal alongside the original `${tamer:...}` source for diffing/debugging\n * and a timestamp so `tamer status` can show staleness.\n */\nexport interface CfiStackOutputValue {\n /** Resolved literal value (e.g. a D1 cfId, R2 bucket name, route URL). */\n value: string;\n /** The original `${tamer:...}` reference from `outputs` at resolve time. */\n source: string;\n /** ISO timestamp the value was last resolved + persisted. */\n resolvedAt: string;\n}\n\n// ── Status ────────────────────────────────────────────────────────────────────\n\nexport type ResourceStatus = \"ok\" | \"missing\" | \"pending\" | \"error\";\n\nexport interface WorkerStatus {\n workerKey: string;\n deployedName: string;\n route?: string;\n status: ResourceStatus;\n d1: Array<{\n binding: string;\n name: string;\n cfId: string;\n status: ResourceStatus;\n }>;\n r2: Array<{ binding: string; name: string; status: ResourceStatus }>;\n error?: string;\n}\n\nexport interface TenantStatus {\n tenant: TenantMeta;\n env: string;\n workers: WorkerStatus[];\n}\n\n// ── Naming conventions ────────────────────────────────────────────────────────\n\nexport interface NamingConventions {\n d1Single?: (logicalName: string, tenantId: string, env: string) => string;\n d1Shard?: (\n logicalName: string,\n date: string,\n tenantId: string,\n env: string,\n ) => string;\n /**\n * Override default `r2-{logical}-t-{tenantId}-{env}` bucket names.\n * `date` is `YYYYMMDD` (no dashes) from the day apply runs — use only if your scheme embeds a stamp; otherwise ignore it.\n */\n r2Bucket?: (\n logicalName: string,\n date: string,\n tenantId: string,\n env: string,\n ) => string;\n workerName?: (\n slug: string,\n workerKey: string,\n env: string,\n tenantId: string,\n ) => string;\n}\n","/**\n * Cloudflare-shaped authoring helpers for Tamer configs.\n *\n * Values are plain objects resolved by `materializeCloudflareBindings()` into the\n * same `${tamer:…}` strings the reference resolver already understands — no\n * second resolution path, no Cloudflare-agnostic indirection.\n */\n\nexport type CfResourceKind =\n | \"d1\"\n | \"r2\"\n | \"kv\"\n | \"queue\"\n | \"hyperdrive\"\n | \"vectorize\"\n | \"ai_gateway\"\n | \"pipeline\"\n | \"workflow\"\n | \"secret_store\";\n\nexport type CfResourceField = \"name\" | \"id\" | \"binding\";\n\nexport type CfLogpushPipelinesField =\n | \"r2_data_catalog_table_name\"\n | \"r2_data_catalog_table_name_pipelines\"\n | \"r2_data_catalog_namespace\"\n | \"name\"\n | \"id\"\n | \"iceberg_table\"\n | \"iceberg_table_pipelines\"\n | \"iceberg_namespace\";\n\nexport type CfBindingSpec =\n | { t: \"resource\"; kind: CfResourceKind; logical: string; field: CfResourceField }\n | { t: \"dispatch_namespace\"; logical: string; field: \"name\" | \"id\" }\n | { t: \"worker\"; workerKey: string; field: \"name\" }\n | { t: \"logpush_pipelines\"; logical: string; field: CfLogpushPipelinesField }\n | { t: \"config\"; logical: \"stack\"; field: \"account_id\" }\n | { t: \"import\"; stack: string; output: string };\n\n/**\n * Opaque handle materialized to a `${tamer:…}` reference before config parse.\n */\nexport class CfBinding {\n constructor(public readonly spec: CfBindingSpec) {}\n\n toRefString(): string {\n return cfBindingSpecToTamerRef(this.spec);\n }\n}\n\nexport function cfBindingSpecToTamerRef(spec: CfBindingSpec): string {\n switch (spec.t) {\n case \"resource\":\n return `\\${tamer:${spec.kind}:${spec.logical}.${spec.field}}`;\n case \"dispatch_namespace\":\n return `\\${tamer:dispatch_namespace:${spec.logical}.${spec.field}}`;\n case \"worker\":\n return `\\${tamer:worker:${spec.workerKey}.${spec.field}}`;\n case \"logpush_pipelines\":\n return `\\${tamer:logpush_pipelines:${spec.logical}.${spec.field}}`;\n case \"config\":\n return `\\${tamer:config:${spec.logical}.${spec.field}}`;\n case \"import\":\n return `\\${tamer:import:${spec.stack}.${spec.output}}`;\n default: {\n const _x: never = spec;\n return _x;\n }\n }\n}\n\nfunction resource(logical: string, kind: CfResourceKind) {\n return {\n get name(): CfBinding {\n return new CfBinding({ t: \"resource\", kind, logical, field: \"name\" });\n },\n get id(): CfBinding {\n return new CfBinding({ t: \"resource\", kind, logical, field: \"id\" });\n },\n get binding(): CfBinding {\n return new CfBinding({ t: \"resource\", kind, logical, field: \"binding\" });\n },\n };\n}\n\n/**\n * Fluent Cloudflare resource references for `vars`, `outputs`, `tamerRoutes`, etc.\n *\n * @example\n * ```ts\n * vars: {\n * BUCKET: cf.r2(\"assets\").name,\n * ACCOUNT: cf.stack.accountId,\n * }\n * ```\n */\nexport const cf = {\n d1: (logical: string) => resource(logical, \"d1\"),\n r2: (logical: string) => resource(logical, \"r2\"),\n kv: (logical: string) => resource(logical, \"kv\"),\n queue: (logical: string) => resource(logical, \"queue\"),\n hyperdrive: (logical: string) => resource(logical, \"hyperdrive\"),\n vectorize: (logical: string) => resource(logical, \"vectorize\"),\n aiGateway: (logical: string) => resource(logical, \"ai_gateway\"),\n pipeline: (logical: string) => resource(logical, \"pipeline\"),\n workflow: (logical: string) => resource(logical, \"workflow\"),\n secretStore: (logical: string) => resource(logical, \"secret_store\"),\n\n dispatchNamespace: (logical: string) => ({\n get name(): CfBinding {\n return new CfBinding({ t: \"dispatch_namespace\", logical, field: \"name\" });\n },\n get id(): CfBinding {\n return new CfBinding({ t: \"dispatch_namespace\", logical, field: \"id\" });\n },\n }),\n\n /** Deployed Worker script name for this env (Wrangler `name`). */\n worker: (workerKey: string) => ({\n get name(): CfBinding {\n return new CfBinding({ t: \"worker\", workerKey, field: \"name\" });\n },\n }),\n\n logpushPipelines: (logical: string) => ({\n get r2DataCatalogTableName(): CfBinding {\n return new CfBinding({\n t: \"logpush_pipelines\",\n logical,\n field: \"r2_data_catalog_table_name\",\n });\n },\n get r2DataCatalogTableNamePipelines(): CfBinding {\n return new CfBinding({\n t: \"logpush_pipelines\",\n logical,\n field: \"r2_data_catalog_table_name_pipelines\",\n });\n },\n get r2DataCatalogNamespace(): CfBinding {\n return new CfBinding({\n t: \"logpush_pipelines\",\n logical,\n field: \"r2_data_catalog_namespace\",\n });\n },\n get pipelineName(): CfBinding {\n return new CfBinding({ t: \"logpush_pipelines\", logical, field: \"name\" });\n },\n get pipelineId(): CfBinding {\n return new CfBinding({ t: \"logpush_pipelines\", logical, field: \"id\" });\n },\n /** Alias for {@link r2DataCatalogTableName}. */\n get icebergTable(): CfBinding {\n return new CfBinding({ t: \"logpush_pipelines\", logical, field: \"iceberg_table\" });\n },\n /** Alias for {@link r2DataCatalogTableNamePipelines}. */\n get icebergTablePipelines(): CfBinding {\n return new CfBinding({\n t: \"logpush_pipelines\",\n logical,\n field: \"iceberg_table_pipelines\",\n });\n },\n /** Alias for {@link r2DataCatalogNamespace}. */\n get icebergNamespace(): CfBinding {\n return new CfBinding({ t: \"logpush_pipelines\", logical, field: \"iceberg_namespace\" });\n },\n }),\n\n stack: {\n get accountId(): CfBinding {\n return new CfBinding({ t: \"config\", logical: \"stack\", field: \"account_id\" });\n },\n },\n\n /** Cross-stack: sibling stack name + published output key. */\n import: (stack: string, output: string) =>\n new CfBinding({ t: \"import\", stack, output }),\n} as const;\n\nexport function isCfBinding(x: unknown): x is CfBinding {\n return x instanceof CfBinding;\n}\n","import type { TamerResolvableString } from \"../types.js\";\nimport { CfBinding, isCfBinding } from \"./cloudflare-bindings.js\";\n\n/** Coerce a post-load or inline authoring value to a `${tamer:…}` / literal string. */\nexport function materializeTamerResolvable(v: TamerResolvableString): string {\n return isCfBinding(v) ? v.toRefString() : v;\n}\n\nexport function materializeVars(\n vars: Record<string, TamerResolvableString> | undefined,\n): Record<string, string> | undefined {\n if (!vars) return undefined;\n return Object.fromEntries(\n Object.entries(vars).map(([k, v]) => [k, materializeTamerResolvable(v)]),\n );\n}\n\n/**\n * Walk a config-shaped value and replace every {@link CfBinding} with its\n * `${tamer:…}` string. Preserves functions (e.g. `naming` callbacks) and\n * non-plain objects are left as-is after a shallow binding check.\n */\nexport function materializeCloudflareBindings(value: unknown): unknown {\n if (isCfBinding(value)) {\n return value.toRefString();\n }\n if (value === null || value === undefined) {\n return value;\n }\n const t = typeof value;\n if (t === \"string\" || t === \"number\" || t === \"boolean\" || t === \"bigint\") {\n return value;\n }\n if (t === \"function\") {\n return value;\n }\n if (value instanceof Date) {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map(materializeCloudflareBindings);\n }\n if (t !== \"object\") {\n return value;\n }\n\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = materializeCloudflareBindings(v);\n }\n return out;\n}\n"],"mappings":"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"}
|