@dragonmastery/tamer 0.31.4 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +2 -0
  2. package/dist/{apply-C_70Hgcf.mjs → apply-BsVQ9cbK.mjs} +25 -11
  3. package/dist/apply-BsVQ9cbK.mjs.map +1 -0
  4. package/dist/{applyTarget-Ce_mtRQX.mjs → applyTarget-BJlVrED9.mjs} +3 -3
  5. package/dist/{applyTarget-Ce_mtRQX.mjs.map → applyTarget-BJlVrED9.mjs.map} +1 -1
  6. package/dist/{bootstrap-CQS5s33A.mjs → bootstrap-CV6OT-c9.mjs} +2 -2
  7. package/dist/{bootstrap-CQS5s33A.mjs.map → bootstrap-CV6OT-c9.mjs.map} +1 -1
  8. package/dist/{buildDispatchUploadForm-CVnPmHg4.mjs → buildDispatchUploadForm-BNDcS5_9.mjs} +1 -1
  9. package/dist/{buildDispatchUploadForm-CVnPmHg4.mjs.map → buildDispatchUploadForm-BNDcS5_9.mjs.map} +1 -1
  10. package/dist/{cloudflareSnapshot-CFErW72O.mjs → cloudflareSnapshot-CMbPQIsN.mjs} +4 -4
  11. package/dist/{cloudflareSnapshot-CFErW72O.mjs.map → cloudflareSnapshot-CMbPQIsN.mjs.map} +1 -1
  12. package/dist/{deploy-D-GXzsWR.mjs → deploy-BP65Wvm1.mjs} +18 -7
  13. package/dist/deploy-BP65Wvm1.mjs.map +1 -0
  14. package/dist/{destroy-DJRw_qLX.mjs → destroy-DPSuQg9v.mjs} +9 -9
  15. package/dist/{destroy-DJRw_qLX.mjs.map → destroy-DPSuQg9v.mjs.map} +1 -1
  16. package/dist/{destroy-tenant-DFHfNImL.mjs → destroy-tenant-hyEb1gEz.mjs} +1 -1
  17. package/dist/{destroy-tenant-DFHfNImL.mjs.map → destroy-tenant-hyEb1gEz.mjs.map} +1 -1
  18. package/dist/{dev-caJxohHI.mjs → dev-Bug5l5OZ.mjs} +6 -6
  19. package/dist/{dev-caJxohHI.mjs.map → dev-Bug5l5OZ.mjs.map} +1 -1
  20. package/dist/{dns-records.resolve-8a_eHfVI.mjs → dns-records.resolve-BBTlY3T5.mjs} +1 -1
  21. package/dist/{dns-records.resolve-BB2agPAb.mjs → dns-records.resolve-DV6XBZf3.mjs} +1 -1
  22. package/dist/{dns-records.resolve-BB2agPAb.mjs.map → dns-records.resolve-DV6XBZf3.mjs.map} +1 -1
  23. package/dist/{dns-records.sync-Ck0Q2OK3.mjs → dns-records.sync-FyzKl-Ph.mjs} +2 -2
  24. package/dist/{dns-records.sync-Ck0Q2OK3.mjs.map → dns-records.sync-FyzKl-Ph.mjs.map} +1 -1
  25. package/dist/{doctor-32YLAXXl.mjs → doctor-BwuEPYM6.mjs} +1 -1
  26. package/dist/{doctor-32YLAXXl.mjs.map → doctor-BwuEPYM6.mjs.map} +1 -1
  27. package/dist/{drift-C-qnJ-mH.mjs → drift-Bp-P13-B.mjs} +6 -6
  28. package/dist/{drift-C-qnJ-mH.mjs.map → drift-Bp-P13-B.mjs.map} +1 -1
  29. package/dist/drift-RM0vq3uF.mjs +8 -0
  30. package/dist/emit-DTpaIMkn.mjs +141 -0
  31. package/dist/emit-DTpaIMkn.mjs.map +1 -0
  32. package/dist/{events-Bor7XgLC.mjs → events-B7Lencm8.mjs} +1 -1
  33. package/dist/{events-Bor7XgLC.mjs.map → events-B7Lencm8.mjs.map} +1 -1
  34. package/dist/{generator-gvCy7ouY.mjs → generator-ZTEeHlft.mjs} +2 -2
  35. package/dist/{generator-gvCy7ouY.mjs.map → generator-ZTEeHlft.mjs.map} +1 -1
  36. package/dist/{import-Vn9lhWic.mjs → import-Ciq9MN3v.mjs} +4 -4
  37. package/dist/{import-Vn9lhWic.mjs.map → import-Ciq9MN3v.mjs.map} +1 -1
  38. package/dist/index.d.mts +121 -21
  39. package/dist/index.d.mts.map +1 -1
  40. package/dist/{logpush-job-DJPlpnRu.mjs → logpush-job-GqVKG_HI.mjs} +1 -1
  41. package/dist/{logpush-job-DJPlpnRu.mjs.map → logpush-job-GqVKG_HI.mjs.map} +1 -1
  42. package/dist/{migrate-skvsDG6d.mjs → migrate-ODEnh6Tv.mjs} +4 -4
  43. package/dist/{migrate-skvsDG6d.mjs.map → migrate-ODEnh6Tv.mjs.map} +1 -1
  44. package/dist/normalize-DVSTRZhO.mjs.map +1 -1
  45. package/dist/{plan-BLy8FaE2.mjs → plan-BuG21Atq.mjs} +9 -9
  46. package/dist/{plan-BLy8FaE2.mjs.map → plan-BuG21Atq.mjs.map} +1 -1
  47. package/dist/{provision-tenant-CleOYFwr.mjs → provision-tenant-BHDWTC2U.mjs} +3 -3
  48. package/dist/{provision-tenant-CleOYFwr.mjs.map → provision-tenant-BHDWTC2U.mjs.map} +1 -1
  49. package/dist/{registry-EWWdkLf7.mjs → registry-BlHEOKlN.mjs} +75 -28
  50. package/dist/registry-BlHEOKlN.mjs.map +1 -0
  51. package/dist/{stackOutputs-BLvUMsQO.mjs → stackOutputs-CkpNSng8.mjs} +1 -1
  52. package/dist/{stackOutputs-BLvUMsQO.mjs.map → stackOutputs-CkpNSng8.mjs.map} +1 -1
  53. package/dist/{status-BxStsax8.mjs → status-PlMHsDzT.mjs} +5 -5
  54. package/dist/{status-BxStsax8.mjs.map → status-PlMHsDzT.mjs.map} +1 -1
  55. package/dist/{sync-KTzMVc_o.mjs → sync-YKZ5HD8v.mjs} +5 -5
  56. package/dist/{sync-KTzMVc_o.mjs.map → sync-YKZ5HD8v.mjs.map} +1 -1
  57. package/dist/tamer.mjs +69 -37
  58. package/dist/tamer.mjs.map +1 -1
  59. package/dist/{tamerArtifactsR2-DnUJmxnO.mjs → tamerArtifactsR2-Ba29OVp9.mjs} +1 -1
  60. package/dist/{tamerArtifactsR2-DnUJmxnO.mjs.map → tamerArtifactsR2-Ba29OVp9.mjs.map} +1 -1
  61. package/dist/{types-D7qKnGsm.mjs → types-DuK39eYA.mjs} +4 -4
  62. package/dist/{types-D7qKnGsm.mjs.map → types-DuK39eYA.mjs.map} +1 -1
  63. package/dist/{verifyPlanFile-CR2bELdE.mjs → verifyPlanFile-BBAwWzUo.mjs} +2 -2
  64. package/dist/{verifyPlanFile-CR2bELdE.mjs.map → verifyPlanFile-BBAwWzUo.mjs.map} +1 -1
  65. package/dist/{wfp-delete-CDBFqmrM.mjs → wfp-delete-CQc9tveU.mjs} +1 -1
  66. package/dist/{wfp-delete-CDBFqmrM.mjs.map → wfp-delete-CQc9tveU.mjs.map} +1 -1
  67. package/dist/{wfp-put-BrwICc9i.mjs → wfp-put-CVw8cloM.mjs} +2 -2
  68. package/dist/{wfp-put-BrwICc9i.mjs.map → wfp-put-CVw8cloM.mjs.map} +1 -1
  69. package/dist/{worker-route-tOqVbhv3.mjs → worker-route-CqBDvjgo.mjs} +2 -2
  70. package/dist/{worker-route-tOqVbhv3.mjs.map → worker-route-CqBDvjgo.mjs.map} +1 -1
  71. package/dist/{workers-6r2ONF9J.mjs → workers-DAv1ze8_.mjs} +2 -2
  72. package/dist/{workers-6r2ONF9J.mjs.map → workers-DAv1ze8_.mjs.map} +1 -1
  73. package/dist/{wranglerSpawn-CUlo2qOJ.mjs → wranglerSpawn-B3TKjpt2.mjs} +1 -1
  74. package/dist/{wranglerSpawn-CUlo2qOJ.mjs.map → wranglerSpawn-B3TKjpt2.mjs.map} +1 -1
  75. package/dist/{zoneResolver-DNNNmO_w.mjs → zoneResolver-D9bz6-0l.mjs} +1 -1
  76. package/dist/{zoneResolver-DNNNmO_w.mjs.map → zoneResolver-D9bz6-0l.mjs.map} +1 -1
  77. package/package.json +1 -1
  78. package/dist/apply-C_70Hgcf.mjs.map +0 -1
  79. package/dist/deploy-D-GXzsWR.mjs.map +0 -1
  80. package/dist/drift-5_LwAdZo.mjs +0 -8
  81. package/dist/registry-EWWdkLf7.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"provision-tenant-CleOYFwr.mjs","names":["result: ProvisionTenantResult","allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null","shards: TenantD1ShardRef[]","form: FormData"],"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 { tamerArtifactsBucketName } 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}): 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}): 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 let form: FormData;\n if (options.main) {\n const mainPath = resolve(process.cwd(), options.main);\n form = buildSingleModuleDispatchForm(mainPath, {\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags,\n });\n } else if (options.artifactKey) {\n const bucket = tamerArtifactsBucketName(env);\n const body = await api.r2GetObject(bucket, options.artifactKey);\n const mod =\n options.moduleName?.trim() ||\n basename(options.artifactKey).replace(/\\.(zip|tar|gz)$/i, \"\") ||\n \"worker.js\";\n form = buildSingleModuleDispatchFormFromBuffer(mod, body, {\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags,\n });\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"],"mappings":"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"}
