@dragonmastery/tamer 0.40.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -5720,6 +5720,26 @@ interface TenantMeta {
5720
5720
  * — the config is the source of truth, the flag only trims.
5721
5721
  */
5722
5722
  d1Shards?: string[];
5723
+ /**
5724
+ * Maps each shard role (from `d1Shards[]`) to its binding name and
5725
+ * migration directory. Used by `wfp tenant provision` to resolve D1
5726
+ * bindings on the dispatch script, and by `wfp tenant migrate` to find
5727
+ * the migration source per shard.
5728
+ *
5729
+ * Example:
5730
+ * ```ts
5731
+ * shardBindings: {
5732
+ * system: { binding: "DB_SYSTEM", migrationsDir: "db/system" },
5733
+ * app: { binding: "DB_APP", migrationsDir: "db/app" },
5734
+ * history: { binding: "DB_HISTORY", migrationsDir: "db/history" },
5735
+ * }
5736
+ * ```
5737
+ */
5738
+ shardBindings?: Record<string, {
5739
+ binding: string;
5740
+ migrationsDir: string;
5741
+ migrationsTable?: string;
5742
+ }>;
5723
5743
  /**
5724
5744
  * Envs that require an explicit `--confirm-tenant <workspace>` (or
5725
5745
  * `--force`) before `destroy-tenant` will run. Defaults to
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/generated/wrangler-types.generated.ts","../src/generated/wrangler-types.ts","../src/dx/cloudflare-bindings.ts","../src/types.ts","../src/dx/normalize.ts","../src/dx/index.ts"],"sourcesContent":[],"mappings":";;AASA;;;;;AAOA;AAYA;AAaY,KAhCA,KAAA,GAgCA,MAAA,GAhCiB,WAgCI,GAhCU,aAgCV,GAhC0B,iBAgC1B;AA+mEjC;;;;;;AAiHU,KAzvEE,oBAAA,GAyvEF,UAAA,GAAA,UAAA,GAAA,cAAA,GAAA,MAAA,GAAA,MAAA,GAAA,cAAA,GAAA,mBAAA;;;;;AAiOkB,KA98EhB,IAAA,GA88EgB,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,IAAA,GAz8ExB,IAy8EwB,EAAA,GAAA;EA2BZ,CAAA,CAAA,EAAA,MAAA,CAAA,EAl+EG,IAk+EH;CAOA;;;;;AAozBF,KAvxGF,qBAAA,GAuxGE;EAOA;;;EAoiBe,IAAA,EAAA,MAAA;EAAqB;AAyOlD;AASA;EASiB,UAAA,EAAA,MAAA;EAYA;AA+BjB;AAWA;EAsDW,WAAA,CAAA,EAAA,MAAA;EAMQ;;;EAoBL,WAAA,CAAA,EAAA,MAAA;CAiCL,EAAA;;;;;;;;;UArnEQ,cAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAsDN;;;;;;mBAMQ,cAAc,gBAAgB;;;;;;;;;;;;;;;;;;;;eAoBlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6MI;oBACM;aACP;YACD;;;;;;;;;;;;;;;;;4BAiBgB;;;;;;;;;;;;;;;;;;;;;;;;;;;gBA2BZ;;;;;;;gBAOA;;;;;;;;;;;;;;;;;;;iBAmBC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2gBI;;;;;;+BAMU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAqPL;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2BZ;;;;;;;cAOA;;;;;;;;;;;;;;;;;;;eAmBC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA2gBI;;;;;;6BAMU;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2gBI;;;;;;+BAMU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAqPL;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2BZ;;;;;;;cAOA;;;;;;;;;;;;;;;;;;;eAmBC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/generated/wrangler-types.generated.ts","../src/generated/wrangler-types.ts","../src/dx/cloudflare-bindings.ts","../src/types.ts","../src/dx/normalize.ts","../src/dx/index.ts"],"sourcesContent":[],"mappings":";;AASA;;;;;AAOA;AAYA;AAaY,KAhCA,KAAA,GAgCA,MAAA,GAhCiB,WAgCI,GAhCU,aAgCV,GAhC0B,iBAgC1B;AA+mEjC;;;;;;AAiHU,KAzvEE,oBAAA,GAyvEF,UAAA,GAAA,UAAA,GAAA,cAAA,GAAA,MAAA,GAAA,MAAA,GAAA,cAAA,GAAA,mBAAA;;;;;AAiOkB,KA98EhB,IAAA,GA88EgB,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,IAAA,GAz8ExB,IAy8EwB,EAAA,GAAA;EA2BZ,CAAA,CAAA,EAAA,MAAA,CAAA,EAl+EG,IAk+EH;CAOA;;;;;AAozBF,KAvxGF,qBAAA,GAuxGE;EAOA;;;EAoiBe,IAAA,EAAA,MAAA;EAAqB;AAyOlD;AASA;EASiB,UAAA,EAAA,MAAA;EAYA;AA+BjB;AAWA;EAsDW,WAAA,CAAA,EAAA,MAAA;EAMQ;;;EAoBL,WAAA,CAAA,EAAA,MAAA;CAiCL,EAAA;;;;;;;;;UArnEQ,cAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAsDN;;;;;;mBAMQ,cAAc,gBAAgB;;;;;;;;;;;;;;;;;;;;eAoBlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6MI;oBACM;aACP;YACD;;;;;;;;;;;;;;;;;4BAiBgB;;;;;;;;;;;;;;;;;;;;;;;;;;;gBA2BZ;;;;;;;gBAOA;;;;;;;;;;;;;;;;;;;iBAmBC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2gBI;;;;;;+BAMU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAqPL;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2BZ;;;;;;;cAOA;;;;;;;;;;;;;;;;;;;eAmBC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA2gBI;;;;;;6BAMU;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2gBI;;;;;;+BAMU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAqPL;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2BZ;;;;;;;cAOA;;;;;;;;;;;;;;;;;;;eAmBC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
@@ -1 +1 @@
1
- {"version":3,"file":"normalize-DVSTRZhO.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/** Context for per-resource Cloudflare name resolution (e.g. sharded D1 shard date). */\nexport interface CloudflareNameContext {\n /** ISO `YYYY-MM-DD` shard stamp for sharded D1. */\n shardDate?: string;\n}\n\n/**\n * Per-resource Cloudflare name for the current env.\n * No UUIDs — name only. Used for sync, plan, apply, drift, import, and generate.\n *\n * Resolution order when unset: stack {@link NamingConventions} hook → {@link NamingEngine} default.\n */\nexport type CloudflareNameFn = (\n tenantId: string,\n env: string,\n ctx?: CloudflareNameContext,\n) => string;\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\n/**\n * Stack identity in `defineConfig({ tenant: … })`. `id` and `slug` feed\n * default resource names and state keys; `slug` is also the default\n * {@link CfiStackConfig.name} when `stack.name` is omitted.\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 * Environment variables injected into the tenant dispatch script's\n * metadata on `wfp tenant provision`. Values may contain\n * `${tamer:...}` references (resolved against state at provision time).\n * D1 bindings are derived automatically from `resources.d1[].registryRole`\n * matched to `d1Shards[]` — only non-D1 vars go here.\n */\n dispatchVars?: Record<string, TamerResolvableString>;\n /**\n * Service bindings injected into the tenant dispatch script's metadata.\n * Each entry produces `env.BINDING` on the tenant Worker pointing at\n * the named service (e.g. the portal-api Worker). `service` may contain\n * `${tamer:...}` references.\n */\n dispatchServices?: Array<{\n name: string;\n service: TamerResolvableString;\n environment?: string;\n }>;\n}\n\n// ── Resources ────────────────────────────────────────────────────────────────\n\n/**\n * D1 database declared on a worker's `resources.d1[]`.\n * Cloudflare **name** for managed databases follows\n * {@link D1ResourceConfig.cloudflareName} → {@link NamingConventions.d1Single} /\n * {@link NamingConventions.d1Shard} → defaults; **id** is stored in state after `sync` / `apply`.\n */\nexport interface D1ResourceConfig {\n logicalName: string;\n /** `\"single\"`: one DB per logical name. `\"sharded\"`: date-stamped shards (see `d1Shard` naming hook). */\n type: \"single\" | \"sharded\";\n /**\n * Optional per-resource Cloudflare database name. For sharded D1, receives\n * {@link CloudflareNameContext.shardDate}. External D1 uses {@link databaseName} instead.\n */\n cloudflareName?: CloudflareNameFn;\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 /**\n * When set, used as the Wrangler D1 `binding` instead of the generated name.\n * Applies to `type: \"single\"` and `type: \"sharded\"`.\n */\n binding?: string;\n /**\n * Pin the shard date for `type: \"sharded\"` (ISO `YYYY-MM-DD` or `YYYYMMDD`).\n * Brownfield: match one physical shard during sync/apply. Greenfield: first shard date.\n */\n shardDate?: string;\n /**\n * Role label in the generated shard registry (defaults to {@link logicalName}).\n */\n registryRole?: string;\n /** Prior shard id strings that decode to this shard (brownfield aliases). */\n legacyIds?: string[];\n /** Prior wrangler binding keys for this shard (dual-bind / rename history). */\n legacyBindings?: string[];\n /** When false, shard is read-only for new writes (multi-shard same role). Default true. */\n current?: boolean;\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\n/** One row in {@link ShardRegistryV1}. */\nexport interface ShardRegistryEntryV1 {\n /** Logical shard id embedded in universal IDs (brownfield: CF database name). */\n id: string;\n /** App vocabulary — typically the D1 `logicalName` or {@link D1ResourceConfig.registryRole}. */\n role: string;\n /** Current Wrangler D1 binding for this env. */\n binding: string;\n /** Physical Cloudflare D1 database name for this env. */\n databaseName: string;\n shardDate?: string;\n /** When multiple shards share a role, marks the write target. Default true. */\n current?: boolean;\n legacyBindings?: string[];\n legacyIds?: string[];\n}\n\n/** Versioned shard registry emitted by `tamer apply` when `codegen.shardRegistry` is set. */\nexport interface ShardRegistryV1 {\n version: 1;\n shards: ShardRegistryEntryV1[];\n}\n\n/** Options for {@link CodegenConfig.shardRegistry}. */\nexport interface ShardRegistryCodegenConfig {\n /**\n * Worker key that receives the generated module. Required when the stack\n * declares `workers` with more than one entry.\n */\n worker?: string;\n /** Path relative to the worker directory. Default: `src/db/shard-registry.ts`. */\n outFile?: string;\n /** Exported constant name. Default: `shardRegistry`. */\n exportName?: string;\n}\n\nexport interface CodegenConfig {\n /** Emit a {@link ShardRegistryV1} module after wrangler generation on `apply`. */\n shardRegistry?: ShardRegistryCodegenConfig;\n}\n\n/** R2 bucket on `resources.r2[]`. Name from {@link cloudflareName} → {@link NamingConventions.r2Bucket} → default. */\nexport interface R2ResourceConfig {\n logicalName: string;\n /** Optional per-resource Cloudflare bucket name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare KV namespace name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare queue name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare Hyperdrive config name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare Vectorize index name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare AI Gateway slug override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare Pipeline name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare workflow registration name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare Secrets Store name override. */\n cloudflareName?: CloudflareNameFn;\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/**\n * Declares secrets a worker requires at deploy/runtime.\n *\n * **Names only — never values.** Secret material lives in the Tamer vault\n * (`tamer secrets set` / `load`) and reaches Cloudflare via `push` / `deploy`.\n * {@link WorkerSecretsConfig.required} is reconciled by `plan` and `drift`\n * against vault fingerprints and deployed worker presence.\n */\nexport interface WorkerSecretsConfig {\n /**\n * Logical secret names this worker needs at runtime (e.g. `STRIPE_KEY`).\n * Values are stored in the Tamer vault via `tamer secrets set` / `load` and\n * pushed to Cloudflare on `tamer secrets push` or `tamer deploy`. Reconciled\n * by `plan`, `drift`, and `tamer secrets verify` against vault fingerprints\n * and {@link SecretStateEntry.lastPushedHash} — never put plaintext here.\n */\n required: string[];\n}\n\n// ── Worker Config ─────────────────────────────────────────────────────────────\n\n/**\n * Pre-deploy build step for a worker (e.g. SPA asset compilation via\n * `vite build`). When set, `tamer deploy` spawns `command` in the worker's\n * `path` directory with the worker's resolved `vars` (references resolved\n * against state for the target env) as environment variables, so compile-time\n * substitution tools (Vite `VITE_*`, esbuild `define`, etc.) read values\n * authored in Tamer config directly — no `.env` file or codegen step.\n * See [docs/spa-build-config.md](../docs/spa-build-config.md).\n */\nexport interface WorkerBuildConfig {\n /**\n * Shell command run in the worker's `path` directory after `wrangler.json`\n * is written but before `wrangler types` / `wrangler deploy`. The worker's\n * resolved `vars` are passed as environment variables (merged onto\n * `process.env`).\n */\n command: string;\n}\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 /** Per-env override for the worker's pre-deploy {@link WorkerBuildConfig}. */\n build?: WorkerBuildConfig;\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 /**\n * Required worker secrets (names only). See {@link WorkerSecretsConfig}.\n * Absent block means no declared secrets for this worker.\n */\n secrets?: WorkerSecretsConfig;\n /** Pre-deploy build step (e.g. `vite build` for SPA workers). See {@link WorkerBuildConfig}. */\n build?: WorkerBuildConfig;\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 /**\n * Optional overrides for Cloudflare-side **names** (D1, R2, worker scripts,\n * workflows). Use for brownfield stacks whose live names differ from Tamer\n * defaults; IDs stay in state after `sync` / `apply`, never in config.\n * @see {@link NamingConventions}\n * @see docs/brownfield-adoption.md\n */\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 * Apply-time code generation hooks. Artifacts are gitignored siblings of\n * `wrangler.json` unless your stack commits them deliberately.\n */\n codegen?: CodegenConfig;\n}\n\n/** Single-worker stack: one top-level `worker` (no `workers` map). */\nexport interface CfiConfigSingle extends CfiConfigBase {\n worker: WorkerConfig;\n workers?: never;\n}\n\n/** Multi-worker stack: `workers` keyed by deploy target (e.g. `api`, `admin`). */\nexport interface CfiConfigMulti extends CfiConfigBase {\n workers: Record<string, WorkerConfig>;\n worker?: never;\n}\n\n/**\n * Root document for `tamer/project.config.ts` (or `tamer.project.config.ts`).\n * Single-worker stacks use {@link CfiConfigSingle}; multi-worker use\n * {@link CfiConfigMulti}.\n */\nexport type CfiConfig = CfiConfigSingle | CfiConfigMulti;\n\n// ── Helper functions ──────────────────────────────────────────────────────────\n\n/**\n * Identity helper for `tamer/project.config.ts` so the config object is typed\n * as {@link CfiConfig} in the IDE. No runtime transformation — the CLI loads\n * and validates the exported default.\n *\n * @example\n * ```ts\n * export default defineConfig({\n * tenant: { id: \"acme\", name: \"Acme\", slug: \"acme\" },\n * workers: { api: { main: \"workers/api/src/index.ts\" } },\n * });\n * ```\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\n/**\n * Identity helper for one entry in `workers` (or the sole `worker` field).\n * Same pattern as {@link defineConfig} — improves hover types only.\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\n/**\n * Last-pushed fingerprint for a worker secret — **no secret material**.\n *\n * Written by `tamer secrets push` and `tamer deploy` after a successful CF API\n * PUT. Stored in {@link CfiState.resources} under key `secret:{worker}:{name}`.\n * Compared to the vault row's `value_hash` to detect rotation (`vault hash !=\n * lastPushedHash` → needs push). Keyed per secret × worker × env so the same\n * secret can be current on one worker and stale on another.\n */\nexport interface SecretStateEntry {\n type: \"secret\";\n /** Worker key from `tamer.config.ts` (e.g. `api`). */\n worker: string;\n /** Logical secret name (e.g. `STRIPE_KEY`). */\n name: string;\n /** Vault `value_hash` last PUT to this worker for this env. */\n lastPushedHash: string;\n /** ISO timestamp of the last successful push. */\n lastPushedAt: 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 | SecretStateEntry;\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\n/**\n * Optional functions that derive **Cloudflare resource names** from config\n * (`logicalName`, `tenant.id`, `env`, etc.). Used when a resource has no\n * {@link CloudflareNameFn} override on its config.\n *\n * **Resolution order (managed resources):**\n * 1. `resource.cloudflareName?(tenantId, env, ctx?)`\n * 2. stack `naming.{kindHook}?(...)` (this interface)\n * 3. {@link NamingEngine} built-in default\n *\n * **Greenfield:** omit both per-resource overrides and this block.\n *\n * **Brownfield:** use per-resource `cloudflareName` when names differ per\n * logical resource; use stack hooks for shared formulas. Config holds logical\n * names and hooks; **state** holds `cfId` after `tamer sync`.\n *\n * @see docs/brownfield-adoption.md\n * @see docs/per-resource-cloudflare-naming.md\n */\nexport interface NamingConventions {\n /**\n * Cloudflare D1 database name for `resources.d1[]` with `type: \"single\"`.\n *\n * **Default:** `db_{logicalName}_t_{tenantId}_{env}`.\n *\n * **`sync`:** exact match on the derived name.\n */\n d1Single?: (logicalName: string, tenantId: string, env: string) => string;\n /**\n * Cloudflare D1 database name for `resources.d1[]` with `type: \"sharded\"`.\n *\n * **`date`:** shard stamp passed to apply (`YYYY-MM-DD` or compact\n * `YYYYMMDD` after normalization). Your hook should embed it the same way\n * legacy DBs do (often `date.replace(/-/g, \"\")`).\n *\n * **Default:** `db_{logicalName}_{YYYYMMDD}_t_{tenantId}_{env}` (omits\n * `{logicalName}_` when logical is `default` or empty).\n *\n * **`sync`:** with a custom hook, **derive-and-match** — Tamer parses the\n * shard date from each account D1 name (`_YYYYMMDD_t_` or `_YYYY_MM_DD_t_`),\n * re-derives via this function, and adopts when the names are equal. Without\n * a hook, prefix/suffix regex matching is used instead.\n */\n d1Shard?: (\n logicalName: string,\n date: string,\n tenantId: string,\n env: string,\n ) => string;\n /**\n * R2 bucket name for `resources.r2[]`.\n *\n * **`date`:** `YYYYMMDD` (no dashes) from the calendar day **apply** or\n * **sync** runs. Ignore unless your legacy scheme embeds a date stamp.\n *\n * **Default:** `r2-{logicalName}-t-{tenantId}-{env}` (no date segment).\n *\n * **`sync`:** exact match on the name produced by calling this hook with\n * today's date (same as apply). Legacy dated buckets\n * `r2-{logical}-YYYYMMDD-t-{tenant}-{env}` still match when the hook is\n * omitted.\n */\n r2Bucket?: (\n logicalName: string,\n date: string,\n tenantId: string,\n env: string,\n ) => string;\n /**\n * Deployed Worker **script** name (wrangler `name` / `tamer deploy` target).\n *\n * **Default:** `{slug}-{workerKey}-{tenantId}` for `local`; otherwise\n * `{slug}-{workerKey}-{env}-{tenantId}`. Overridable per worker via\n * {@link WorkerConfig.scriptName}.\n *\n * **`sync`:** worker script list is matched by deployed name (routes use\n * the same derivation).\n */\n workerName?: (\n slug: string,\n workerKey: string,\n env: string,\n tenantId: string,\n ) => string;\n /**\n * Cloudflare workflow **registration** name for `resources.workflows[]`.\n * Workflow names are immutable on Cloudflare — brownfield stacks must\n * return the live registration name here.\n *\n * **Default:** `wf-{logicalName}-t-{tenantId}-{env}` (lowercased).\n *\n * **`sync`:** exact match on the derived name (same as D1 single / default\n * R2). Prefer `tamer import --kind workflow` only when a legacy name cannot\n * be expressed as a function of logical name, tenant, and env.\n */\n workflow?: (logicalName: string, tenantId: string, env: string) => 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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"}
1
+ {"version":3,"file":"normalize-DVSTRZhO.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/** Context for per-resource Cloudflare name resolution (e.g. sharded D1 shard date). */\nexport interface CloudflareNameContext {\n /** ISO `YYYY-MM-DD` shard stamp for sharded D1. */\n shardDate?: string;\n}\n\n/**\n * Per-resource Cloudflare name for the current env.\n * No UUIDs — name only. Used for sync, plan, apply, drift, import, and generate.\n *\n * Resolution order when unset: stack {@link NamingConventions} hook → {@link NamingEngine} default.\n */\nexport type CloudflareNameFn = (\n tenantId: string,\n env: string,\n ctx?: CloudflareNameContext,\n) => string;\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\n/**\n * Stack identity in `defineConfig({ tenant: … })`. `id` and `slug` feed\n * default resource names and state keys; `slug` is also the default\n * {@link CfiStackConfig.name} when `stack.name` is omitted.\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 * Maps each shard role (from `d1Shards[]`) to its binding name and\n * migration directory. Used by `wfp tenant provision` to resolve D1\n * bindings on the dispatch script, and by `wfp tenant migrate` to find\n * the migration source per shard.\n *\n * Example:\n * ```ts\n * shardBindings: {\n * system: { binding: \"DB_SYSTEM\", migrationsDir: \"db/system\" },\n * app: { binding: \"DB_APP\", migrationsDir: \"db/app\" },\n * history: { binding: \"DB_HISTORY\", migrationsDir: \"db/history\" },\n * }\n * ```\n */\n shardBindings?: Record<string, {\n binding: string;\n migrationsDir: string;\n migrationsTable?: string;\n }>;\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 * Environment variables injected into the tenant dispatch script's\n * metadata on `wfp tenant provision`. Values may contain\n * `${tamer:...}` references (resolved against state at provision time).\n * D1 bindings are derived automatically from `resources.d1[].registryRole`\n * matched to `d1Shards[]` — only non-D1 vars go here.\n */\n dispatchVars?: Record<string, TamerResolvableString>;\n /**\n * Service bindings injected into the tenant dispatch script's metadata.\n * Each entry produces `env.BINDING` on the tenant Worker pointing at\n * the named service (e.g. the portal-api Worker). `service` may contain\n * `${tamer:...}` references.\n */\n dispatchServices?: Array<{\n name: string;\n service: TamerResolvableString;\n environment?: string;\n }>;\n}\n\n// ── Resources ────────────────────────────────────────────────────────────────\n\n/**\n * D1 database declared on a worker's `resources.d1[]`.\n * Cloudflare **name** for managed databases follows\n * {@link D1ResourceConfig.cloudflareName} → {@link NamingConventions.d1Single} /\n * {@link NamingConventions.d1Shard} → defaults; **id** is stored in state after `sync` / `apply`.\n */\nexport interface D1ResourceConfig {\n logicalName: string;\n /** `\"single\"`: one DB per logical name. `\"sharded\"`: date-stamped shards (see `d1Shard` naming hook). */\n type: \"single\" | \"sharded\";\n /**\n * Optional per-resource Cloudflare database name. For sharded D1, receives\n * {@link CloudflareNameContext.shardDate}. External D1 uses {@link databaseName} instead.\n */\n cloudflareName?: CloudflareNameFn;\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 /**\n * When set, used as the Wrangler D1 `binding` instead of the generated name.\n * Applies to `type: \"single\"` and `type: \"sharded\"`.\n */\n binding?: string;\n /**\n * Pin the shard date for `type: \"sharded\"` (ISO `YYYY-MM-DD` or `YYYYMMDD`).\n * Brownfield: match one physical shard during sync/apply. Greenfield: first shard date.\n */\n shardDate?: string;\n /**\n * Role label in the generated shard registry (defaults to {@link logicalName}).\n */\n registryRole?: string;\n /** Prior shard id strings that decode to this shard (brownfield aliases). */\n legacyIds?: string[];\n /** Prior wrangler binding keys for this shard (dual-bind / rename history). */\n legacyBindings?: string[];\n /** When false, shard is read-only for new writes (multi-shard same role). Default true. */\n current?: boolean;\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\n/** One row in {@link ShardRegistryV1}. */\nexport interface ShardRegistryEntryV1 {\n /** Logical shard id embedded in universal IDs (brownfield: CF database name). */\n id: string;\n /** App vocabulary — typically the D1 `logicalName` or {@link D1ResourceConfig.registryRole}. */\n role: string;\n /** Current Wrangler D1 binding for this env. */\n binding: string;\n /** Physical Cloudflare D1 database name for this env. */\n databaseName: string;\n shardDate?: string;\n /** When multiple shards share a role, marks the write target. Default true. */\n current?: boolean;\n legacyBindings?: string[];\n legacyIds?: string[];\n}\n\n/** Versioned shard registry emitted by `tamer apply` when `codegen.shardRegistry` is set. */\nexport interface ShardRegistryV1 {\n version: 1;\n shards: ShardRegistryEntryV1[];\n}\n\n/** Options for {@link CodegenConfig.shardRegistry}. */\nexport interface ShardRegistryCodegenConfig {\n /**\n * Worker key that receives the generated module. Required when the stack\n * declares `workers` with more than one entry.\n */\n worker?: string;\n /** Path relative to the worker directory. Default: `src/db/shard-registry.ts`. */\n outFile?: string;\n /** Exported constant name. Default: `shardRegistry`. */\n exportName?: string;\n}\n\nexport interface CodegenConfig {\n /** Emit a {@link ShardRegistryV1} module after wrangler generation on `apply`. */\n shardRegistry?: ShardRegistryCodegenConfig;\n}\n\n/** R2 bucket on `resources.r2[]`. Name from {@link cloudflareName} → {@link NamingConventions.r2Bucket} → default. */\nexport interface R2ResourceConfig {\n logicalName: string;\n /** Optional per-resource Cloudflare bucket name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare KV namespace name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare queue name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare Hyperdrive config name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare Vectorize index name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare AI Gateway slug override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare Pipeline name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare workflow registration name override. */\n cloudflareName?: CloudflareNameFn;\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 /** Optional per-resource Cloudflare Secrets Store name override. */\n cloudflareName?: CloudflareNameFn;\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/**\n * Declares secrets a worker requires at deploy/runtime.\n *\n * **Names only — never values.** Secret material lives in the Tamer vault\n * (`tamer secrets set` / `load`) and reaches Cloudflare via `push` / `deploy`.\n * {@link WorkerSecretsConfig.required} is reconciled by `plan` and `drift`\n * against vault fingerprints and deployed worker presence.\n */\nexport interface WorkerSecretsConfig {\n /**\n * Logical secret names this worker needs at runtime (e.g. `STRIPE_KEY`).\n * Values are stored in the Tamer vault via `tamer secrets set` / `load` and\n * pushed to Cloudflare on `tamer secrets push` or `tamer deploy`. Reconciled\n * by `plan`, `drift`, and `tamer secrets verify` against vault fingerprints\n * and {@link SecretStateEntry.lastPushedHash} — never put plaintext here.\n */\n required: string[];\n}\n\n// ── Worker Config ─────────────────────────────────────────────────────────────\n\n/**\n * Pre-deploy build step for a worker (e.g. SPA asset compilation via\n * `vite build`). When set, `tamer deploy` spawns `command` in the worker's\n * `path` directory with the worker's resolved `vars` (references resolved\n * against state for the target env) as environment variables, so compile-time\n * substitution tools (Vite `VITE_*`, esbuild `define`, etc.) read values\n * authored in Tamer config directly — no `.env` file or codegen step.\n * See [docs/spa-build-config.md](../docs/spa-build-config.md).\n */\nexport interface WorkerBuildConfig {\n /**\n * Shell command run in the worker's `path` directory after `wrangler.json`\n * is written but before `wrangler types` / `wrangler deploy`. The worker's\n * resolved `vars` are passed as environment variables (merged onto\n * `process.env`).\n */\n command: string;\n}\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 /** Per-env override for the worker's pre-deploy {@link WorkerBuildConfig}. */\n build?: WorkerBuildConfig;\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 /**\n * Required worker secrets (names only). See {@link WorkerSecretsConfig}.\n * Absent block means no declared secrets for this worker.\n */\n secrets?: WorkerSecretsConfig;\n /** Pre-deploy build step (e.g. `vite build` for SPA workers). See {@link WorkerBuildConfig}. */\n build?: WorkerBuildConfig;\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 /**\n * Optional overrides for Cloudflare-side **names** (D1, R2, worker scripts,\n * workflows). Use for brownfield stacks whose live names differ from Tamer\n * defaults; IDs stay in state after `sync` / `apply`, never in config.\n * @see {@link NamingConventions}\n * @see docs/brownfield-adoption.md\n */\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 * Apply-time code generation hooks. Artifacts are gitignored siblings of\n * `wrangler.json` unless your stack commits them deliberately.\n */\n codegen?: CodegenConfig;\n}\n\n/** Single-worker stack: one top-level `worker` (no `workers` map). */\nexport interface CfiConfigSingle extends CfiConfigBase {\n worker: WorkerConfig;\n workers?: never;\n}\n\n/** Multi-worker stack: `workers` keyed by deploy target (e.g. `api`, `admin`). */\nexport interface CfiConfigMulti extends CfiConfigBase {\n workers: Record<string, WorkerConfig>;\n worker?: never;\n}\n\n/**\n * Root document for `tamer/project.config.ts` (or `tamer.project.config.ts`).\n * Single-worker stacks use {@link CfiConfigSingle}; multi-worker use\n * {@link CfiConfigMulti}.\n */\nexport type CfiConfig = CfiConfigSingle | CfiConfigMulti;\n\n// ── Helper functions ──────────────────────────────────────────────────────────\n\n/**\n * Identity helper for `tamer/project.config.ts` so the config object is typed\n * as {@link CfiConfig} in the IDE. No runtime transformation — the CLI loads\n * and validates the exported default.\n *\n * @example\n * ```ts\n * export default defineConfig({\n * tenant: { id: \"acme\", name: \"Acme\", slug: \"acme\" },\n * workers: { api: { main: \"workers/api/src/index.ts\" } },\n * });\n * ```\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\n/**\n * Identity helper for one entry in `workers` (or the sole `worker` field).\n * Same pattern as {@link defineConfig} — improves hover types only.\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\n/**\n * Last-pushed fingerprint for a worker secret — **no secret material**.\n *\n * Written by `tamer secrets push` and `tamer deploy` after a successful CF API\n * PUT. Stored in {@link CfiState.resources} under key `secret:{worker}:{name}`.\n * Compared to the vault row's `value_hash` to detect rotation (`vault hash !=\n * lastPushedHash` → needs push). Keyed per secret × worker × env so the same\n * secret can be current on one worker and stale on another.\n */\nexport interface SecretStateEntry {\n type: \"secret\";\n /** Worker key from `tamer.config.ts` (e.g. `api`). */\n worker: string;\n /** Logical secret name (e.g. `STRIPE_KEY`). */\n name: string;\n /** Vault `value_hash` last PUT to this worker for this env. */\n lastPushedHash: string;\n /** ISO timestamp of the last successful push. */\n lastPushedAt: 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 | SecretStateEntry;\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\n/**\n * Optional functions that derive **Cloudflare resource names** from config\n * (`logicalName`, `tenant.id`, `env`, etc.). Used when a resource has no\n * {@link CloudflareNameFn} override on its config.\n *\n * **Resolution order (managed resources):**\n * 1. `resource.cloudflareName?(tenantId, env, ctx?)`\n * 2. stack `naming.{kindHook}?(...)` (this interface)\n * 3. {@link NamingEngine} built-in default\n *\n * **Greenfield:** omit both per-resource overrides and this block.\n *\n * **Brownfield:** use per-resource `cloudflareName` when names differ per\n * logical resource; use stack hooks for shared formulas. Config holds logical\n * names and hooks; **state** holds `cfId` after `tamer sync`.\n *\n * @see docs/brownfield-adoption.md\n * @see docs/per-resource-cloudflare-naming.md\n */\nexport interface NamingConventions {\n /**\n * Cloudflare D1 database name for `resources.d1[]` with `type: \"single\"`.\n *\n * **Default:** `db_{logicalName}_t_{tenantId}_{env}`.\n *\n * **`sync`:** exact match on the derived name.\n */\n d1Single?: (logicalName: string, tenantId: string, env: string) => string;\n /**\n * Cloudflare D1 database name for `resources.d1[]` with `type: \"sharded\"`.\n *\n * **`date`:** shard stamp passed to apply (`YYYY-MM-DD` or compact\n * `YYYYMMDD` after normalization). Your hook should embed it the same way\n * legacy DBs do (often `date.replace(/-/g, \"\")`).\n *\n * **Default:** `db_{logicalName}_{YYYYMMDD}_t_{tenantId}_{env}` (omits\n * `{logicalName}_` when logical is `default` or empty).\n *\n * **`sync`:** with a custom hook, **derive-and-match** — Tamer parses the\n * shard date from each account D1 name (`_YYYYMMDD_t_` or `_YYYY_MM_DD_t_`),\n * re-derives via this function, and adopts when the names are equal. Without\n * a hook, prefix/suffix regex matching is used instead.\n */\n d1Shard?: (\n logicalName: string,\n date: string,\n tenantId: string,\n env: string,\n ) => string;\n /**\n * R2 bucket name for `resources.r2[]`.\n *\n * **`date`:** `YYYYMMDD` (no dashes) from the calendar day **apply** or\n * **sync** runs. Ignore unless your legacy scheme embeds a date stamp.\n *\n * **Default:** `r2-{logicalName}-t-{tenantId}-{env}` (no date segment).\n *\n * **`sync`:** exact match on the name produced by calling this hook with\n * today's date (same as apply). Legacy dated buckets\n * `r2-{logical}-YYYYMMDD-t-{tenant}-{env}` still match when the hook is\n * omitted.\n */\n r2Bucket?: (\n logicalName: string,\n date: string,\n tenantId: string,\n env: string,\n ) => string;\n /**\n * Deployed Worker **script** name (wrangler `name` / `tamer deploy` target).\n *\n * **Default:** `{slug}-{workerKey}-{tenantId}` for `local`; otherwise\n * `{slug}-{workerKey}-{env}-{tenantId}`. Overridable per worker via\n * {@link WorkerConfig.scriptName}.\n *\n * **`sync`:** worker script list is matched by deployed name (routes use\n * the same derivation).\n */\n workerName?: (\n slug: string,\n workerKey: string,\n env: string,\n tenantId: string,\n ) => string;\n /**\n * Cloudflare workflow **registration** name for `resources.workflows[]`.\n * Workflow names are immutable on Cloudflare — brownfield stacks must\n * return the live registration name here.\n *\n * **Default:** `wf-{logicalName}-t-{tenantId}-{env}` (lowercased).\n *\n * **`sync`:** exact match on the derived name (same as D1 single / default\n * R2). Prefer `tamer import --kind workflow` only when a legacy name cannot\n * be expressed as a function of logical name, tenant, and env.\n */\n workflow?: (logicalName: string, tenantId: string, env: string) => 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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"}
@@ -4,7 +4,7 @@ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
4
4
  import { n as logApplyChange } from "./planFormat-DpA8XhzX.mjs";
