@dragonmastery/tamer 0.31.5 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{apply-C_70Hgcf.mjs → apply-BsVQ9cbK.mjs} +25 -11
- package/dist/apply-BsVQ9cbK.mjs.map +1 -0
- package/dist/{applyTarget-Ce_mtRQX.mjs → applyTarget-BJlVrED9.mjs} +3 -3
- package/dist/{applyTarget-Ce_mtRQX.mjs.map → applyTarget-BJlVrED9.mjs.map} +1 -1
- package/dist/{bootstrap-CQS5s33A.mjs → bootstrap-CV6OT-c9.mjs} +2 -2
- package/dist/{bootstrap-CQS5s33A.mjs.map → bootstrap-CV6OT-c9.mjs.map} +1 -1
- package/dist/{buildDispatchUploadForm-CVnPmHg4.mjs → buildDispatchUploadForm-BNDcS5_9.mjs} +1 -1
- package/dist/{buildDispatchUploadForm-CVnPmHg4.mjs.map → buildDispatchUploadForm-BNDcS5_9.mjs.map} +1 -1
- package/dist/{cloudflareSnapshot-CFErW72O.mjs → cloudflareSnapshot-CMbPQIsN.mjs} +4 -4
- package/dist/{cloudflareSnapshot-CFErW72O.mjs.map → cloudflareSnapshot-CMbPQIsN.mjs.map} +1 -1
- package/dist/{deploy-D-GXzsWR.mjs → deploy-dNygCucr.mjs} +33 -8
- package/dist/deploy-dNygCucr.mjs.map +1 -0
- package/dist/{destroy-DJRw_qLX.mjs → destroy-C0cKkpWe.mjs} +9 -9
- package/dist/{destroy-DJRw_qLX.mjs.map → destroy-C0cKkpWe.mjs.map} +1 -1
- package/dist/{destroy-tenant-DFHfNImL.mjs → destroy-tenant-hyEb1gEz.mjs} +1 -1
- package/dist/{destroy-tenant-DFHfNImL.mjs.map → destroy-tenant-hyEb1gEz.mjs.map} +1 -1
- package/dist/{dev-caJxohHI.mjs → dev-Bug5l5OZ.mjs} +6 -6
- package/dist/{dev-caJxohHI.mjs.map → dev-Bug5l5OZ.mjs.map} +1 -1
- package/dist/{dns-records.resolve-8a_eHfVI.mjs → dns-records.resolve-BBTlY3T5.mjs} +1 -1
- package/dist/{dns-records.resolve-BB2agPAb.mjs → dns-records.resolve-DV6XBZf3.mjs} +1 -1
- package/dist/{dns-records.resolve-BB2agPAb.mjs.map → dns-records.resolve-DV6XBZf3.mjs.map} +1 -1
- package/dist/{dns-records.sync-Ck0Q2OK3.mjs → dns-records.sync-FyzKl-Ph.mjs} +2 -2
- package/dist/{dns-records.sync-Ck0Q2OK3.mjs.map → dns-records.sync-FyzKl-Ph.mjs.map} +1 -1
- package/dist/{doctor-32YLAXXl.mjs → doctor-BwuEPYM6.mjs} +1 -1
- package/dist/{doctor-32YLAXXl.mjs.map → doctor-BwuEPYM6.mjs.map} +1 -1
- package/dist/{drift-C-qnJ-mH.mjs → drift-B5y9LF5X.mjs} +6 -6
- package/dist/{drift-C-qnJ-mH.mjs.map → drift-B5y9LF5X.mjs.map} +1 -1
- package/dist/drift-DENAf1Oe.mjs +8 -0
- package/dist/emit-DTpaIMkn.mjs +141 -0
- package/dist/emit-DTpaIMkn.mjs.map +1 -0
- package/dist/{events-Bor7XgLC.mjs → events-B7Lencm8.mjs} +1 -1
- package/dist/{events-Bor7XgLC.mjs.map → events-B7Lencm8.mjs.map} +1 -1
- package/dist/{generator-gvCy7ouY.mjs → generator-ZTEeHlft.mjs} +2 -2
- package/dist/{generator-gvCy7ouY.mjs.map → generator-ZTEeHlft.mjs.map} +1 -1
- package/dist/{import-Vn9lhWic.mjs → import-Ciq9MN3v.mjs} +4 -4
- package/dist/{import-Vn9lhWic.mjs.map → import-Ciq9MN3v.mjs.map} +1 -1
- package/dist/index.d.mts +143 -21
- package/dist/index.d.mts.map +1 -1
- package/dist/{logpush-job-DJPlpnRu.mjs → logpush-job-GqVKG_HI.mjs} +1 -1
- package/dist/{logpush-job-DJPlpnRu.mjs.map → logpush-job-GqVKG_HI.mjs.map} +1 -1
- package/dist/{migrate-skvsDG6d.mjs → migrate-DWyUVN1I.mjs} +4 -4
- package/dist/{migrate-skvsDG6d.mjs.map → migrate-DWyUVN1I.mjs.map} +1 -1
- package/dist/normalize-DVSTRZhO.mjs.map +1 -1
- package/dist/{plan-BLy8FaE2.mjs → plan-Blxn-yKr.mjs} +9 -9
- package/dist/{plan-BLy8FaE2.mjs.map → plan-Blxn-yKr.mjs.map} +1 -1
- package/dist/{provision-tenant-CleOYFwr.mjs → provision-tenant-BHDWTC2U.mjs} +3 -3
- package/dist/{provision-tenant-CleOYFwr.mjs.map → provision-tenant-BHDWTC2U.mjs.map} +1 -1
- package/dist/{registry-EWWdkLf7.mjs → registry-BlHEOKlN.mjs} +75 -28
- package/dist/registry-BlHEOKlN.mjs.map +1 -0
- package/dist/{stackOutputs-BLvUMsQO.mjs → stackOutputs-CkpNSng8.mjs} +1 -1
- package/dist/{stackOutputs-BLvUMsQO.mjs.map → stackOutputs-CkpNSng8.mjs.map} +1 -1
- package/dist/{status-BxStsax8.mjs → status-PlMHsDzT.mjs} +5 -5
- package/dist/{status-BxStsax8.mjs.map → status-PlMHsDzT.mjs.map} +1 -1
- package/dist/{sync-KTzMVc_o.mjs → sync-YKZ5HD8v.mjs} +5 -5
- package/dist/{sync-KTzMVc_o.mjs.map → sync-YKZ5HD8v.mjs.map} +1 -1
- package/dist/tamer.mjs +35 -19
- package/dist/tamer.mjs.map +1 -1
- package/dist/{tamerArtifactsR2-DnUJmxnO.mjs → tamerArtifactsR2-Ba29OVp9.mjs} +1 -1
- package/dist/{tamerArtifactsR2-DnUJmxnO.mjs.map → tamerArtifactsR2-Ba29OVp9.mjs.map} +1 -1
- package/dist/{types-D7qKnGsm.mjs → types-CADr4Kck.mjs} +4 -4
- package/dist/{types-D7qKnGsm.mjs.map → types-CADr4Kck.mjs.map} +1 -1
- package/dist/{verifyPlanFile-CR2bELdE.mjs → verifyPlanFile-BBAwWzUo.mjs} +2 -2
- package/dist/{verifyPlanFile-CR2bELdE.mjs.map → verifyPlanFile-BBAwWzUo.mjs.map} +1 -1
- package/dist/{wfp-delete-CDBFqmrM.mjs → wfp-delete-CQc9tveU.mjs} +1 -1
- package/dist/{wfp-delete-CDBFqmrM.mjs.map → wfp-delete-CQc9tveU.mjs.map} +1 -1
- package/dist/{wfp-put-BrwICc9i.mjs → wfp-put-CVw8cloM.mjs} +2 -2
- package/dist/{wfp-put-BrwICc9i.mjs.map → wfp-put-CVw8cloM.mjs.map} +1 -1
- package/dist/{worker-route-tOqVbhv3.mjs → worker-route-CqBDvjgo.mjs} +2 -2
- package/dist/{worker-route-tOqVbhv3.mjs.map → worker-route-CqBDvjgo.mjs.map} +1 -1
- package/dist/{workers-6r2ONF9J.mjs → workers-y9RTOzbC.mjs} +2 -2
- package/dist/{workers-6r2ONF9J.mjs.map → workers-y9RTOzbC.mjs.map} +1 -1
- package/dist/wranglerSpawn-CqVNGkA9.mjs +42 -0
- package/dist/{wranglerSpawn-CUlo2qOJ.mjs.map → wranglerSpawn-CqVNGkA9.mjs.map} +1 -1
- package/dist/{zoneResolver-DNNNmO_w.mjs → zoneResolver-D9bz6-0l.mjs} +1 -1
- package/dist/{zoneResolver-DNNNmO_w.mjs.map → zoneResolver-D9bz6-0l.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/apply-C_70Hgcf.mjs.map +0 -1
- package/dist/deploy-D-GXzsWR.mjs.map +0 -1
- package/dist/drift-5_LwAdZo.mjs +0 -8
- package/dist/registry-EWWdkLf7.mjs.map +0 -1
- package/dist/wranglerSpawn-CUlo2qOJ.mjs +0 -24
|
@@ -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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
153
|
-
const derivedName$1 = d1DeriveName(config, env,
|
|
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:
|
|
202
|
+
shardDate: iso$1,
|
|
160
203
|
derivedName: derivedName$1,
|
|
161
|
-
bindingKey:
|
|
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
|
|
172
|
-
const derivedName = d1DeriveName(config, env,
|
|
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:
|
|
220
|
+
shardDate: iso,
|
|
178
221
|
derivedName,
|
|
179
|
-
bindingKey:
|
|
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
|
|
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
|
|
212
|
-
if (!
|
|
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:
|
|
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
|
|
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)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
|
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 =
|
|
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-
|
|
3080
|
+
//# sourceMappingURL=registry-BlHEOKlN.mjs.map
|