1
+ {"version":3,"file":"provision-tenant-BHDWTC2U.mjs","names":["result: ProvisionTenantResult","allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null","shards: TenantD1ShardRef[]","form: FormData"],"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 { tamerArtifactsBucketName } 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}): 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}): 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 let form: FormData;\n if (options.main) {\n const mainPath = resolve(process.cwd(), options.main);\n form = buildSingleModuleDispatchForm(mainPath, {\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags,\n });\n } else if (options.artifactKey) {\n const bucket = tamerArtifactsBucketName(env);\n const body = await api.r2GetObject(bucket, options.artifactKey);\n const mod =\n options.moduleName?.trim() ||\n basename(options.artifactKey).replace(/\\.(zip|tar|gz)$/i, \"\") ||\n \"worker.js\";\n form = buildSingleModuleDispatchFormFromBuffer(mod, body, {\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags,\n });\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"],"mappings":"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"}
@@ -91,26 +91,56 @@ function d1CloudflareDatabaseName(config, env, naming) {
91
91
 
92
92
  //#endregion
93
93
  //#region src/features/d1/d1.naming.ts
94
+ /** Normalize config/import shard dates to ISO `YYYY-MM-DD`. */
95
+ function normalizeShardDateIso(raw) {
96
+ const trimmed = raw.trim();
97
+ if (/^\d{8}$/.test(trimmed)) return `${trimmed.slice(0, 4)}-${trimmed.slice(4, 6)}-${trimmed.slice(6, 8)}`;
98
+ if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) return trimmed;
99
+ throw new Error(`Invalid shardDate "${raw}" — use YYYY-MM-DD or YYYYMMDD`);
100
+ }
94
101
  function d1DeriveName(config, env, shardDate, naming) {
95
102
  if (config.type === "single") return resolveD1CloudflareName(config, env, naming);
96
- if (!shardDate) throw new Error(`Sharded D1 "${config.logicalName}" requires shardDate`);
97
- return resolveD1CloudflareName(config, env, naming, shardDate);
103
+ const resolved = shardDate ?? config.shardDate;
104
+ if (!resolved) throw new Error(`Sharded D1 "${config.logicalName}" requires shardDate`);
105
+ return resolveD1CloudflareName(config, env, naming, normalizeShardDateIso(resolved));
106
+ }
107
+ function d1DeriveBindingKey(config, shardDate, naming) {
108
+ const explicit = config.binding?.trim();
109
+ if (config.type === "single") return explicit || naming.d1SingleBindingKey(config.logicalName);
110
+ if (explicit) return explicit;
111
+ const resolved = shardDate ?? config.shardDate;
112
+ if (!resolved) throw new Error(`Sharded D1 "${config.logicalName}" requires shardDate`);
113
+ return naming.d1ShardBindingKey(config.logicalName, normalizeShardDateIso(resolved));
114
+ }
115
+ /** Shard date stored on state rows (ISO), from config pin or CF name extraction. */
116
+ function d1ResolveShardDateIso(config, extractedFromName) {
117
+ if (config.type !== "sharded") return void 0;
118
+ if (config.shardDate) return normalizeShardDateIso(config.shardDate);
119
+ return extractedFromName ?? void 0;
98
120
  }