5
5
  import { i as tamerArtifactsKeyPrefix, r as tamerArtifactsBucketName } from "./tamerArtifactsR2-B9myb-IA.mjs";
6
6
  import { n as buildSingleModuleDispatchFormFromBuffer, t as buildSingleModuleDispatchForm } from "./buildDispatchUploadForm-D_fM8JaL.mjs";
7
- import { resolveTenantBindings } from "./resolveTenantBindings-DJDPhILD.mjs";
7
+ import { resolveTenantBindings } from "./resolveTenantBindings-4grVKHIG.mjs";
8
8
  import { basename, resolve } from "path";
9
9
 
10
10
  //#region src/cli/commands/provision-tenant.ts
@@ -198,29 +198,17 @@ async function migrateTenantShards(config, shards, env, naming) {
198
198
  const { tmpdir } = await import("os");
199
199
  const { spawnWranglerSync } = await import("./wranglerSpawn-aARBLHpA.mjs");
200
200
  const { wranglerConfigCliArgs } = await import("./wranglerOutFile-f08VsoAj.mjs");
201
- const { resolveTenantD1Bindings } = await import("./resolveTenantBindings-DJDPhILD.mjs");
202
- const workers = config.workers ? Object.values(config.workers) : config.worker ? [config.worker] : [];
203
- const roleToMigrations = /* @__PURE__ */ new Map();
204
- for (const wc of workers) {
205
- const d1s = wc.resources?.d1;
206
- if (!d1s) continue;
207
- for (const d1 of d1s) {
208
- const role = d1.registryRole ?? "";
209
- if (role) roleToMigrations.set(role, {
210
- dir: d1.migrationsDir,
211
- table: d1.migrationsTable
212
- });
213
- }
214
- }
201
+ const { resolveTenantD1Bindings } = await import("./resolveTenantBindings-4grVKHIG.mjs");
202
+ const shardBindings = config.tenant.shardBindings ?? {};
215
203
  const d1Bindings = resolveTenantD1Bindings(config, shards, naming);
216
204
  const tempDir = join$1(tmpdir(), `tamer-tenant-migrate-${Date.now()}`);
217
205
  mkdirSync(tempDir, { recursive: true });
218
206
  for (let i = 0; i < shards.length; i++) {
219
207
  const shard = shards[i];
220
208
  const binding = d1Bindings[i];
221
- const migInfo = roleToMigrations.get(shard.role);
222
- if (!migInfo?.dir) {
223
- console.log(` migrate: skipping ${shard.role} (no migrationsDir configured)`);
209
+ const info = shardBindings[shard.role];
210
+ if (!info?.migrationsDir) {
211
+ console.log(` migrate: skipping ${shard.role} (no migrationsDir in shardBindings)`);
224
212
  continue;
225
213
  }
226
214
  const tempWrangler = join$1(tempDir, `wrangler.${shard.role}.json`);
@@ -242,10 +230,10 @@ async function migrateTenantShards(config, shards, env, naming) {
242
230
  binding.name,
243
231
  "--remote"
244
232
  ];
245
- if (migInfo.table) migrateArgs.push("--migrations-table", migInfo.table);
233
+ if (info.migrationsTable) migrateArgs.push("--migrations-table", info.migrationsTable);
246
234
  console.log(` migrate: applying migrations for ${shard.role} (${shard.derivedName})...`);
247
235
  const result = spawnWranglerSync(migrateArgs, {
248
- cwd: migInfo.dir,
236
+ cwd: info.migrationsDir,
249
237
  stdio: "inherit"
250
238
  });
251
239
  if (result.status !== 0) console.warn(` migrate: failed for ${shard.role} (exit ${result.status})`);
@@ -260,4 +248,4 @@ async function migrateTenantShards(config, shards, env, naming) {
260
248
 
261
249
  //#endregion
262
250
  export { runProvisionTenant };
263
- //# sourceMappingURL=provision-tenant-DW4eg-7P.mjs.map
251
+ //# sourceMappingURL=provision-tenant-R6Xa3IUJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provision-tenant-R6Xa3IUJ.mjs","names":["result: ProvisionTenantResult","allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null","shards: TenantD1ShardRef[]","form: FormData","join"],"sources":["../src/cli/commands/provision-tenant.ts"],"sourcesContent":["import { basename, resolve } from \"path\";\nimport { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { getDispatchNamespaces } from \"../../types.js\";\nimport { effectiveDispatchNamespaceName } from \"../../features/dispatch-namespace/dispatch-namespace.resolve.js\";\nimport {\n parseTenantShardRoles,\n tenantDispatchScriptName,\n tenantShardDatabaseName,\n tenantStateKey,\n} from \"../../core/tenant/tenantKeys.js\";\nimport {\n buildSingleModuleDispatchForm,\n buildSingleModuleDispatchFormFromBuffer,\n} from \"../../core/wfp/buildDispatchUploadForm.js\";\nimport { resolveTenantBindings } from \"../../core/tenant/resolveTenantBindings.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { tamerArtifactsBucketName, tamerArtifactsKeyPrefix } from \"../../core/state/tamerArtifactsR2.js\";\nimport { logApplyChange } from \"../../core/plan/planFormat.js\";\nimport type { TenantD1ShardRef, TenantStateEntry } from \"../../types.js\";\n\nconst now = (): string => new Date().toISOString();\n\nfunction buildTenantEntry(\n options: {\n product: string;\n workspace: string;\n dispatchNamespaceName: string;\n scriptName: string;\n },\n status: TenantStateEntry[\"provisioningStatus\"],\n existing: TenantStateEntry | undefined,\n patch?: Partial<Pick<TenantStateEntry, \"d1Shards\">>,\n): TenantStateEntry {\n return {\n product: options.product,\n workspace: options.workspace,\n provisioningStatus: status,\n dispatchNamespaceName: options.dispatchNamespaceName,\n scriptName: options.scriptName,\n d1Shards: patch?.d1Shards ?? existing?.d1Shards,\n createdAt: existing?.createdAt ?? now(),\n updatedAt: now(),\n };\n}\n\n/**\n * Resolve the effective shard set for this provision call.\n *\n * 1. The configured layout (`tenant.d1Shards` in `tamer.config.ts`)\n * is the source of truth — any role used by `--shards` must\n * appear here, and `apply` / `drift` / other operators read the\n * same shape.\n * 2. When `--shards a,b` is passed, trim to that subset (preserving\n * configured order so plan/provision logs stay deterministic).\n * Extending the layout is an edit to `tamer.config.ts`, not a\n * CLI flag — that's why `parseTenantShardRoles` rejects roles\n * outside the configured set.\n * 3. When the config has no `tenant.d1Shards`, the resolved set is\n * empty: `provision-tenant` only uploads the dispatch script\n * (truly generic single-Worker tenant case).\n */\nfunction resolveShardRoles(\n configured: readonly string[],\n shardsRaw: string | undefined,\n): string[] {\n if (shardsRaw == null || shardsRaw.trim() === \"\") return [...configured];\n return parseTenantShardRoles(shardsRaw, configured);\n}\n\n/**\n * Idempotent tenant provisioning. Each call:\n *\n * 1. Marks the tenant `pending` if there's no record yet.\n * 2. For every shard role declared in `tenant.d1Shards` in\n * `tamer.config.ts` (or a `--shards` subset): looks up an\n * existing D1 by derived name (`tenantShardDatabaseName(role,\n * …)`), creating it via `d1Create` only when missing. Persists\n * the `(role, derivedName, cfId)` triple to\n * `state.tenants[…].d1Shards[]`.\n * 3. Marks the tenant `d1_created` once *all* requested shards\n * exist in state.\n * 4. Uploads the dispatch script (from `--main` or `--artifact-key`)\n * via the WFP dispatch namespace.\n * 5. Marks the tenant `ready`.\n *\n * When `tenant.d1Shards` is omitted, step 2/3 collapse to a no-op\n * (truly generic single-Worker tenant — only the dispatch script is\n * provisioned). Re-running with the same shard set is a no-op (state\n * already has those rows). Editing `tamer.config.ts` to **extend**\n * the layout (e.g. adding a new role) and re-running picks up the\n * missing shards without disturbing existing ones, so a tenant\n * created with `[\"app\"]` can be migrated to `[\"system\",\"app\",\n * \"history\"]` (or `[\"primary\",\"audit\"]`, or any other set) with one\n * config edit + one `provision-tenant` invocation. Re-running after\n * a config that **shrinks** the layout never deletes shards (use\n * `destroy-tenant` for that).\n */\n/**\n * Machine-readable result envelope emitted on the final stdout line\n * when `--json` is passed. Designed for the Cloudflare Container caller\n * (`provision-workflow`, see `docs/handoff.md` §7) so the Worker can\n * parse the result without scraping human logs. On success the Workflow\n * sees `status: \"ready\"` plus the materialized tenant shape; on\n * failure it sees `status: \"failed\"` with `error` + best-effort\n * `partial` state, and the process exits non-zero.\n */\nexport interface ProvisionTenantResult {\n status: \"ready\" | \"failed\" | \"noop\";\n tenantKey: string;\n product: string;\n workspace: string;\n env: string;\n scriptName: string;\n dispatchNamespaceName: string;\n shards: { role: string; derivedName: string; cfId: string }[];\n error?: string;\n}\n\nexport async function runProvisionTenant(options: {\n env: string;\n product: string;\n workspace: string;\n main?: string;\n artifactKey?: string;\n moduleName?: string;\n configPath?: string;\n compatibilityDate?: string;\n compatibilityFlags?: string[];\n /**\n * Raw `--shards` value (`\"system,app\"` etc.) — validated against\n * `tenant.d1Shards` from the loaded config. Omit to provision every\n * configured shard.\n */\n shardsRaw?: string;\n json?: boolean;\n migrate?: boolean;\n}): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\n \"provision-tenant requires a non-local --env (Cloudflare-backed state).\",\n );\n }\n\n try {\n await provisionTenantInner(options);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (options.json) {\n // Failure envelope: we may have died before loadConfig, so we\n // can't ask `tenant.ephemeralEnvPattern` whether `env` would\n // get the `-{env}` suffix. Emit the non-ephemeral form as a\n // best-effort hint — `error` carries the real signal.\n const result: ProvisionTenantResult = {\n status: \"failed\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName: `${options.product}-${options.workspace}`,\n dispatchNamespaceName: \"\",\n shards: [],\n error: msg,\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n // Container caller (provision-workflow) reads exit code as\n // primary success/failure signal; JSON envelope on stdout is\n // secondary detail. Re-throw so the CLI surfaces the original\n // error in human mode.\n throw err;\n }\n}\n\nasync function provisionTenantInner(options: {\n env: string;\n product: string;\n workspace: string;\n main?: string;\n artifactKey?: string;\n moduleName?: string;\n configPath?: string;\n compatibilityDate?: string;\n compatibilityFlags?: string[];\n shardsRaw?: string;\n json?: boolean;\n migrate?: boolean;\n}): Promise<void> {\n const env = options.env;\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\n const dns = getDispatchNamespaces(config);\n if (dns.length === 0) {\n throw new Error(\n \"provision-tenant requires at least one dispatch namespace in the Tamer project config (dispatchNamespaces).\",\n );\n }\n const nsConfig = dns[0]!;\n const dispatchNamespaceName = effectiveDispatchNamespaceName(\n nsConfig,\n env,\n config.tenant,\n );\n const scriptName = tenantDispatchScriptName(\n options.product,\n options.workspace,\n env,\n config.tenant,\n );\n const desiredRoles = resolveShardRoles(\n config.tenant.d1Shards ?? [],\n options.shardsRaw,\n );\n\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n\n let existing = state.getTenant(options.product, options.workspace);\n // \"ready\" is a no-op only when the existing layout already covers\n // every requested shard role. Adding a new role re-opens the tenant\n // for incremental provisioning without forcing a full re-deploy.\n if (existing?.provisioningStatus === \"ready\") {\n const have = new Set((existing.d1Shards ?? []).map((s) => s.role));\n const missing = desiredRoles.filter((r) => !have.has(r));\n if (missing.length === 0) {\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} already ready; nothing to do.`,\n );\n if (options.json) {\n const result: ProvisionTenantResult = {\n status: \"noop\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName,\n dispatchNamespaceName,\n shards: (existing.d1Shards ?? []).map((s) => ({\n role: s.role,\n derivedName: s.derivedName,\n cfId: s.cfId,\n })),\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n return;\n }\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} ready but missing shards: ${missing.join(\", \")}; extending layout.`,\n );\n }\n\n const compatDate =\n options.compatibilityDate ??\n config.compatibility_date ??\n new Date().toISOString().slice(0, 10);\n\n const entryOpts = {\n product: options.product,\n workspace: options.workspace,\n dispatchNamespaceName,\n scriptName,\n };\n\n if (!existing) {\n state.setTenant(buildTenantEntry(entryOpts, \"pending\", undefined));\n await state.persist(api);\n existing = state.getTenant(options.product, options.workspace)!;\n }\n\n // Reconcile each requested shard role. We list D1 once per call when\n // we need to (some role is missing from state) and reuse the result\n // for every subsequent role.\n let allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null = null;\n const shards: TenantD1ShardRef[] = [...(existing.d1Shards ?? [])];\n\n for (const role of desiredRoles) {\n if (shards.find((s) => s.role === role)) continue;\n const derivedName = tenantShardDatabaseName(\n role,\n options.workspace,\n options.product,\n config.tenant.id,\n env,\n );\n if (allD1 === null) {\n allD1 = await api.d1ListAll();\n }\n let cfId = allD1.find((d) => d.name === derivedName)?.uuid;\n if (cfId) {\n // Adopt an existing D1 (e.g. partial-failure resume, or\n // operator created it out-of-band) without re-issuing POST.\n logApplyChange({\n kind: \"d1\",\n action: \"no_change\",\n logical: `${role}-shard`,\n derived: derivedName,\n });\n } else {\n logApplyChange({\n kind: \"d1\",\n action: \"create\",\n logical: `${role}-shard`,\n derived: derivedName,\n });\n const created = await api.d1Create(derivedName);\n cfId = created.uuid;\n }\n shards.push({ role, derivedName, cfId });\n // Persist after each shard so a network failure mid-loop leaves\n // the already-created shards recorded — `provision-tenant` is\n // resumable role-by-role.\n state.setTenant(\n buildTenantEntry(entryOpts, \"d1_created\", existing, { d1Shards: shards }),\n );\n await state.persist(api);\n existing = state.getTenant(options.product, options.workspace)!;\n }\n\n // Resolve dispatch script bindings from shards + config.\n const naming = namingFromConfig(config);\n const bindings = resolveTenantBindings(\n config,\n shards,\n naming,\n state,\n env,\n accountId,\n );\n\n // Optionally run migrations on each shard before uploading the script.\n if (options.migrate !== false && shards.length > 0) {\n await migrateTenantShards(config, shards, env, naming);\n }\n\n let form: FormData;\n const formOpts = {\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags,\n bindings,\n };\n if (options.main) {\n const mainPath = resolve(process.cwd(), options.main);\n form = buildSingleModuleDispatchForm(mainPath, formOpts);\n } else if (options.artifactKey) {\n const bucket = tamerArtifactsBucketName();\n const fullKey = `${tamerArtifactsKeyPrefix(env)}${options.artifactKey}`;\n const body = await api.r2GetObject(bucket, fullKey);\n const mod =\n options.moduleName?.trim() ||\n basename(options.artifactKey).replace(/\\.(zip|tar|gz)$/i, \"\") ||\n \"worker.js\";\n form = buildSingleModuleDispatchFormFromBuffer(mod, body, formOpts);\n } else {\n throw new Error(\"provision-tenant: provide --main or --artifact-key\");\n }\n\n await api.dispatchNamespaceScriptPut(dispatchNamespaceName, scriptName, form);\n\n state.setTenant(\n buildTenantEntry(entryOpts, \"ready\", existing, { d1Shards: shards }),\n );\n await state.persist(api);\n\n const shardsSummary =\n shards.length > 0\n ? `shards: ${shards.map((s) => s.role).join(\", \")}`\n : \"no shards declared\";\n console.log(\n `Provisioned tenant ${tenantStateKey(options.product, options.workspace)} → script ${scriptName} in ${dispatchNamespaceName} (${shardsSummary})`,\n );\n\n if (options.json) {\n const result: ProvisionTenantResult = {\n status: \"ready\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName,\n dispatchNamespaceName,\n shards: shards.map((s) => ({\n role: s.role,\n derivedName: s.derivedName,\n cfId: s.cfId,\n })),\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n}\n\n/**\n * Run D1 migrations on each tenant shard by writing a temporary wrangler.json\n * with the shard binding and invoking `wrangler d1 migrations apply`.\n */\nasync function migrateTenantShards(\n config: import(\"../../types.js\").CfiConfig,\n shards: TenantD1ShardRef[],\n env: string,\n naming: import(\"../../core/naming/NamingEngine.js\").NamingEngine,\n): Promise<void> {\n const { writeFileSync, unlinkSync, mkdirSync } = await import(\"fs\");\n const { join } = await import(\"path\");\n const { tmpdir } = await import(\"os\");\n const { spawnWranglerSync } = await import(\n \"../../core/wrangler/wranglerSpawn.js\"\n );\n const { wranglerConfigCliArgs } = await import(\n \"../../core/wrangler/wranglerOutFile.js\"\n );\n const { resolveTenantD1Bindings } = await import(\n \"../../core/tenant/resolveTenantBindings.js\"\n );\n\n const shardBindings = config.tenant.shardBindings ?? {};\n const d1Bindings = resolveTenantD1Bindings(config, shards, naming);\n const tempDir = join(tmpdir(), `tamer-tenant-migrate-${Date.now()}`);\n mkdirSync(tempDir, { recursive: true });\n\n for (let i = 0; i < shards.length; i++) {\n const shard = shards[i]!;\n const binding = d1Bindings[i]!;\n const info = shardBindings[shard.role];\n if (!info?.migrationsDir) {\n console.log(` migrate: skipping ${shard.role} (no migrationsDir in shardBindings)`);\n continue;\n }\n\n const tempWrangler = join(tempDir, `wrangler.${shard.role}.json`);\n const tempConfig = {\n name: `tenant-migrate-${shard.role}`,\n d1_databases: [{ name: binding.name, database_id: shard.cfId }],\n compatibility_date: config.compatibility_date ?? \"2024-01-01\",\n };\n writeFileSync(tempWrangler, JSON.stringify(tempConfig, null, 2));\n\n const migrateArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(`wrangler.${shard.role}.json`),\n \"d1\",\n \"migrations\",\n \"apply\",\n binding.name,\n \"--remote\",\n ];\n if (info.migrationsTable) {\n migrateArgs.push(\"--migrations-table\", info.migrationsTable);\n }\n\n console.log(` migrate: applying migrations for ${shard.role} (${shard.derivedName})...`);\n const result = spawnWranglerSync(migrateArgs, {\n cwd: info.migrationsDir!,\n stdio: \"inherit\",\n });\n if (result.status !== 0) {\n console.warn(` migrate: failed for ${shard.role} (exit ${result.status})`);\n }\n try {\n unlinkSync(tempWrangler);\n } catch {\n /* best-effort cleanup */\n }\n }\n\n try {\n unlinkSync(tempDir);\n } catch {\n /* best-effort */\n }\n}\n"],"mappings":"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"}
@@ -3,37 +3,15 @@ import { j as resolveReferencesInString } from "./tamer.mjs";
3
3
 
4
4
  //#region src/core/tenant/resolveTenantBindings.ts
5
5
  /**
6
- * Collect all D1 resource configs across all workers in the stack.
7
- * Returns a map of `registryRole` → `{ binding, migrationsDir, migrationsTable }`.
8
- */
9
- function collectD1RegistryRoles(config) {
10
- const out = /* @__PURE__ */ new Map();
11
- const workers = config.workers ? config.workers : config.worker ? { default: config.worker } : {};
12
- for (const wc of Object.values(workers)) {
13
- const d1s = wc.resources?.d1;
14
- if (!d1s) continue;
15
- for (const d1 of d1s) {
16
- const role = d1.registryRole ?? d1.logicalName;
17
- const binding = d1.binding?.trim() || `DB_${role.toUpperCase()}`;
18
- if (!out.has(role)) out.set(role, {
19
- binding,
20
- migrationsDir: d1.migrationsDir,
21
- migrationsTable: d1.migrationsTable
22
- });
23
- }
24
- }
25
- return out;
26
- }
27
- /**
28
6
  * Resolve D1 bindings for the tenant dispatch script.
29
- * Matches shard roles to `resources.d1[].registryRole` and produces
7
+ * Matches shard roles to `tenant.shardBindings` and produces
30
8
  * `{ name: binding, id: cfId }` entries for the multipart metadata.
31
9
  */
32
10
  function resolveTenantD1Bindings(config, shards, naming) {
33
- const registryRoles = collectD1RegistryRoles(config);
11
+ const shardBindings = config.tenant.shardBindings ?? {};
34
12
  return shards.map((shard) => {
35
13
  return {
36
- name: registryRoles.get(shard.role)?.binding ?? naming.workflowBindingKey(shard.role),
14
+ name: shardBindings[shard.role]?.binding ?? `DB_${shard.role.toUpperCase().replace(/-/g, "_")}`,
37
15
  id: shard.cfId
38
16
  };
39
17
  });
@@ -77,4 +55,4 @@ function resolveTenantBindings(config, shards, naming, state, env, accountId) {
77
55
 
78
56
  //#endregion
79
57
  export { resolveTenantBindings, resolveTenantD1Bindings };
80
- //# sourceMappingURL=resolveTenantBindings-DJDPhILD.mjs.map
58
+ //# sourceMappingURL=resolveTenantBindings-4grVKHIG.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveTenantBindings-4grVKHIG.mjs","names":["bindings: DispatchScriptBindings","refCtx: ReferenceContext","vars: Record<string, string>"],"sources":["../src/core/tenant/resolveTenantBindings.ts"],"sourcesContent":["/**\n * Resolve dispatch script bindings for a provisioned tenant.\n *\n * D1 bindings derive from `tenant.shardBindings` matched to the tenant's\n * shard roles. Vars and services come from `tenant.dispatchVars` /\n * `tenant.dispatchServices` (resolved against state for `${tamer:...}`\n * references).\n */\nimport type { CfiConfig, TenantD1ShardRef } from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { NamingEngine } from \"../../core/naming/NamingEngine.js\";\nimport type { DispatchScriptBindings } from \"../../core/wfp/buildDispatchUploadForm.js\";\nimport { materializeTamerResolvable } from \"../../dx/normalize.js\";\nimport {\n resolveReferencesInString,\n type ReferenceContext,\n} from \"../../core/references/references.js\";\n\n/**\n * Resolve D1 bindings for the tenant dispatch script.\n * Matches shard roles to `tenant.shardBindings` and produces\n * `{ name: binding, id: cfId }` entries for the multipart metadata.\n */\nexport function resolveTenantD1Bindings(\n config: CfiConfig,\n shards: TenantD1ShardRef[],\n naming: NamingEngine,\n): Array<{ name: string; id: string }> {\n const shardBindings = config.tenant.shardBindings ?? {};\n return shards.map((shard) => {\n const info = shardBindings[shard.role];\n const binding = info?.binding ?? `DB_${shard.role.toUpperCase().replace(/-/g, \"_\")}`;\n return { name: binding, id: shard.cfId };\n });\n}\n\n/**\n * Resolve all dispatch script bindings (D1 + vars + services).\n * References in vars/services are resolved against state.\n */\nexport function resolveTenantBindings(\n config: CfiConfig,\n shards: TenantD1ShardRef[],\n naming: NamingEngine,\n state: StateManager,\n env: string,\n accountId: string,\n): DispatchScriptBindings {\n const bindings: DispatchScriptBindings = {};\n\n // D1 bindings from shard roles\n const d1Bindings = resolveTenantD1Bindings(config, shards, naming);\n if (d1Bindings.length > 0) {\n bindings.d1_databases = d1Bindings;\n }\n\n // Vars from config.tenant.dispatchVars\n if (config.tenant.dispatchVars) {\n const refCtx: ReferenceContext = {\n config,\n env,\n state,\n naming,\n accountId,\n };\n const vars: Record<string, string> = {};\n for (const [key, value] of Object.entries(config.tenant.dispatchVars)) {\n vars[key] = resolveReferencesInString(\n materializeTamerResolvable(value),\n refCtx,\n `tenant.dispatchVars.${key}`,\n );\n }\n if (Object.keys(vars).length > 0) {\n bindings.vars = vars;\n }\n }\n\n // Service bindings from config.tenant.dispatchServices\n if (config.tenant.dispatchServices?.length) {\n const refCtx: ReferenceContext = {\n config,\n env,\n state,\n naming,\n accountId,\n };\n bindings.services = config.tenant.dispatchServices.map((s) => ({\n name: s.name,\n service: resolveReferencesInString(\n materializeTamerResolvable(s.service),\n refCtx,\n `tenant.dispatchServices.${s.name}.service`,\n ),\n ...(s.environment ? { environment: s.environment } : {}),\n }));\n }\n\n return bindings;\n}\n"],"mappings":";;;;;;;;;AAuBA,SAAgB,wBACd,QACA,QACA,QACqC;CACrC,MAAM,gBAAgB,OAAO,OAAO,iBAAiB,EAAE;AACvD,QAAO,OAAO,KAAK,UAAU;AAG3B,SAAO;GAAE,MAFI,cAAc,MAAM,OACX,WAAW,MAAM,MAAM,KAAK,aAAa,CAAC,QAAQ,MAAM,IAAI;GAC1D,IAAI,MAAM;GAAM;GACxC;;;;;;AAOJ,SAAgB,sBACd,QACA,QACA,QACA,OACA,KACA,WACwB;CACxB,MAAMA,WAAmC,EAAE;CAG3C,MAAM,aAAa,wBAAwB,QAAQ,QAAQ,OAAO;AAClE,KAAI,WAAW,SAAS,EACtB,UAAS,eAAe;AAI1B,KAAI,OAAO,OAAO,cAAc;EAC9B,MAAMC,SAA2B;GAC/B;GACA;GACA;GACA;GACA;GACD;EACD,MAAMC,OAA+B,EAAE;AACvC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,OAAO,aAAa,CACnE,MAAK,OAAO,0BACV,2BAA2B,MAAM,EACjC,QACA,uBAAuB,MACxB;AAEH,MAAI,OAAO,KAAK,KAAK,CAAC,SAAS,EAC7B,UAAS,OAAO;;AAKpB,KAAI,OAAO,OAAO,kBAAkB,QAAQ;EAC1C,MAAMD,SAA2B;GAC/B;GACA;GACA;GACA;GACA;GACD;AACD,WAAS,WAAW,OAAO,OAAO,iBAAiB,KAAK,OAAO;GAC7D,MAAM,EAAE;GACR,SAAS,0BACP,2BAA2B,EAAE,QAAQ,EACrC,QACA,2BAA2B,EAAE,KAAK,UACnC;GACD,GAAI,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,GAAG,EAAE;GACxD,EAAE;;AAGL,QAAO"}
package/dist/tamer.mjs CHANGED
@@ -9011,7 +9011,7 @@ async function main() {
9011
9011
  break;
9012
9012
  }
9013
9013
  if (tenantSub === "provision") {
9014
- await import("./provision-tenant-DW4eg-7P.mjs").then((m) => m.runProvisionTenant(parseProvisionTenantArgs(tenantRest)));
9014
+ await import("./provision-tenant-R6Xa3IUJ.mjs").then((m) => m.runProvisionTenant(parseProvisionTenantArgs(tenantRest)));
9015
9015
  break;
9016
9016
  }
9017
9017
  if (tenantSub === "destroy") {
@@ -9019,7 +9019,7 @@ async function main() {
9019
9019
  break;
9020
9020
  }
9021
9021
  if (tenantSub === "migrate") {
9022
- await import("./tenant-migrate-CT-qyTUD.mjs").then((m) => m.runTenantMigrate({
9022
+ await import("./tenant-migrate-BfvYL0HH.mjs").then((m) => m.runTenantMigrate({
9023
9023
  env: parseStringFlag(wfpRest, "--env"),
9024
9024
  product: parseStringFlag(wfpRest, "--product") ?? "",
9025
9025
  workspace: parseStringFlag(wfpRest, "--workspace") ?? "",
@@ -1,6 +1,6 @@
1
1
  import { N as wranglerConfigCliArgs, R as CFApiClient, U as loadConfig, h as StateManager, u as namingFromConfig, w as stackNameForConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
2
  import { n as spawnWranglerSync } from "./wranglerSpawn-DWdgrsmQ.mjs";
3
- import { resolveTenantD1Bindings } from "./resolveTenantBindings-DJDPhILD.mjs";
3
+ import { resolveTenantD1Bindings } from "./resolveTenantBindings-4grVKHIG.mjs";
4
4
  import { join } from "path";
5
5
  import { mkdirSync, unlinkSync, writeFileSync } from "fs";
6
6
  import { tmpdir } from "os";
@@ -31,19 +31,7 @@ async function runTenantMigrate(options) {
31
31
  console.log(`Tenant "${options.product}:${options.workspace}" has no D1 shards.`);
32
32
  return;
33
33
  }
34
- const workers = config.workers ? Object.values(config.workers) : config.worker ? [config.worker] : [];
35
- const roleToMigrations = /* @__PURE__ */ new Map();
36
- for (const wc of workers) {
37
- const d1s = wc.resources?.d1;
38
- if (!d1s) continue;
39
- for (const d1 of d1s) {
40
- const role = d1.registryRole ?? "";
41
- if (role) roleToMigrations.set(role, {
42
- dir: d1.migrationsDir,
43
- table: d1.migrationsTable
44
- });
45
- }
46
- }
34
+ const shardBindings = config.tenant.shardBindings ?? {};
47
35
  const d1Bindings = resolveTenantD1Bindings(config, tenant.d1Shards, naming);
48
36
  const tempDir = join(tmpdir(), `tamer-tenant-migrate-${Date.now()}`);
49
37
  mkdirSync(tempDir, { recursive: true });
@@ -52,9 +40,9 @@ async function runTenantMigrate(options) {
52
40
  for (let i = 0; i < tenant.d1Shards.length; i++) {
53
41
  const shard = tenant.d1Shards[i];
54
42
  const binding = d1Bindings[i];
55
- const migInfo = roleToMigrations.get(shard.role);
56
- if (!migInfo?.dir) {
57
- console.log(` migrate: skipping ${shard.role} (no migrationsDir configured)`);
43
+ const info = shardBindings[shard.role];
44
+ if (!info?.migrationsDir) {
45
+ console.log(` migrate: skipping ${shard.role} (no migrationsDir in shardBindings)`);
58
46
  skipped++;
59
47
  continue;
60
48
  }
@@ -77,10 +65,10 @@ async function runTenantMigrate(options) {
77
65
  binding.name,
78
66
  "--remote"
79
67
  ];
80
- if (migInfo.table) migrateArgs.push("--migrations-table", migInfo.table);
68
+ if (info.migrationsTable) migrateArgs.push("--migrations-table", info.migrationsTable);
81
69
  console.log(` migrate: applying for ${shard.role} (${shard.derivedName})...`);
82
70
  const result = spawnWranglerSync(migrateArgs, {
83
- cwd: migInfo.dir,
71
+ cwd: info.migrationsDir,
84
72
  stdio: "inherit"
85
73
  });
86
74
  if (result.status === 0) migrated++;
@@ -97,4 +85,4 @@ async function runTenantMigrate(options) {
97
85
 
98
86
  //#endregion
99
87
  export { runTenantMigrate };
100
- //# sourceMappingURL=tenant-migrate-CT-qyTUD.mjs.map
88
+ //# sourceMappingURL=tenant-migrate-BfvYL0HH.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-migrate-BfvYL0HH.mjs","names":[],"sources":["../src/cli/commands/tenant-migrate.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { resolveTenantD1Bindings } from \"../../core/tenant/resolveTenantBindings.js\";\nimport { spawnWranglerSync } from \"../../core/wrangler/wranglerSpawn.js\";\nimport { wranglerConfigCliArgs } from \"../../core/wrangler/wranglerOutFile.js\";\nimport { writeFileSync, unlinkSync, mkdirSync } from \"fs\";\nimport { join } from \"path\";\nimport { tmpdir } from \"os\";\n\n/**\n * `tamer wfp tenant migrate --env dev --product X --workspace Y`\n *\n * Runs D1 migrations on each shard for the named tenant. Matches shard roles\n * to `resources.d1[].registryRole` → `migrationsDir` to find the migration\n * source directory. Writes a temporary wrangler.json with the shard binding\n * and invokes `wrangler d1 migrations apply --remote`.\n */\nexport async function runTenantMigrate(options: {\n env?: string;\n product: string;\n workspace: string;\n configPath?: string;\n}): Promise<void> {\n const env = options.env;\n if (!env) {\n throw new Error(\"tenant migrate: --env is required\");\n }\n if (env === \"local\") {\n throw new Error(\"tenant migrate requires a non-local --env.\");\n }\n\n const config = await loadConfig(options.configPath, { env });\n const accountId =\n 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\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\n const tenant = state.getTenant(options.product, options.workspace);\n if (!tenant) {\n throw new Error(\n `Tenant \"${options.product}:${options.workspace}\" not found in env ${env}.`,\n );\n }\n if (!tenant.d1Shards || tenant.d1Shards.length === 0) {\n console.log(`Tenant \"${options.product}:${options.workspace}\" has no D1 shards.`);\n return;\n }\n\n const shardBindings = config.tenant.shardBindings ?? {};\n const d1Bindings = resolveTenantD1Bindings(config, tenant.d1Shards, naming);\n const tempDir = join(tmpdir(), `tamer-tenant-migrate-${Date.now()}`);\n mkdirSync(tempDir, { recursive: true });\n\n let migrated = 0;\n let skipped = 0;\n\n for (let i = 0; i < tenant.d1Shards.length; i++) {\n const shard = tenant.d1Shards[i]!;\n const binding = d1Bindings[i]!;\n const info = shardBindings[shard.role];\n if (!info?.migrationsDir) {\n console.log(` migrate: skipping ${shard.role} (no migrationsDir in shardBindings)`);\n skipped++;\n continue;\n }\n\n const tempWrangler = join(tempDir, `wrangler.${shard.role}.json`);\n const tempConfig = {\n name: `tenant-migrate-${shard.role}`,\n d1_databases: [{ name: binding.name, database_id: shard.cfId }],\n compatibility_date: config.compatibility_date ?? \"2024-01-01\",\n };\n writeFileSync(tempWrangler, JSON.stringify(tempConfig, null, 2));\n\n const migrateArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(`wrangler.${shard.role}.json`),\n \"d1\",\n \"migrations\",\n \"apply\",\n binding.name,\n \"--remote\",\n ];\n if (info.migrationsTable) {\n migrateArgs.push(\"--migrations-table\", info.migrationsTable);\n }\n\n console.log(` migrate: applying for ${shard.role} (${shard.derivedName})...`);\n const result = spawnWranglerSync(migrateArgs, {\n cwd: info.migrationsDir!,\n stdio: \"inherit\",\n });\n if (result.status === 0) {\n migrated++;\n } else {\n console.warn(` migrate: failed for ${shard.role} (exit ${result.status})`);\n }\n try {\n unlinkSync(tempWrangler);\n } catch {\n /* best-effort */\n }\n }\n\n try {\n unlinkSync(tempDir);\n } catch {\n /* best-effort */\n }\n\n console.log(\n `\\nMigrations complete: ${migrated} shard(s) migrated, ${skipped} skipped.`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,eAAsB,iBAAiB,SAKrB;CAChB,MAAM,MAAM,QAAQ;AACpB,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,oCAAoC;AAEtD,KAAI,QAAQ,QACV,OAAM,IAAI,MAAM,6CAA6C;CAG/D,MAAM,SAAS,MAAM,WAAW,QAAQ,YAAY,EAAE,KAAK,CAAC;CAC5D,MAAM,YACJ,OAAO,cAAc,4BAA4B;AACnD,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAGH,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;CAExB,MAAM,SAAS,MAAM,UAAU,QAAQ,SAAS,QAAQ,UAAU;AAClE,KAAI,CAAC,OACH,OAAM,IAAI,MACR,WAAW,QAAQ,QAAQ,GAAG,QAAQ,UAAU,qBAAqB,IAAI,GAC1E;AAEH,KAAI,CAAC,OAAO,YAAY,OAAO,SAAS,WAAW,GAAG;AACpD,UAAQ,IAAI,WAAW,QAAQ,QAAQ,GAAG,QAAQ,UAAU,qBAAqB;AACjF;;CAGF,MAAM,gBAAgB,OAAO,OAAO,iBAAiB,EAAE;CACvD,MAAM,aAAa,wBAAwB,QAAQ,OAAO,UAAU,OAAO;CAC3E,MAAM,UAAU,KAAK,QAAQ,EAAE,wBAAwB,KAAK,KAAK,GAAG;AACpE,WAAU,SAAS,EAAE,WAAW,MAAM,CAAC;CAEvC,IAAI,WAAW;CACf,IAAI,UAAU;AAEd,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,SAAS,QAAQ,KAAK;EAC/C,MAAM,QAAQ,OAAO,SAAS;EAC9B,MAAM,UAAU,WAAW;EAC3B,MAAM,OAAO,cAAc,MAAM;AACjC,MAAI,CAAC,MAAM,eAAe;AACxB,WAAQ,IAAI,uBAAuB,MAAM,KAAK,sCAAsC;AACpF;AACA;;EAGF,MAAM,eAAe,KAAK,SAAS,YAAY,MAAM,KAAK,OAAO;EACjE,MAAM,aAAa;GACjB,MAAM,kBAAkB,MAAM;GAC9B,cAAc,CAAC;IAAE,MAAM,QAAQ;IAAM,aAAa,MAAM;IAAM,CAAC;GAC/D,oBAAoB,OAAO,sBAAsB;GAClD;AACD,gBAAc,cAAc,KAAK,UAAU,YAAY,MAAM,EAAE,CAAC;EAEhE,MAAM,cAAc;GAClB;GACA,GAAG,sBAAsB,YAAY,MAAM,KAAK,OAAO;GACvD;GACA;GACA;GACA,QAAQ;GACR;GACD;AACD,MAAI,KAAK,gBACP,aAAY,KAAK,sBAAsB,KAAK,gBAAgB;AAG9D,UAAQ,IAAI,2BAA2B,MAAM,KAAK,IAAI,MAAM,YAAY,MAAM;EAC9E,MAAM,SAAS,kBAAkB,aAAa;GAC5C,KAAK,KAAK;GACV,OAAO;GACR,CAAC;AACF,MAAI,OAAO,WAAW,EACpB;MAEA,SAAQ,KAAK,yBAAyB,MAAM,KAAK,SAAS,OAAO,OAAO,GAAG;AAE7E,MAAI;AACF,cAAW,aAAa;UAClB;;AAKV,KAAI;AACF,aAAW,QAAQ;SACb;AAIR,SAAQ,IACN,0BAA0B,SAAS,sBAAsB,QAAQ,WAClE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dragonmastery/tamer",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "description": "Tamer: Cloudflare Workers infra CLI (sync, apply, deploy, migrate, destroy) and Wrangler-oriented TypeScript types.",
5
5
  "author": "DragonMastery",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1 +0,0 @@
1
- {"version":3,"file":"provision-tenant-DW4eg-7P.mjs","names":["result: ProvisionTenantResult","allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null","shards: TenantD1ShardRef[]","form: FormData","roleToMigrations: Map<string, { dir?: string; table?: string }>","join"],"sources":["../src/cli/commands/provision-tenant.ts"],"sourcesContent":["import { basename, resolve } from \"path\";\nimport { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { getDispatchNamespaces } from \"../../types.js\";\nimport { effectiveDispatchNamespaceName } from \"../../features/dispatch-namespace/dispatch-namespace.resolve.js\";\nimport {\n parseTenantShardRoles,\n tenantDispatchScriptName,\n tenantShardDatabaseName,\n tenantStateKey,\n} from \"../../core/tenant/tenantKeys.js\";\nimport {\n buildSingleModuleDispatchForm,\n buildSingleModuleDispatchFormFromBuffer,\n} from \"../../core/wfp/buildDispatchUploadForm.js\";\nimport { resolveTenantBindings } from \"../../core/tenant/resolveTenantBindings.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { tamerArtifactsBucketName, tamerArtifactsKeyPrefix } from \"../../core/state/tamerArtifactsR2.js\";\nimport { logApplyChange } from \"../../core/plan/planFormat.js\";\nimport type { TenantD1ShardRef, TenantStateEntry } from \"../../types.js\";\n\nconst now = (): string => new Date().toISOString();\n\nfunction buildTenantEntry(\n options: {\n product: string;\n workspace: string;\n dispatchNamespaceName: string;\n scriptName: string;\n },\n status: TenantStateEntry[\"provisioningStatus\"],\n existing: TenantStateEntry | undefined,\n patch?: Partial<Pick<TenantStateEntry, \"d1Shards\">>,\n): TenantStateEntry {\n return {\n product: options.product,\n workspace: options.workspace,\n provisioningStatus: status,\n dispatchNamespaceName: options.dispatchNamespaceName,\n scriptName: options.scriptName,\n d1Shards: patch?.d1Shards ?? existing?.d1Shards,\n createdAt: existing?.createdAt ?? now(),\n updatedAt: now(),\n };\n}\n\n/**\n * Resolve the effective shard set for this provision call.\n *\n * 1. The configured layout (`tenant.d1Shards` in `tamer.config.ts`)\n * is the source of truth — any role used by `--shards` must\n * appear here, and `apply` / `drift` / other operators read the\n * same shape.\n * 2. When `--shards a,b` is passed, trim to that subset (preserving\n * configured order so plan/provision logs stay deterministic).\n * Extending the layout is an edit to `tamer.config.ts`, not a\n * CLI flag — that's why `parseTenantShardRoles` rejects roles\n * outside the configured set.\n * 3. When the config has no `tenant.d1Shards`, the resolved set is\n * empty: `provision-tenant` only uploads the dispatch script\n * (truly generic single-Worker tenant case).\n */\nfunction resolveShardRoles(\n configured: readonly string[],\n shardsRaw: string | undefined,\n): string[] {\n if (shardsRaw == null || shardsRaw.trim() === \"\") return [...configured];\n return parseTenantShardRoles(shardsRaw, configured);\n}\n\n/**\n * Idempotent tenant provisioning. Each call:\n *\n * 1. Marks the tenant `pending` if there's no record yet.\n * 2. For every shard role declared in `tenant.d1Shards` in\n * `tamer.config.ts` (or a `--shards` subset): looks up an\n * existing D1 by derived name (`tenantShardDatabaseName(role,\n * …)`), creating it via `d1Create` only when missing. Persists\n * the `(role, derivedName, cfId)` triple to\n * `state.tenants[…].d1Shards[]`.\n * 3. Marks the tenant `d1_created` once *all* requested shards\n * exist in state.\n * 4. Uploads the dispatch script (from `--main` or `--artifact-key`)\n * via the WFP dispatch namespace.\n * 5. Marks the tenant `ready`.\n *\n * When `tenant.d1Shards` is omitted, step 2/3 collapse to a no-op\n * (truly generic single-Worker tenant — only the dispatch script is\n * provisioned). Re-running with the same shard set is a no-op (state\n * already has those rows). Editing `tamer.config.ts` to **extend**\n * the layout (e.g. adding a new role) and re-running picks up the\n * missing shards without disturbing existing ones, so a tenant\n * created with `[\"app\"]` can be migrated to `[\"system\",\"app\",\n * \"history\"]` (or `[\"primary\",\"audit\"]`, or any other set) with one\n * config edit + one `provision-tenant` invocation. Re-running after\n * a config that **shrinks** the layout never deletes shards (use\n * `destroy-tenant` for that).\n */\n/**\n * Machine-readable result envelope emitted on the final stdout line\n * when `--json` is passed. Designed for the Cloudflare Container caller\n * (`provision-workflow`, see `docs/handoff.md` §7) so the Worker can\n * parse the result without scraping human logs. On success the Workflow\n * sees `status: \"ready\"` plus the materialized tenant shape; on\n * failure it sees `status: \"failed\"` with `error` + best-effort\n * `partial` state, and the process exits non-zero.\n */\nexport interface ProvisionTenantResult {\n status: \"ready\" | \"failed\" | \"noop\";\n tenantKey: string;\n product: string;\n workspace: string;\n env: string;\n scriptName: string;\n dispatchNamespaceName: string;\n shards: { role: string; derivedName: string; cfId: string }[];\n error?: string;\n}\n\nexport async function runProvisionTenant(options: {\n env: string;\n product: string;\n workspace: string;\n main?: string;\n artifactKey?: string;\n moduleName?: string;\n configPath?: string;\n compatibilityDate?: string;\n compatibilityFlags?: string[];\n /**\n * Raw `--shards` value (`\"system,app\"` etc.) — validated against\n * `tenant.d1Shards` from the loaded config. Omit to provision every\n * configured shard.\n */\n shardsRaw?: string;\n json?: boolean;\n migrate?: boolean;\n}): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\n \"provision-tenant requires a non-local --env (Cloudflare-backed state).\",\n );\n }\n\n try {\n await provisionTenantInner(options);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (options.json) {\n // Failure envelope: we may have died before loadConfig, so we\n // can't ask `tenant.ephemeralEnvPattern` whether `env` would\n // get the `-{env}` suffix. Emit the non-ephemeral form as a\n // best-effort hint — `error` carries the real signal.\n const result: ProvisionTenantResult = {\n status: \"failed\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName: `${options.product}-${options.workspace}`,\n dispatchNamespaceName: \"\",\n shards: [],\n error: msg,\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n // Container caller (provision-workflow) reads exit code as\n // primary success/failure signal; JSON envelope on stdout is\n // secondary detail. Re-throw so the CLI surfaces the original\n // error in human mode.\n throw err;\n }\n}\n\nasync function provisionTenantInner(options: {\n env: string;\n product: string;\n workspace: string;\n main?: string;\n artifactKey?: string;\n moduleName?: string;\n configPath?: string;\n compatibilityDate?: string;\n compatibilityFlags?: string[];\n shardsRaw?: string;\n json?: boolean;\n migrate?: boolean;\n}): Promise<void> {\n const env = options.env;\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\n const dns = getDispatchNamespaces(config);\n if (dns.length === 0) {\n throw new Error(\n \"provision-tenant requires at least one dispatch namespace in the Tamer project config (dispatchNamespaces).\",\n );\n }\n const nsConfig = dns[0]!;\n const dispatchNamespaceName = effectiveDispatchNamespaceName(\n nsConfig,\n env,\n config.tenant,\n );\n const scriptName = tenantDispatchScriptName(\n options.product,\n options.workspace,\n env,\n config.tenant,\n );\n const desiredRoles = resolveShardRoles(\n config.tenant.d1Shards ?? [],\n options.shardsRaw,\n );\n\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n\n let existing = state.getTenant(options.product, options.workspace);\n // \"ready\" is a no-op only when the existing layout already covers\n // every requested shard role. Adding a new role re-opens the tenant\n // for incremental provisioning without forcing a full re-deploy.\n if (existing?.provisioningStatus === \"ready\") {\n const have = new Set((existing.d1Shards ?? []).map((s) => s.role));\n const missing = desiredRoles.filter((r) => !have.has(r));\n if (missing.length === 0) {\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} already ready; nothing to do.`,\n );\n if (options.json) {\n const result: ProvisionTenantResult = {\n status: \"noop\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName,\n dispatchNamespaceName,\n shards: (existing.d1Shards ?? []).map((s) => ({\n role: s.role,\n derivedName: s.derivedName,\n cfId: s.cfId,\n })),\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n return;\n }\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} ready but missing shards: ${missing.join(\", \")}; extending layout.`,\n );\n }\n\n const compatDate =\n options.compatibilityDate ??\n config.compatibility_date ??\n new Date().toISOString().slice(0, 10);\n\n const entryOpts = {\n product: options.product,\n workspace: options.workspace,\n dispatchNamespaceName,\n scriptName,\n };\n\n if (!existing) {\n state.setTenant(buildTenantEntry(entryOpts, \"pending\", undefined));\n await state.persist(api);\n existing = state.getTenant(options.product, options.workspace)!;\n }\n\n // Reconcile each requested shard role. We list D1 once per call when\n // we need to (some role is missing from state) and reuse the result\n // for every subsequent role.\n let allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null = null;\n const shards: TenantD1ShardRef[] = [...(existing.d1Shards ?? [])];\n\n for (const role of desiredRoles) {\n if (shards.find((s) => s.role === role)) continue;\n const derivedName = tenantShardDatabaseName(\n role,\n options.workspace,\n options.product,\n config.tenant.id,\n env,\n );\n if (allD1 === null) {\n allD1 = await api.d1ListAll();\n }\n let cfId = allD1.find((d) => d.name === derivedName)?.uuid;\n if (cfId) {\n // Adopt an existing D1 (e.g. partial-failure resume, or\n // operator created it out-of-band) without re-issuing POST.\n logApplyChange({\n kind: \"d1\",\n action: \"no_change\",\n logical: `${role}-shard`,\n derived: derivedName,\n });\n } else {\n logApplyChange({\n kind: \"d1\",\n action: \"create\",\n logical: `${role}-shard`,\n derived: derivedName,\n });\n const created = await api.d1Create(derivedName);\n cfId = created.uuid;\n }\n shards.push({ role, derivedName, cfId });\n // Persist after each shard so a network failure mid-loop leaves\n // the already-created shards recorded — `provision-tenant` is\n // resumable role-by-role.\n state.setTenant(\n buildTenantEntry(entryOpts, \"d1_created\", existing, { d1Shards: shards }),\n );\n await state.persist(api);\n existing = state.getTenant(options.product, options.workspace)!;\n }\n\n // Resolve dispatch script bindings from shards + config.\n const naming = namingFromConfig(config);\n const bindings = resolveTenantBindings(\n config,\n shards,\n naming,\n state,\n env,\n accountId,\n );\n\n // Optionally run migrations on each shard before uploading the script.\n if (options.migrate !== false && shards.length > 0) {\n await migrateTenantShards(config, shards, env, naming);\n }\n\n let form: FormData;\n const formOpts = {\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags,\n bindings,\n };\n if (options.main) {\n const mainPath = resolve(process.cwd(), options.main);\n form = buildSingleModuleDispatchForm(mainPath, formOpts);\n } else if (options.artifactKey) {\n const bucket = tamerArtifactsBucketName();\n const fullKey = `${tamerArtifactsKeyPrefix(env)}${options.artifactKey}`;\n const body = await api.r2GetObject(bucket, fullKey);\n const mod =\n options.moduleName?.trim() ||\n basename(options.artifactKey).replace(/\\.(zip|tar|gz)$/i, \"\") ||\n \"worker.js\";\n form = buildSingleModuleDispatchFormFromBuffer(mod, body, formOpts);\n } else {\n throw new Error(\"provision-tenant: provide --main or --artifact-key\");\n }\n\n await api.dispatchNamespaceScriptPut(dispatchNamespaceName, scriptName, form);\n\n state.setTenant(\n buildTenantEntry(entryOpts, \"ready\", existing, { d1Shards: shards }),\n );\n await state.persist(api);\n\n const shardsSummary =\n shards.length > 0\n ? `shards: ${shards.map((s) => s.role).join(\", \")}`\n : \"no shards declared\";\n console.log(\n `Provisioned tenant ${tenantStateKey(options.product, options.workspace)} → script ${scriptName} in ${dispatchNamespaceName} (${shardsSummary})`,\n );\n\n if (options.json) {\n const result: ProvisionTenantResult = {\n status: \"ready\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName,\n dispatchNamespaceName,\n shards: shards.map((s) => ({\n role: s.role,\n derivedName: s.derivedName,\n cfId: s.cfId,\n })),\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n}\n\n/**\n * Run D1 migrations on each tenant shard by writing a temporary wrangler.json\n * with the shard binding and invoking `wrangler d1 migrations apply`.\n */\nasync function migrateTenantShards(\n config: import(\"../../types.js\").CfiConfig,\n shards: TenantD1ShardRef[],\n env: string,\n naming: import(\"../../core/naming/NamingEngine.js\").NamingEngine,\n): Promise<void> {\n const { writeFileSync, unlinkSync, mkdirSync } = await import(\"fs\");\n const { join } = await import(\"path\");\n const { tmpdir } = await import(\"os\");\n const { spawnWranglerSync } = await import(\n \"../../core/wrangler/wranglerSpawn.js\"\n );\n const { wranglerConfigCliArgs } = await import(\n \"../../core/wrangler/wranglerOutFile.js\"\n );\n const { resolveTenantD1Bindings } = await import(\n \"../../core/tenant/resolveTenantBindings.js\"\n );\n\n // Collect migration dirs from config: registryRole → migrationsDir\n const workers = config.workers\n ? Object.values(config.workers as Record<string, unknown>)\n : config.worker\n ? [config.worker]\n : [];\n const roleToMigrations: Map<string, { dir?: string; table?: string }> = new Map();\n for (const wc of workers) {\n const d1s = (wc as { resources?: { d1?: Array<{ registryRole?: string; migrationsDir?: string; migrationsTable?: string }> } }).resources?.d1;\n if (!d1s) continue;\n for (const d1 of d1s) {\n const role = d1.registryRole ?? \"\";\n if (role) {\n roleToMigrations.set(role, { dir: d1.migrationsDir, table: d1.migrationsTable });\n }\n }\n }\n\n const d1Bindings = resolveTenantD1Bindings(config, shards, naming);\n const tempDir = join(tmpdir(), `tamer-tenant-migrate-${Date.now()}`);\n mkdirSync(tempDir, { recursive: true });\n\n for (let i = 0; i < shards.length; i++) {\n const shard = shards[i]!;\n const binding = d1Bindings[i]!;\n const migInfo = roleToMigrations.get(shard.role);\n if (!migInfo?.dir) {\n console.log(` migrate: skipping ${shard.role} (no migrationsDir configured)`);\n continue;\n }\n\n const tempWrangler = join(tempDir, `wrangler.${shard.role}.json`);\n const tempConfig = {\n name: `tenant-migrate-${shard.role}`,\n d1_databases: [{ name: binding.name, database_id: shard.cfId }],\n compatibility_date: config.compatibility_date ?? \"2024-01-01\",\n };\n writeFileSync(tempWrangler, JSON.stringify(tempConfig, null, 2));\n\n const migrateArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(`wrangler.${shard.role}.json`),\n \"d1\",\n \"migrations\",\n \"apply\",\n binding.name,\n \"--remote\",\n ];\n if (migInfo.table) {\n migrateArgs.push(\"--migrations-table\", migInfo.table);\n }\n\n console.log(` migrate: applying migrations for ${shard.role} (${shard.derivedName})...`);\n const result = spawnWranglerSync(migrateArgs, {\n cwd: migInfo.dir!,\n stdio: \"inherit\",\n });\n if (result.status !== 0) {\n console.warn(` migrate: failed for ${shard.role} (exit ${result.status})`);\n }\n try {\n unlinkSync(tempWrangler);\n } catch {\n /* best-effort cleanup */\n }\n }\n\n try {\n unlinkSync(tempDir);\n } catch {\n /* best-effort */\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"resolveTenantBindings-DJDPhILD.mjs","names":["workers: Record<string, WorkerConfig>","bindings: DispatchScriptBindings","refCtx: ReferenceContext","vars: Record<string, string>"],"sources":["../src/core/tenant/resolveTenantBindings.ts"],"sourcesContent":["/**\n * Resolve dispatch script bindings for a provisioned tenant.\n *\n * D1 bindings derive automatically from `resources.d1[].registryRole`\n * matched to the tenant's shard roles. Vars and services come from\n * `tenant.dispatchVars` / `tenant.dispatchServices` (resolved against\n * state for `${tamer:...}` references).\n */\nimport type { CfiConfig, WorkerConfig, WorkerResources, TamerResolvableString } from \"../../types.js\";\nimport type { NamingEngine } from \"../../core/naming/NamingEngine.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { TenantD1ShardRef } from \"../../types.js\";\nimport type { DispatchScriptBindings } from \"../../core/wfp/buildDispatchUploadForm.js\";\nimport { materializeTamerResolvable } from \"../../dx/normalize.js\";\nimport {\n resolveReferencesInString,\n type ReferenceContext,\n} from \"../../core/references/references.js\";\n\n/**\n * Collect all D1 resource configs across all workers in the stack.\n * Returns a map of `registryRole` → `{ binding, migrationsDir, migrationsTable }`.\n */\nfunction collectD1RegistryRoles(\n config: CfiConfig,\n): Map<string, { binding: string; migrationsDir?: string; migrationsTable?: string }> {\n const out = new Map<\n string,\n { binding: string; migrationsDir?: string; migrationsTable?: string }\n >();\n const workers: Record<string, WorkerConfig> = config.workers\n ? (config.workers as Record<string, WorkerConfig>)\n : config.worker\n ? { default: config.worker as WorkerConfig }\n : {};\n for (const wc of Object.values(workers)) {\n const d1s = (wc as WorkerConfig & { resources?: WorkerResources }).resources?.d1;\n if (!d1s) continue;\n for (const d1 of d1s) {\n const role = d1.registryRole ?? d1.logicalName;\n const binding = d1.binding?.trim() || `DB_${role.toUpperCase()}`;\n if (!out.has(role)) {\n out.set(role, {\n binding,\n migrationsDir: d1.migrationsDir,\n migrationsTable: d1.migrationsTable,\n });\n }\n }\n }\n return out;\n}\n\n/**\n * Resolve D1 bindings for the tenant dispatch script.\n * Matches shard roles to `resources.d1[].registryRole` and produces\n * `{ name: binding, id: cfId }` entries for the multipart metadata.\n */\nexport function resolveTenantD1Bindings(\n config: CfiConfig,\n shards: TenantD1ShardRef[],\n naming: NamingEngine,\n): Array<{ name: string; id: string }> {\n const registryRoles = collectD1RegistryRoles(config);\n return shards.map((shard) => {\n const roleInfo = registryRoles.get(shard.role);\n const binding = roleInfo?.binding ?? naming.workflowBindingKey(shard.role);\n return { name: binding, id: shard.cfId };\n });\n}\n\n/**\n * Resolve all dispatch script bindings (D1 + vars + services).\n * References in vars/services are resolved against state.\n */\nexport function resolveTenantBindings(\n config: CfiConfig,\n shards: TenantD1ShardRef[],\n naming: NamingEngine,\n state: StateManager,\n env: string,\n accountId: string,\n): DispatchScriptBindings {\n const bindings: DispatchScriptBindings = {};\n\n // D1 bindings from shard roles\n const d1Bindings = resolveTenantD1Bindings(config, shards, naming);\n if (d1Bindings.length > 0) {\n bindings.d1_databases = d1Bindings;\n }\n\n // Vars from config.tenant.dispatchVars\n if (config.tenant.dispatchVars) {\n const refCtx: ReferenceContext = {\n config,\n env,\n state,\n naming,\n accountId,\n };\n const vars: Record<string, string> = {};\n for (const [key, value] of Object.entries(config.tenant.dispatchVars)) {\n vars[key] = resolveReferencesInString(\n materializeTamerResolvable(value),\n refCtx,\n `tenant.dispatchVars.${key}`,\n );\n }\n if (Object.keys(vars).length > 0) {\n bindings.vars = vars;\n }\n }\n\n // Service bindings from config.tenant.dispatchServices\n if (config.tenant.dispatchServices?.length) {\n const refCtx: ReferenceContext = {\n config,\n env,\n state,\n naming,\n accountId,\n };\n bindings.services = config.tenant.dispatchServices.map((s) => ({\n name: s.name,\n service: resolveReferencesInString(\n materializeTamerResolvable(s.service),\n refCtx,\n `tenant.dispatchServices.${s.name}.service`,\n ),\n ...(s.environment ? { environment: s.environment } : {}),\n }));\n }\n\n return bindings;\n}\n"],"mappings":";;;;;;;;AAuBA,SAAS,uBACP,QACoF;CACpF,MAAM,sBAAM,IAAI,KAGb;CACH,MAAMA,UAAwC,OAAO,UAChD,OAAO,UACR,OAAO,SACL,EAAE,SAAS,OAAO,QAAwB,GAC1C,EAAE;AACR,MAAK,MAAM,MAAM,OAAO,OAAO,QAAQ,EAAE;EACvC,MAAM,MAAO,GAAsD,WAAW;AAC9E,MAAI,CAAC,IAAK;AACV,OAAK,MAAM,MAAM,KAAK;GACpB,MAAM,OAAO,GAAG,gBAAgB,GAAG;GACnC,MAAM,UAAU,GAAG,SAAS,MAAM,IAAI,MAAM,KAAK,aAAa;AAC9D,OAAI,CAAC,IAAI,IAAI,KAAK,CAChB,KAAI,IAAI,MAAM;IACZ;IACA,eAAe,GAAG;IAClB,iBAAiB,GAAG;IACrB,CAAC;;;AAIR,QAAO;;;;;;;AAQT,SAAgB,wBACd,QACA,QACA,QACqC;CACrC,MAAM,gBAAgB,uBAAuB,OAAO;AACpD,QAAO,OAAO,KAAK,UAAU;AAG3B,SAAO;GAAE,MAFQ,cAAc,IAAI,MAAM,KAAK,EACpB,WAAW,OAAO,mBAAmB,MAAM,KAAK;GAClD,IAAI,MAAM;GAAM;GACxC;;;;;;AAOJ,SAAgB,sBACd,QACA,QACA,QACA,OACA,KACA,WACwB;CACxB,MAAMC,WAAmC,EAAE;CAG3C,MAAM,aAAa,wBAAwB,QAAQ,QAAQ,OAAO;AAClE,KAAI,WAAW,SAAS,EACtB,UAAS,eAAe;AAI1B,KAAI,OAAO,OAAO,cAAc;EAC9B,MAAMC,SAA2B;GAC/B;GACA;GACA;GACA;GACA;GACD;EACD,MAAMC,OAA+B,EAAE;AACvC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,OAAO,aAAa,CACnE,MAAK,OAAO,0BACV,2BAA2B,MAAM,EACjC,QACA,uBAAuB,MACxB;AAEH,MAAI,OAAO,KAAK,KAAK,CAAC,SAAS,EAC7B,UAAS,OAAO;;AAKpB,KAAI,OAAO,OAAO,kBAAkB,QAAQ;EAC1C,MAAMD,SAA2B;GAC/B;GACA;GACA;GACA;GACA;GACD;AACD,WAAS,WAAW,OAAO,OAAO,iBAAiB,KAAK,OAAO;GAC7D,MAAM,EAAE;GACR,SAAS,0BACP,2BAA2B,EAAE,QAAQ,EACrC,QACA,2BAA2B,EAAE,KAAK,UACnC;GACD,GAAI,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,GAAG,EAAE;GACxD,EAAE;;AAGL,QAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant-migrate-CT-qyTUD.mjs","names":[],"sources":["../src/cli/commands/tenant-migrate.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { resolveTenantD1Bindings } from \"../../core/tenant/resolveTenantBindings.js\";\nimport { spawnWranglerSync } from \"../../core/wrangler/wranglerSpawn.js\";\nimport { wranglerConfigCliArgs } from \"../../core/wrangler/wranglerOutFile.js\";\nimport { writeFileSync, unlinkSync, mkdirSync } from \"fs\";\nimport { join } from \"path\";\nimport { tmpdir } from \"os\";\n\n/**\n * `tamer wfp tenant migrate --env dev --product X --workspace Y`\n *\n * Runs D1 migrations on each shard for the named tenant. Matches shard roles\n * to `resources.d1[].registryRole` → `migrationsDir` to find the migration\n * source directory. Writes a temporary wrangler.json with the shard binding\n * and invokes `wrangler d1 migrations apply --remote`.\n */\nexport async function runTenantMigrate(options: {\n env?: string;\n product: string;\n workspace: string;\n configPath?: string;\n}): Promise<void> {\n const env = options.env;\n if (!env) {\n throw new Error(\"tenant migrate: --env is required\");\n }\n if (env === \"local\") {\n throw new Error(\"tenant migrate requires a non-local --env.\");\n }\n\n const config = await loadConfig(options.configPath, { env });\n const accountId =\n 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\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\n const tenant = state.getTenant(options.product, options.workspace);\n if (!tenant) {\n throw new Error(\n `Tenant \"${options.product}:${options.workspace}\" not found in env ${env}.`,\n );\n }\n if (!tenant.d1Shards || tenant.d1Shards.length === 0) {\n console.log(`Tenant \"${options.product}:${options.workspace}\" has no D1 shards.`);\n return;\n }\n\n // Collect migration dirs from config: registryRole → migrationsDir\n const workers = config.workers\n ? Object.values(config.workers as Record<string, unknown>)\n : config.worker\n ? [config.worker]\n : [];\n const roleToMigrations = new Map<string, { dir?: string; table?: string }>();\n for (const wc of workers) {\n const d1s = (wc as {\n resources?: { d1?: Array<{ registryRole?: string; migrationsDir?: string; migrationsTable?: string }> };\n }).resources?.d1;\n if (!d1s) continue;\n for (const d1 of d1s) {\n const role = d1.registryRole ?? \"\";\n if (role) {\n roleToMigrations.set(role, { dir: d1.migrationsDir, table: d1.migrationsTable });\n }\n }\n }\n\n const d1Bindings = resolveTenantD1Bindings(config, tenant.d1Shards, naming);\n const tempDir = join(tmpdir(), `tamer-tenant-migrate-${Date.now()}`);\n mkdirSync(tempDir, { recursive: true });\n\n let migrated = 0;\n let skipped = 0;\n\n for (let i = 0; i < tenant.d1Shards.length; i++) {\n const shard = tenant.d1Shards[i]!;\n const binding = d1Bindings[i]!;\n const migInfo = roleToMigrations.get(shard.role);\n if (!migInfo?.dir) {\n console.log(` migrate: skipping ${shard.role} (no migrationsDir configured)`);\n skipped++;\n continue;\n }\n\n const tempWrangler = join(tempDir, `wrangler.${shard.role}.json`);\n const tempConfig = {\n name: `tenant-migrate-${shard.role}`,\n d1_databases: [{ name: binding.name, database_id: shard.cfId }],\n compatibility_date: config.compatibility_date ?? \"2024-01-01\",\n };\n writeFileSync(tempWrangler, JSON.stringify(tempConfig, null, 2));\n\n const migrateArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(`wrangler.${shard.role}.json`),\n \"d1\",\n \"migrations\",\n \"apply\",\n binding.name,\n \"--remote\",\n ];\n if (migInfo.table) {\n migrateArgs.push(\"--migrations-table\", migInfo.table);\n }\n\n console.log(` migrate: applying for ${shard.role} (${shard.derivedName})...`);\n const result = spawnWranglerSync(migrateArgs, {\n cwd: migInfo.dir!,\n stdio: \"inherit\",\n });\n if (result.status === 0) {\n migrated++;\n } else {\n console.warn(` migrate: failed for ${shard.role} (exit ${result.status})`);\n }\n try {\n unlinkSync(tempWrangler);\n } catch {\n /* best-effort */\n }\n }\n\n try {\n unlinkSync(tempDir);\n } catch {\n /* best-effort */\n }\n\n console.log(\n `\\nMigrations complete: ${migrated} shard(s) migrated, ${skipped} skipped.`,\n );\n}\n"],"mappings":"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"}