99
121
  function d1MatchPattern(config, env, naming) {
100
122
  if (config.type === "single") {
101
123
  const exactName = resolveD1CloudflareName(config, env, naming);
102
124
  return (name) => name === exactName;
103
125
  }
126
+ const pinnedIso = config.shardDate ? normalizeShardDateIso(config.shardDate) : void 0;
104
127
  if (config.cloudflareName || naming.hasD1ShardConvention()) return (name) => {
105
128
  const shardDate = d1ExtractShardDate(name, naming);
106
129
  if (!shardDate) return false;
130
+ if (pinnedIso && normalizeShardDateIso(shardDate) !== pinnedIso) return false;
107
131
  try {
108
132
  return resolveD1CloudflareName(config, env, naming, shardDate) === name;
109
133
  } catch {
110
134
  return false;
111
135
  }
112
136
  };
113
- return naming.d1MatchPattern(config.logicalName, env);
137
+ const base = naming.d1MatchPattern(config.logicalName, env);
138
+ if (!pinnedIso) return base;
139
+ return (name) => {
140
+ if (!base(name)) return false;
141
+ const shardDate = d1ExtractShardDate(name, naming);
142
+ return shardDate !== null && normalizeShardDateIso(shardDate) === pinnedIso;
143
+ };
114
144
  }
115
145
  function d1ExtractShardDate(name, naming) {
116
146
  return naming.extractD1ShardDate(name);
@@ -124,6 +154,19 @@ function todayNoDashes() {
124
154
  function todayIso$1() {
125
155
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
126
156
  }
157
+ function resolveInitialShardDate(config) {
158
+ if (config.shardDate) {
159
+ const iso = normalizeShardDateIso(config.shardDate);
160
+ return {
161
+ iso,
162
+ compact: iso.replace(/-/g, "")
163
+ };
164
+ }
165
+ return {
166
+ iso: todayIso$1(),
167
+ compact: todayNoDashes()
168
+ };
169
+ }
127
170
  async function d1Apply(resources, tenant, env, api, state, naming, addShard) {
128
171
  for (const config of resources) {
129
172
  if (config.type === "single") {
@@ -138,7 +181,7 @@ async function d1Apply(resources, tenant, env, api, state, naming, addShard) {
138
181
  type: "d1_database",
139
182
  logicalName: config.logicalName,
140
183
  derivedName: derivedName$1,
141
- bindingKey: config.binding?.trim() || naming.d1SingleBindingKey(config.logicalName),
184
+ bindingKey: d1DeriveBindingKey(config, void 0, naming),
142
185
  cfId: uuid$1,
143
186
  migrationsDir: config.migrationsDir,
144
187
  preserveOnDestroy: false,
@@ -149,16 +192,16 @@ async function d1Apply(resources, tenant, env, api, state, naming, addShard) {
149
192
  }
150
193
  if (addShard && addShard !== config.logicalName) continue;
151
194
  if (addShard && addShard === config.logicalName) {
152
- const shardDate$1 = todayNoDashes();
153
- const derivedName$1 = d1DeriveName(config, env, shardDate$1, naming);
195
+ const { iso: iso$1, compact: compact$1 } = resolveInitialShardDate(config);
196
+ const derivedName$1 = d1DeriveName(config, env, compact$1, naming);
154
197
  if (state.get(derivedName$1)) continue;
155
198
  const { uuid: uuid$1 } = await api.d1Create(derivedName$1);
156
199
  state.set(derivedName$1, {
157
200
  type: "d1_database",
158
201
  logicalName: config.logicalName,
159
- shardDate: todayIso$1(),
202
+ shardDate: iso$1,
160
203
  derivedName: derivedName$1,
161
- bindingKey: naming.d1ShardBindingKey(config.logicalName, shardDate$1),
204
+ bindingKey: d1DeriveBindingKey(config, iso$1, naming),
162
205
  cfId: uuid$1,
163
206
  migrationsDir: config.migrationsDir,
164
207
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -168,15 +211,15 @@ async function d1Apply(resources, tenant, env, api, state, naming, addShard) {
168
211
  }
169
212
  const allResources = state.getAll();
170
213
  if (Object.values(allResources).filter((e) => e.type === "d1_database" && "logicalName" in e && e.logicalName === config.logicalName).length > 0) continue;
171
- const shardDate = todayNoDashes();
172
- const derivedName = d1DeriveName(config, env, shardDate, naming);
214
+ const { iso, compact } = resolveInitialShardDate(config);
215
+ const derivedName = d1DeriveName(config, env, compact, naming);
173
216
  const { uuid } = await api.d1Create(derivedName);
174
217
  state.set(derivedName, {
175
218
  type: "d1_database",
176
219
  logicalName: config.logicalName,
177
- shardDate: todayIso$1(),
220
+ shardDate: iso,
178
221
  derivedName,
179
- bindingKey: naming.d1ShardBindingKey(config.logicalName, shardDate),
222
+ bindingKey: d1DeriveBindingKey(config, iso, naming),
180
223
  cfId: uuid,
181
224
  migrationsDir: config.migrationsDir,
182
225
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -197,7 +240,7 @@ function d1Sync(allD1, resources, tenant, env, state, naming) {
197
240
  type: "d1_database",
198
241
  logicalName: config.logicalName,
199
242
  derivedName: match.name,
200
- bindingKey: config.binding?.trim() || naming.d1SingleBindingKey(config.logicalName),
243
+ bindingKey: d1DeriveBindingKey(config, void 0, naming),
201
244
  cfId: match.uuid,
202
245
  migrationsDir: config.migrationsDir,
203
246
  preserveOnDestroy: d1SkipsProvisionAndMigrate(config),
@@ -208,14 +251,14 @@ function d1Sync(allD1, resources, tenant, env, state, naming) {
208
251
  }
209
252
  const shards = allD1.filter((db) => pattern(db.name));
210
253
  for (const shard of shards) {
211
- const shardDate = d1ExtractShardDate(shard.name, naming);
212
- if (!shardDate) continue;
254
+ const shardDateIso = d1ResolveShardDateIso(config, d1ExtractShardDate(shard.name, naming));
255
+ if (!shardDateIso) continue;
213
256
  state.set(shard.name, {
214
257
  type: "d1_database",
215
258
  logicalName: config.logicalName,
216
- shardDate,
259
+ shardDate: shardDateIso,
217
260
  derivedName: shard.name,
218
- bindingKey: naming.d1ShardBindingKey(config.logicalName, shardDate),
261
+ bindingKey: d1DeriveBindingKey(config, shardDateIso, naming),
219
262
  cfId: shard.uuid,
220
263
  migrationsDir: config.migrationsDir,
221
264
  createdAt: shard.created_at,
@@ -372,7 +415,7 @@ function d1Status(resources, env, state, naming) {
372
415
  const derivedName = d1CloudflareDatabaseName(config, env, naming);
373
416
  const entry = state.get(derivedName);
374
417
  results.push({
375
- binding: config.binding?.trim() || naming.d1SingleBindingKey(config.logicalName),
418
+ binding: d1DeriveBindingKey(config, void 0, naming),
376
419
  name: derivedName,
377
420
  cfId: entry?.cfId ?? "",
378
421
  status: entry ? "ok" : "missing"
@@ -387,12 +430,15 @@ function d1Status(resources, env, state, naming) {
387
430
  cfId: shard.cfId,
388
431
  status: "ok"
389
432
  });
390
- if (shards.length === 0) results.push({
391
- binding: naming.d1ShardBindingKey(config.logicalName, "00000000"),
392
- name: `(no shards for ${config.logicalName})`,
393
- cfId: "",
394
- status: "missing"
395
- });
433
+ if (shards.length === 0) {
434
+ const placeholderDate = config.shardDate ?? "00000000";
435
+ results.push({
436
+ binding: d1DeriveBindingKey(config, placeholderDate, naming),
437
+ name: `(no shards for ${config.logicalName})`,
438
+ cfId: "",
439
+ status: "missing"
440
+ });
441
+ }
396
442
  }
397
443
  return results;
398
444
  }
@@ -476,15 +522,16 @@ const d1Module = {
476
522
  if (!resourceConfig) throw new Error(`import d1: no resources.d1 entry with logicalName "${options.logical}" in the Tamer project config`);
477
523
  const hit = (await api.d1ListAll()).find((d) => d.uuid === options.cfId);
478
524
  if (!hit) throw new Error(`import d1: D1 database with uuid "${options.cfId}" not found in account`);
479
- const derivedName = options.shardDate ? d1DeriveName(resourceConfig, env, options.shardDate, naming) : d1DeriveName(resourceConfig, env, void 0, naming);
525
+ const shardDateIso = options.shardDate ? normalizeShardDateIso(options.shardDate) : resourceConfig.shardDate ? normalizeShardDateIso(resourceConfig.shardDate) : void 0;
526
+ const derivedName = shardDateIso ? d1DeriveName(resourceConfig, env, shardDateIso, naming) : d1DeriveName(resourceConfig, env, void 0, naming);
480
527
  if (hit.name !== derivedName) throw new Error(`import d1: cf name "${hit.name}" does not match derived "${derivedName}" — wrong --logical, missing --shard-date, or naming convention drift?`);
481
- const bindingKey = options.shardDate ? naming.d1ShardBindingKey(options.logical, options.shardDate) : naming.d1SingleBindingKey(options.logical);
528
+ const bindingKey = d1DeriveBindingKey(resourceConfig, shardDateIso, naming);
482
529
  const existing = state.get(derivedName);
483
530
  if (existing && existing.type === "d1_database" && existing.cfId !== options.cfId) throw new Error(`import d1: state already tracks "${derivedName}" with a different cfId`);
484
531
  const entry = {
485
532
  type: "d1_database",
486
533
  logicalName: options.logical,
487
- shardDate: options.shardDate,
534
+ shardDate: shardDateIso ?? options.shardDate,
488
535
  derivedName,
489
536
  bindingKey,
490
537
  cfId: options.cfId,
@@ -3030,4 +3077,4 @@ function getResourceModule(kind) {
3030
3077
 
3031
3078
  //#endregion
3032
3079
  export { d1CloudflareDatabaseName as a, logicalNamesForResourceKind as i, resourceModules as n, d1SkipsProvisionAndMigrate as o, secretsStoreDeriveName as r, getResourceModule as t };
3033
- //# sourceMappingURL=registry-EWWdkLf7.mjs.map
3080
+ //# sourceMappingURL=registry-BlHEOKlN.mjs.map