@dragonmastery/tamer 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/{apply-CWU3HY0P.mjs → apply-BjrYbyHn.mjs} +14 -16
- package/dist/{apply-CWU3HY0P.mjs.map → apply-BjrYbyHn.mjs.map} +1 -1
- package/dist/{applyTarget-D15T_q7G.mjs → applyTarget-Ce_mtRQX.mjs} +3 -3
- package/dist/{applyTarget-D15T_q7G.mjs.map → applyTarget-Ce_mtRQX.mjs.map} +1 -1
- package/dist/{bootstrap-BicPW44a.mjs → bootstrap-D__dHw1w.mjs} +6 -6
- package/dist/bootstrap-D__dHw1w.mjs.map +1 -0
- package/dist/{buildDispatchUploadForm-BoUB93b3.mjs → buildDispatchUploadForm-CVnPmHg4.mjs} +1 -1
- package/dist/{buildDispatchUploadForm-BoUB93b3.mjs.map → buildDispatchUploadForm-CVnPmHg4.mjs.map} +1 -1
- package/dist/{cloudflareSnapshot-GBUHeg2m.mjs → cloudflareSnapshot-C6cF8GG8.mjs} +5 -7
- package/dist/{cloudflareSnapshot-GBUHeg2m.mjs.map → cloudflareSnapshot-C6cF8GG8.mjs.map} +1 -1
- package/dist/{deploy-DAEjDjOm.mjs → deploy-C6fX9td0.mjs} +23 -11
- package/dist/deploy-C6fX9td0.mjs.map +1 -0
- package/dist/{destroy-tenant-B-VLKfc6.mjs → destroy-tenant-T_94ed9x.mjs} +2 -4
- package/dist/{destroy-tenant-B-VLKfc6.mjs.map → destroy-tenant-T_94ed9x.mjs.map} +1 -1
- package/dist/{destroy-DtgPD_bD.mjs → destroy-vfk2Zbfj.mjs} +11 -13
- package/dist/{destroy-DtgPD_bD.mjs.map → destroy-vfk2Zbfj.mjs.map} +1 -1
- package/dist/{dev-BYItpt9U.mjs → dev-BLthyLml.mjs} +8 -10
- package/dist/{dev-BYItpt9U.mjs.map → dev-BLthyLml.mjs.map} +1 -1
- package/dist/{dns-records.resolve-C2T0m4NG.mjs → dns-records.resolve-8a_eHfVI.mjs} +1 -1
- package/dist/{dns-records.resolve-DwBR_1WI.mjs → dns-records.resolve-BB2agPAb.mjs} +1 -1
- package/dist/{dns-records.resolve-DwBR_1WI.mjs.map → dns-records.resolve-BB2agPAb.mjs.map} +1 -1
- package/dist/{dns-records.sync-CfI1mqXv.mjs → dns-records.sync-DqYROe07.mjs} +3 -3
- package/dist/{dns-records.sync-CfI1mqXv.mjs.map → dns-records.sync-DqYROe07.mjs.map} +1 -1
- package/dist/{doctor-C_hs7k2D.mjs → doctor-32YLAXXl.mjs} +2 -2
- package/dist/{doctor-C_hs7k2D.mjs.map → doctor-32YLAXXl.mjs.map} +1 -1
- package/dist/drift-BCxWdYHG.mjs +8 -0
- package/dist/{drift-DncpkI2R.mjs → drift-CeemyFqL.mjs} +37 -9
- package/dist/drift-CeemyFqL.mjs.map +1 -0
- package/dist/{events-B6oCdvSt.mjs → events-otk0l3aJ.mjs} +2 -4
- package/dist/{events-B6oCdvSt.mjs.map → events-otk0l3aJ.mjs.map} +1 -1
- package/dist/{generator-h_VG0Q5f.mjs → generator-gvCy7ouY.mjs} +2 -2
- package/dist/{generator-h_VG0Q5f.mjs.map → generator-gvCy7ouY.mjs.map} +1 -1
- package/dist/{import-D8zaVvwK.mjs → import-OvohE-H2.mjs} +6 -8
- package/dist/{import-D8zaVvwK.mjs.map → import-OvohE-H2.mjs.map} +1 -1
- package/dist/index.d.mts +264 -26
- package/dist/index.d.mts.map +1 -1
- package/dist/{logpush-job-DsRkOORJ.mjs → logpush-job-DJPlpnRu.mjs} +2 -2
- package/dist/{logpush-job-DsRkOORJ.mjs.map → logpush-job-DJPlpnRu.mjs.map} +1 -1
- package/dist/{migrate-Bwl0w6XN.mjs → migrate-CroDjbJz.mjs} +6 -8
- package/dist/{migrate-Bwl0w6XN.mjs.map → migrate-CroDjbJz.mjs.map} +1 -1
- package/dist/normalize-DVSTRZhO.mjs.map +1 -1
- package/dist/{plan-BNIAD--f.mjs → plan-C2urqJOz.mjs} +39 -14
- package/dist/plan-C2urqJOz.mjs.map +1 -0
- package/dist/{planFormat-CJw8Kq2s.mjs → planFormat-5XMJK879.mjs} +1 -1
- package/dist/{planFormat-CJw8Kq2s.mjs.map → planFormat-5XMJK879.mjs.map} +1 -1
- package/dist/{provision-tenant-BcZocyyn.mjs → provision-tenant-BJ1KugON.mjs} +6 -8
- package/dist/{provision-tenant-BcZocyyn.mjs.map → provision-tenant-BJ1KugON.mjs.map} +1 -1
- package/dist/{r2S3EmptyBucket-DD81ZWQ7.mjs → r2S3EmptyBucket-B9_pHfvB.mjs} +1 -1
- package/dist/{r2S3EmptyBucket-DD81ZWQ7.mjs.map → r2S3EmptyBucket-B9_pHfvB.mjs.map} +1 -1
- package/dist/{fetchStackImports-ClUYZy_U.mjs → registry-EWWdkLf7.mjs} +5 -982
- package/dist/registry-EWWdkLf7.mjs.map +1 -0
- package/dist/secrets-CnzjvndT.mjs +3 -0
- package/dist/{stackOutputs-D33EmyfT.mjs → stackOutputs-Cltzl2g0.mjs} +2 -2
- package/dist/{stackOutputs-D33EmyfT.mjs.map → stackOutputs-Cltzl2g0.mjs.map} +1 -1
- package/dist/{status-BAPpi2Zt.mjs → status-DkkS5lc9.mjs} +7 -9
- package/dist/{status-BAPpi2Zt.mjs.map → status-DkkS5lc9.mjs.map} +1 -1
- package/dist/{sync-BdJ43vO7.mjs → sync-CpfxqlOx.mjs} +7 -9
- package/dist/{sync-BdJ43vO7.mjs.map → sync-CpfxqlOx.mjs.map} +1 -1
- package/dist/tamer.mjs +4422 -221
- package/dist/tamer.mjs.map +1 -1
- package/dist/{tamerArtifactsR2-Ccgplu2Q.mjs → tamerArtifactsR2-DnUJmxnO.mjs} +2 -2
- package/dist/{tamerArtifactsR2-Ccgplu2Q.mjs.map → tamerArtifactsR2-DnUJmxnO.mjs.map} +1 -1
- package/dist/{types-CN1BOr0U.mjs → types-BzzHwIdw.mjs} +6 -8
- package/dist/{types-CN1BOr0U.mjs.map → types-BzzHwIdw.mjs.map} +1 -1
- package/dist/{verifyPlanFile-BQ7GCDC2.mjs → verifyPlanFile-BmEadIqm.mjs} +2 -2
- package/dist/{verifyPlanFile-BQ7GCDC2.mjs.map → verifyPlanFile-BmEadIqm.mjs.map} +1 -1
- package/dist/{wfp-delete-BG9WBd7F.mjs → wfp-delete-CDBFqmrM.mjs} +2 -3
- package/dist/{wfp-delete-BG9WBd7F.mjs.map → wfp-delete-CDBFqmrM.mjs.map} +1 -1
- package/dist/{wfp-put-DjErqxFa.mjs → wfp-put-BrwICc9i.mjs} +3 -4
- package/dist/{wfp-put-DjErqxFa.mjs.map → wfp-put-BrwICc9i.mjs.map} +1 -1
- package/dist/{worker-route-DY1onr-h.mjs → worker-route-x8q3K4-z.mjs} +3 -4
- package/dist/{worker-route-DY1onr-h.mjs.map → worker-route-x8q3K4-z.mjs.map} +1 -1
- package/dist/{workers-DNKsZOq4.mjs → workers-D3Ekf3mF.mjs} +3 -4
- package/dist/{workers-DNKsZOq4.mjs.map → workers-D3Ekf3mF.mjs.map} +1 -1
- package/dist/{wranglerSpawn-DmEz0ldT.mjs → wranglerSpawn-CUlo2qOJ.mjs} +1 -1
- package/dist/{wranglerSpawn-DmEz0ldT.mjs.map → wranglerSpawn-CUlo2qOJ.mjs.map} +1 -1
- package/dist/{zoneResolver-VoxLHM4N.mjs → zoneResolver-DNNNmO_w.mjs} +1 -1
- package/dist/{zoneResolver-VoxLHM4N.mjs.map → zoneResolver-DNNNmO_w.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/CFApiClient-DhbyyV71.mjs +0 -868
- package/dist/CFApiClient-DhbyyV71.mjs.map +0 -1
- package/dist/StateManager-JLBtz9V-.mjs +0 -760
- package/dist/StateManager-JLBtz9V-.mjs.map +0 -1
- package/dist/bootstrap-BicPW44a.mjs.map +0 -1
- package/dist/deploy-DAEjDjOm.mjs.map +0 -1
- package/dist/drift-DRnwTyZD.mjs +0 -10
- package/dist/drift-DncpkI2R.mjs.map +0 -1
- package/dist/fetchStackImports-ClUYZy_U.mjs.map +0 -1
- package/dist/loader-DnT9iqz9.mjs +0 -531
- package/dist/loader-DnT9iqz9.mjs.map +0 -1
- package/dist/plan-BNIAD--f.mjs.map +0 -1
|
@@ -1,760 +0,0 @@
|
|
|
1
|
-
import { a as boolean, c as literal, d as record, f as string, i as array, l as number, n as _enum, s as discriminatedUnion, t as StateConflictError, u as object } from "./tamer.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/core/state/stateSchema.ts
|
|
4
|
-
const D1StateEntrySchema = object({
|
|
5
|
-
type: literal("d1_database"),
|
|
6
|
-
logicalName: string(),
|
|
7
|
-
shardDate: string().optional(),
|
|
8
|
-
derivedName: string(),
|
|
9
|
-
bindingKey: string(),
|
|
10
|
-
cfId: string(),
|
|
11
|
-
migrationsDir: string().optional(),
|
|
12
|
-
preserveOnDestroy: boolean().optional(),
|
|
13
|
-
createdAt: string(),
|
|
14
|
-
updatedAt: string()
|
|
15
|
-
});
|
|
16
|
-
const R2StateEntrySchema = object({
|
|
17
|
-
type: literal("r2_bucket"),
|
|
18
|
-
logicalName: string(),
|
|
19
|
-
createdDate: string(),
|
|
20
|
-
derivedName: string(),
|
|
21
|
-
bindingKey: string(),
|
|
22
|
-
createdAt: string(),
|
|
23
|
-
updatedAt: string()
|
|
24
|
-
});
|
|
25
|
-
const KVStateEntrySchema = object({
|
|
26
|
-
type: literal("kv_namespace"),
|
|
27
|
-
logicalName: string(),
|
|
28
|
-
derivedName: string(),
|
|
29
|
-
bindingKey: string(),
|
|
30
|
-
cfId: string(),
|
|
31
|
-
createdAt: string(),
|
|
32
|
-
updatedAt: string()
|
|
33
|
-
});
|
|
34
|
-
const QueueStateEntrySchema = object({
|
|
35
|
-
type: literal("queue"),
|
|
36
|
-
logicalName: string(),
|
|
37
|
-
derivedName: string(),
|
|
38
|
-
bindingKey: string(),
|
|
39
|
-
cfId: string(),
|
|
40
|
-
producerBinding: boolean(),
|
|
41
|
-
createdAt: string(),
|
|
42
|
-
updatedAt: string()
|
|
43
|
-
});
|
|
44
|
-
const VectorizeStateEntrySchema = object({
|
|
45
|
-
type: literal("vectorize"),
|
|
46
|
-
logicalName: string(),
|
|
47
|
-
derivedName: string(),
|
|
48
|
-
bindingKey: string(),
|
|
49
|
-
cfId: string(),
|
|
50
|
-
dimensions: number(),
|
|
51
|
-
metric: _enum([
|
|
52
|
-
"cosine",
|
|
53
|
-
"euclidean",
|
|
54
|
-
"dot-product"
|
|
55
|
-
]),
|
|
56
|
-
createdAt: string(),
|
|
57
|
-
updatedAt: string()
|
|
58
|
-
});
|
|
59
|
-
const AIGatewayStateEntrySchema = object({
|
|
60
|
-
type: literal("ai_gateway"),
|
|
61
|
-
logicalName: string(),
|
|
62
|
-
derivedName: string(),
|
|
63
|
-
bindingKey: string(),
|
|
64
|
-
cfId: string(),
|
|
65
|
-
cacheTtl: number(),
|
|
66
|
-
cacheInvalidateOnUpdate: boolean(),
|
|
67
|
-
collectLogs: boolean(),
|
|
68
|
-
authentication: boolean(),
|
|
69
|
-
rateLimitingInterval: number(),
|
|
70
|
-
rateLimitingLimit: number(),
|
|
71
|
-
rateLimitingTechnique: _enum(["fixed", "sliding"]),
|
|
72
|
-
createdAt: string(),
|
|
73
|
-
updatedAt: string()
|
|
74
|
-
});
|
|
75
|
-
const PipelineStateEntrySchema = object({
|
|
76
|
-
type: literal("pipeline"),
|
|
77
|
-
logicalName: string(),
|
|
78
|
-
derivedName: string(),
|
|
79
|
-
bindingKey: string(),
|
|
80
|
-
cfId: string(),
|
|
81
|
-
sql: string(),
|
|
82
|
-
status: string().optional(),
|
|
83
|
-
createdAt: string(),
|
|
84
|
-
updatedAt: string()
|
|
85
|
-
});
|
|
86
|
-
const WorkflowStateEntrySchema = object({
|
|
87
|
-
type: literal("workflow"),
|
|
88
|
-
logicalName: string(),
|
|
89
|
-
derivedName: string(),
|
|
90
|
-
bindingKey: string(),
|
|
91
|
-
cfId: string(),
|
|
92
|
-
className: string(),
|
|
93
|
-
scriptName: string(),
|
|
94
|
-
limits: object({ steps: number().int().positive().optional() }).optional(),
|
|
95
|
-
createdAt: string(),
|
|
96
|
-
updatedAt: string()
|
|
97
|
-
});
|
|
98
|
-
const SecretsStoreStateEntrySchema = object({
|
|
99
|
-
type: literal("secrets_store"),
|
|
100
|
-
logicalName: string(),
|
|
101
|
-
derivedName: string(),
|
|
102
|
-
bindingKey: string(),
|
|
103
|
-
cfId: string(),
|
|
104
|
-
createdAt: string(),
|
|
105
|
-
updatedAt: string()
|
|
106
|
-
});
|
|
107
|
-
const HyperdriveStateEntrySchema = object({
|
|
108
|
-
type: literal("hyperdrive"),
|
|
109
|
-
logicalName: string(),
|
|
110
|
-
derivedName: string(),
|
|
111
|
-
bindingKey: string(),
|
|
112
|
-
cfId: string(),
|
|
113
|
-
scheme: _enum([
|
|
114
|
-
"postgres",
|
|
115
|
-
"postgresql",
|
|
116
|
-
"mysql"
|
|
117
|
-
]),
|
|
118
|
-
originHost: string(),
|
|
119
|
-
originDatabase: string(),
|
|
120
|
-
createdAt: string(),
|
|
121
|
-
updatedAt: string()
|
|
122
|
-
});
|
|
123
|
-
const DnsRecordTypeSchema = _enum([
|
|
124
|
-
"A",
|
|
125
|
-
"AAAA",
|
|
126
|
-
"CNAME",
|
|
127
|
-
"TXT",
|
|
128
|
-
"MX",
|
|
129
|
-
"NS",
|
|
130
|
-
"CAA",
|
|
131
|
-
"SRV",
|
|
132
|
-
"PTR",
|
|
133
|
-
"HTTPS",
|
|
134
|
-
"SVCB"
|
|
135
|
-
]);
|
|
136
|
-
const DnsRecordStateEntrySchema = object({
|
|
137
|
-
type: literal("dns_record"),
|
|
138
|
-
logicalName: string(),
|
|
139
|
-
zoneId: string(),
|
|
140
|
-
recordType: DnsRecordTypeSchema,
|
|
141
|
-
name: string(),
|
|
142
|
-
content: string(),
|
|
143
|
-
ttl: number(),
|
|
144
|
-
proxied: boolean(),
|
|
145
|
-
priority: number().optional(),
|
|
146
|
-
comment: string(),
|
|
147
|
-
recordId: string(),
|
|
148
|
-
createdAt: string(),
|
|
149
|
-
updatedAt: string()
|
|
150
|
-
});
|
|
151
|
-
const DispatchNamespaceStateEntrySchema = object({
|
|
152
|
-
type: literal("dispatch_namespace"),
|
|
153
|
-
logicalName: string(),
|
|
154
|
-
derivedName: string(),
|
|
155
|
-
createdAt: string(),
|
|
156
|
-
updatedAt: string()
|
|
157
|
-
});
|
|
158
|
-
const LogpushJobStateEntrySchema = object({
|
|
159
|
-
type: literal("logpush_job"),
|
|
160
|
-
logicalName: string(),
|
|
161
|
-
derivedName: string(),
|
|
162
|
-
cfJobId: number(),
|
|
163
|
-
dataset: string(),
|
|
164
|
-
createdAt: string(),
|
|
165
|
-
updatedAt: string()
|
|
166
|
-
});
|
|
167
|
-
const LogpushPipelinesStateEntrySchema = object({
|
|
168
|
-
type: literal("logpush_pipelines"),
|
|
169
|
-
logicalName: string(),
|
|
170
|
-
streamId: string(),
|
|
171
|
-
streamIngestBaseUrl: string().optional(),
|
|
172
|
-
sinkId: string(),
|
|
173
|
-
pipelineId: string(),
|
|
174
|
-
streamName: string(),
|
|
175
|
-
sinkName: string(),
|
|
176
|
-
pipelineName: string(),
|
|
177
|
-
r2DataCatalogTableName: string().optional(),
|
|
178
|
-
r2DataCatalogTableNamePipelines: string().optional(),
|
|
179
|
-
r2DataCatalogNamespace: string().optional(),
|
|
180
|
-
catalogBucketDerivedName: string(),
|
|
181
|
-
mintedR2CatalogTokenId: string().optional(),
|
|
182
|
-
mintedR2CatalogTokenValue: string().optional(),
|
|
183
|
-
mintedPipelinesSendTokenId: string().optional(),
|
|
184
|
-
mintedPipelinesSendTokenValue: string().optional(),
|
|
185
|
-
createdAt: string(),
|
|
186
|
-
updatedAt: string()
|
|
187
|
-
});
|
|
188
|
-
const WorkerRouteStateEntrySchema = object({
|
|
189
|
-
type: literal("worker_route"),
|
|
190
|
-
workerKey: string(),
|
|
191
|
-
workerName: string(),
|
|
192
|
-
zoneId: string(),
|
|
193
|
-
zoneName: string(),
|
|
194
|
-
routeId: string(),
|
|
195
|
-
pattern: string(),
|
|
196
|
-
createdAt: string(),
|
|
197
|
-
updatedAt: string()
|
|
198
|
-
});
|
|
199
|
-
const StateEntrySchema = discriminatedUnion("type", [
|
|
200
|
-
D1StateEntrySchema,
|
|
201
|
-
R2StateEntrySchema,
|
|
202
|
-
KVStateEntrySchema,
|
|
203
|
-
QueueStateEntrySchema,
|
|
204
|
-
HyperdriveStateEntrySchema,
|
|
205
|
-
VectorizeStateEntrySchema,
|
|
206
|
-
AIGatewayStateEntrySchema,
|
|
207
|
-
PipelineStateEntrySchema,
|
|
208
|
-
WorkflowStateEntrySchema,
|
|
209
|
-
SecretsStoreStateEntrySchema,
|
|
210
|
-
DnsRecordStateEntrySchema,
|
|
211
|
-
DispatchNamespaceStateEntrySchema,
|
|
212
|
-
LogpushJobStateEntrySchema,
|
|
213
|
-
LogpushPipelinesStateEntrySchema,
|
|
214
|
-
WorkerRouteStateEntrySchema
|
|
215
|
-
]);
|
|
216
|
-
const ProvisioningStatusSchema = _enum([
|
|
217
|
-
"pending",
|
|
218
|
-
"d1_created",
|
|
219
|
-
"migrations_applied",
|
|
220
|
-
"script_uploaded",
|
|
221
|
-
"ready",
|
|
222
|
-
"tombstoned"
|
|
223
|
-
]);
|
|
224
|
-
const TenantD1ShardRefSchema = object({
|
|
225
|
-
role: string(),
|
|
226
|
-
derivedName: string(),
|
|
227
|
-
cfId: string()
|
|
228
|
-
});
|
|
229
|
-
const TenantStateEntrySchema = object({
|
|
230
|
-
product: string(),
|
|
231
|
-
workspace: string(),
|
|
232
|
-
provisioningStatus: ProvisioningStatusSchema,
|
|
233
|
-
dispatchNamespaceName: string(),
|
|
234
|
-
scriptName: string(),
|
|
235
|
-
d1Shards: array(TenantD1ShardRefSchema).optional(),
|
|
236
|
-
createdAt: string(),
|
|
237
|
-
updatedAt: string()
|
|
238
|
-
});
|
|
239
|
-
const CfiStackMetaSchema = object({
|
|
240
|
-
name: string().optional(),
|
|
241
|
-
owner: string().optional()
|
|
242
|
-
});
|
|
243
|
-
const CfiOperationNameSchema = _enum([
|
|
244
|
-
"bootstrap",
|
|
245
|
-
"apply",
|
|
246
|
-
"deploy",
|
|
247
|
-
"destroy",
|
|
248
|
-
"provision-tenant",
|
|
249
|
-
"destroy-tenant",
|
|
250
|
-
"import",
|
|
251
|
-
"sync"
|
|
252
|
-
]);
|
|
253
|
-
const CfiOperationStatusSchema = _enum([
|
|
254
|
-
"in_progress",
|
|
255
|
-
"succeeded",
|
|
256
|
-
"failed"
|
|
257
|
-
]);
|
|
258
|
-
const CfiStackOutputValueSchema = object({
|
|
259
|
-
value: string(),
|
|
260
|
-
source: string(),
|
|
261
|
-
resolvedAt: string()
|
|
262
|
-
});
|
|
263
|
-
const CfiOperationRecordSchema = object({
|
|
264
|
-
command: CfiOperationNameSchema,
|
|
265
|
-
status: CfiOperationStatusSchema,
|
|
266
|
-
startedAt: string(),
|
|
267
|
-
completedAt: string().optional(),
|
|
268
|
-
errorMessage: string().optional(),
|
|
269
|
-
detail: string().optional()
|
|
270
|
-
});
|
|
271
|
-
const CfiStateSchema = object({
|
|
272
|
-
tenantId: string(),
|
|
273
|
-
env: string(),
|
|
274
|
-
schemaVersion: number(),
|
|
275
|
-
syncedAt: string(),
|
|
276
|
-
resources: record(string(), StateEntrySchema),
|
|
277
|
-
revision: number().optional(),
|
|
278
|
-
tenants: record(string(), TenantStateEntrySchema).optional(),
|
|
279
|
-
stack: CfiStackMetaSchema.optional(),
|
|
280
|
-
stackOutputs: record(string(), CfiStackOutputValueSchema).optional(),
|
|
281
|
-
lastOperation: CfiOperationRecordSchema.optional(),
|
|
282
|
-
operationHistory: array(CfiOperationRecordSchema).optional()
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
//#endregion
|
|
286
|
-
//#region src/core/state/stackName.ts
|
|
287
|
-
const DEFAULT_STACK_NAME = "default";
|
|
288
|
-
function stackNameForConfig(config) {
|
|
289
|
-
return config.stack?.name ?? config.tenant.slug ?? DEFAULT_STACK_NAME;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
//#endregion
|
|
293
|
-
//#region src/core/state/tamerStateDb.ts
|
|
294
|
-
/**
|
|
295
|
-
* Schema versions:
|
|
296
|
-
* 2: original (resources only).
|
|
297
|
-
* 3: + `tenants` map, + `revision` for optimistic concurrency.
|
|
298
|
-
* 4: + `stack` metadata, + `lastOperation` (CloudFormation-style stack info).
|
|
299
|
-
* All v4 additions are optional; existing v3 documents are upgraded
|
|
300
|
-
* in-place during parse with no data loss.
|
|
301
|
-
*/
|
|
302
|
-
const STATE_SCHEMA_VERSION = 4;
|
|
303
|
-
/** Cloudflare D1 database that holds JSON state for an env (`tamer-state-dev`, …). */
|
|
304
|
-
function tamerStateDatabaseName(env) {
|
|
305
|
-
return `tamer-state-${env}`;
|
|
306
|
-
}
|
|
307
|
-
function createEmptyCfiState(tenantId, env) {
|
|
308
|
-
return {
|
|
309
|
-
tenantId,
|
|
310
|
-
env,
|
|
311
|
-
schemaVersion: STATE_SCHEMA_VERSION,
|
|
312
|
-
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
313
|
-
resources: {},
|
|
314
|
-
revision: 0,
|
|
315
|
-
tenants: {}
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
/** In-place upgrade for parsed JSON before Zod validation. */
|
|
319
|
-
function migrateRawCfiStateInPlace(raw) {
|
|
320
|
-
const v = raw.schemaVersion;
|
|
321
|
-
if (typeof v !== "number") throw new Error("tamer state: schemaVersion must be a number");
|
|
322
|
-
if (v < 2) throw new Error(`tamer state: unsupported schemaVersion ${v}`);
|
|
323
|
-
if (v > STATE_SCHEMA_VERSION) throw new Error(`tamer state: unknown schemaVersion ${v} (engine supports up to ${STATE_SCHEMA_VERSION})`);
|
|
324
|
-
if (v === 2) {
|
|
325
|
-
raw.tenants = {};
|
|
326
|
-
raw.revision = 0;
|
|
327
|
-
raw.schemaVersion = 3;
|
|
328
|
-
}
|
|
329
|
-
if (!raw.tenants || typeof raw.tenants !== "object") raw.tenants = {};
|
|
330
|
-
if (typeof raw.revision !== "number") raw.revision = 0;
|
|
331
|
-
if (raw.schemaVersion === 3) raw.schemaVersion = STATE_SCHEMA_VERSION;
|
|
332
|
-
}
|
|
333
|
-
async function findTamerStateDatabaseUuid(api, env) {
|
|
334
|
-
const name = tamerStateDatabaseName(env);
|
|
335
|
-
return (await api.d1ListAll()).find((d) => d.name === name)?.uuid;
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Create `tamer-state-{env}` if missing, ensure `tamer_kv` table, and seed an
|
|
339
|
-
* initial empty `cfi_state:{stackName}` row when this stack has no row yet.
|
|
340
|
-
* Idempotent — re-running for the same stack is a no-op; re-running for a
|
|
341
|
-
* different stack against the same env D1 just adds another row.
|
|
342
|
-
*/
|
|
343
|
-
async function ensureTamerStateDatabase(api, tenantId, env, stackName = DEFAULT_STACK_NAME) {
|
|
344
|
-
let uuid = await findTamerStateDatabaseUuid(api, env);
|
|
345
|
-
if (!uuid) uuid = (await api.d1Create(tamerStateDatabaseName(env))).uuid;
|
|
346
|
-
await api.d1Query(uuid, `CREATE TABLE IF NOT EXISTS tamer_kv (
|
|
347
|
-
k TEXT PRIMARY KEY,
|
|
348
|
-
v TEXT NOT NULL
|
|
349
|
-
)`);
|
|
350
|
-
const rowKey = `cfi_state:${stackName}`;
|
|
351
|
-
const { rows } = await api.d1Query(uuid, `SELECT v FROM tamer_kv WHERE k = ?`, [rowKey]);
|
|
352
|
-
if (rows.length === 0) {
|
|
353
|
-
const initial = createEmptyCfiState(tenantId, env);
|
|
354
|
-
await api.d1Query(uuid, `INSERT INTO tamer_kv (k, v) VALUES (?, ?)`, [rowKey, JSON.stringify(initial)]);
|
|
355
|
-
}
|
|
356
|
-
return uuid;
|
|
357
|
-
}
|
|
358
|
-
function parseCfiStateJson(json) {
|
|
359
|
-
const raw = JSON.parse(json);
|
|
360
|
-
migrateRawCfiStateInPlace(raw);
|
|
361
|
-
const result = CfiStateSchema.safeParse(raw);
|
|
362
|
-
if (!result.success) throw new Error(`Invalid tamer state JSON: ${result.error.message}`);
|
|
363
|
-
return result.data;
|
|
364
|
-
}
|
|
365
|
-
async function destroyTamerStateDatabase(api, env) {
|
|
366
|
-
const uuid = await findTamerStateDatabaseUuid(api, env);
|
|
367
|
-
if (!uuid) return false;
|
|
368
|
-
await api.d1Delete(uuid);
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
//#endregion
|
|
373
|
-
//#region src/features/dispatch-namespace/dispatch-namespace.resolve.ts
|
|
374
|
-
const ephemeralPredicateCache = /* @__PURE__ */ new WeakMap();
|
|
375
|
-
function ephemeralPredicateFor(tenant) {
|
|
376
|
-
if (ephemeralPredicateCache.has(tenant)) return ephemeralPredicateCache.get(tenant) ?? null;
|
|
377
|
-
const pat = tenant.ephemeralEnvPattern;
|
|
378
|
-
const compiled = pat ? new RegExp(pat) : null;
|
|
379
|
-
ephemeralPredicateCache.set(tenant, compiled);
|
|
380
|
-
return compiled;
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* `true` when `env` matches `tenant.ephemeralEnvPattern` (e.g.
|
|
384
|
-
* `"^pr-"` for PR previews, `"^(pr|feature)-"` for branch previews).
|
|
385
|
-
*
|
|
386
|
-
* Ephemeral envs share **one** dispatch namespace
|
|
387
|
-
* (`{ns}-ephemeral`) so we don't churn dispatch-namespace creates per
|
|
388
|
-
* preview, and dispatch-script names carry the env suffix
|
|
389
|
-
* (`{product}-{workspace}-{env}`) so multiple previews can coexist
|
|
390
|
-
* inside that shared namespace. When the config doesn't pin a
|
|
391
|
-
* pattern, no env is ephemeral — every env gets its own dispatch
|
|
392
|
-
* namespace `{ns}-{env}`.
|
|
393
|
-
*/
|
|
394
|
-
function isEphemeralEnv(env, tenant) {
|
|
395
|
-
const re = ephemeralPredicateFor(tenant);
|
|
396
|
-
if (!re) return false;
|
|
397
|
-
return re.test(env);
|
|
398
|
-
}
|
|
399
|
-
/** Resolved Cloudflare dispatch namespace name for the given env. */
|
|
400
|
-
function effectiveDispatchNamespaceName(config, env, tenant) {
|
|
401
|
-
if (config.envSuffix) {
|
|
402
|
-
if (env === "local") return config.namespace;
|
|
403
|
-
if (isEphemeralEnv(env, tenant)) return `${config.namespace}-ephemeral`;
|
|
404
|
-
return `${config.namespace}-${env}`;
|
|
405
|
-
}
|
|
406
|
-
return config.namespace;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
//#endregion
|
|
410
|
-
//#region src/core/tenant/tenantKeys.ts
|
|
411
|
-
/** Stable map key for `CfiState.tenants` (workspace-scoped product tenant). */
|
|
412
|
-
function tenantStateKey(product, workspace) {
|
|
413
|
-
return `${product}:${workspace}`;
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Dispatch-namespace script name per `docs/handoff.md` §6: non-ephemeral
|
|
417
|
-
* envs collapse to `{product}-{workspace}` (one script per workspace);
|
|
418
|
-
* ephemeral envs (matching `tenant.ephemeralEnvPattern`) carry the env
|
|
419
|
-
* in the script name (`{product}-{workspace}-{env}`) so multiple
|
|
420
|
-
* previews can coexist in the shared `{ns}-ephemeral` namespace.
|
|
421
|
-
*/
|
|
422
|
-
function tenantDispatchScriptName(product, workspace, env, tenant) {
|
|
423
|
-
if (isEphemeralEnv(env, tenant)) return `${product}-${workspace}-${env}`;
|
|
424
|
-
return `${product}-${workspace}`;
|
|
425
|
-
}
|
|
426
|
-
const SAFE = /[^a-z0-9_-]/gi;
|
|
427
|
-
/**
|
|
428
|
-
* Per-shard D1 database name for a tenant. Stable across `provision-tenant`
|
|
429
|
-
* runs and across env so re-provisioning + drift detection can match by
|
|
430
|
-
* name. Format: `db_{role}_{w}_{p}_t_{tid}_{env}`.
|
|
431
|
-
*
|
|
432
|
-
* db_system_acme_todo_t_platform_prod
|
|
433
|
-
* db_app_acme_todo_t_platform_prod
|
|
434
|
-
*
|
|
435
|
-
* `role` is whatever the operator declared in `tenant.d1Shards` in
|
|
436
|
-
* `tamer.config.ts`. Tamer is opinion-free about the shard layout — a
|
|
437
|
-
* Dragoncore-style product picks `["system", "app", "history"]`, a
|
|
438
|
-
* single-DB tenant picks `["main"]`, an audit-only tenant picks
|
|
439
|
-
* `["audit"]`, etc. The role itself is validated by the loader (lowercase
|
|
440
|
-
* ASCII subset) so it slots cleanly into the D1 naming scheme.
|
|
441
|
-
*
|
|
442
|
-
* D1 names are length-bounded (Cloudflare currently allows up to 64
|
|
443
|
-
* chars), and this scheme keeps every shard well under that limit even
|
|
444
|
-
* for long workspace + product slugs.
|
|
445
|
-
*/
|
|
446
|
-
function tenantShardDatabaseName(role, workspace, product, platformTenantId, env) {
|
|
447
|
-
return `db_${role}_${workspace.replace(SAFE, "_").toLowerCase()}_${product.replace(SAFE, "_").toLowerCase()}_t_${platformTenantId}_${env}`;
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Parse + validate a `--shards a,b,c` CLI argument against the configured
|
|
451
|
-
* shard set in `tamer.config.ts`. The CLI flag may only **trim** the
|
|
452
|
-
* configured layout (e.g. `--shards system` on a stack whose config
|
|
453
|
-
* declares `["system","app","history"]` provisions just the system
|
|
454
|
-
* shard for an ephemeral preview); it cannot extend it, because the
|
|
455
|
-
* config is the source of truth that `apply` / `drift` / `destroy`
|
|
456
|
-
* other operators read.
|
|
457
|
-
*
|
|
458
|
-
* Returns the requested roles in canonical order (matches `allowed`
|
|
459
|
-
* order, regardless of input order) so plan/apply output is
|
|
460
|
-
* deterministic and partial-failure resumes pick up at the next
|
|
461
|
-
* canonical role.
|
|
462
|
-
*/
|
|
463
|
-
function parseTenantShardRoles(raw, allowed) {
|
|
464
|
-
const allowedSet = new Set(allowed);
|
|
465
|
-
const requested = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
466
|
-
const unknown = requested.filter((r) => !allowedSet.has(r));
|
|
467
|
-
if (unknown.length > 0) {
|
|
468
|
-
const list = allowed.length > 0 ? allowed.join(", ") : "(none configured)";
|
|
469
|
-
throw new Error(`unknown tenant shard role(s) "${unknown.join(", ")}"; must be a subset of tenant.d1Shards in the Tamer project config: ${list}`);
|
|
470
|
-
}
|
|
471
|
-
const seen = new Set(requested);
|
|
472
|
-
return allowed.filter((r) => seen.has(r));
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
//#endregion
|
|
476
|
-
//#region src/core/state/StateManager.ts
|
|
477
|
-
/** D1 `tamer_kv.k` value for a given stack's state row. */
|
|
478
|
-
function stateRowKey(stackName) {
|
|
479
|
-
return `cfi_state:${stackName}`;
|
|
480
|
-
}
|
|
481
|
-
const OPERATION_HISTORY_CAP = 50;
|
|
482
|
-
/**
|
|
483
|
-
* Authoritative deployment state for an env.
|
|
484
|
-
*
|
|
485
|
-
* - **Non-local:** stored as JSON in Cloudflare D1 (`tamer-state-{env}`).
|
|
486
|
-
* Call {@link hydrate} before {@link load}, then {@link persist} after mutations.
|
|
487
|
-
* - **local:** in-memory only (no persistence).
|
|
488
|
-
*/
|
|
489
|
-
var StateManager = class {
|
|
490
|
-
state = null;
|
|
491
|
-
dirty = false;
|
|
492
|
-
/** Set when {@link hydrate} loads remote state. */
|
|
493
|
-
tamerStateDbUuid = null;
|
|
494
|
-
/**
|
|
495
|
-
* Remote `revision` at last hydrate (or last successful persist). Used for
|
|
496
|
-
* optimistic concurrency on D1 persist.
|
|
497
|
-
*/
|
|
498
|
-
baselineRevision = 0;
|
|
499
|
-
/**
|
|
500
|
-
* @param tenantId `config.tenant.id` — recorded on the state row for
|
|
501
|
-
* diagnostics; not part of the row key.
|
|
502
|
-
* @param env Cloudflare environment name; selects the
|
|
503
|
-
* `tamer-state-{env}` D1 database.
|
|
504
|
-
* @param stackName Stack identity (`config.stack.name ?? tenant.slug`).
|
|
505
|
-
* The state row in D1 is keyed `cfi_state:{stackName}`,
|
|
506
|
-
* so multiple stacks coexist in one env D1 without
|
|
507
|
-
* clobbering each other. Defaults to `"default"` —
|
|
508
|
-
* unit tests that synthesize a StateManager without
|
|
509
|
-
* a config get a stable key without extra plumbing.
|
|
510
|
-
*/
|
|
511
|
-
constructor(tenantId, env, stackName = DEFAULT_STACK_NAME) {
|
|
512
|
-
this.tenantId = tenantId;
|
|
513
|
-
this.env = env;
|
|
514
|
-
this.stackName = stackName;
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Load state from D1 (remote) or allocate empty state (local).
|
|
518
|
-
* Required before {@link load} for every command.
|
|
519
|
-
*/
|
|
520
|
-
async hydrate(api) {
|
|
521
|
-
if (this.state) return;
|
|
522
|
-
if (this.env === "local") {
|
|
523
|
-
this.state = createEmptyCfiState(this.tenantId, this.env);
|
|
524
|
-
this.baselineRevision = this.state.revision ?? 0;
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
const name = tamerStateDatabaseName(this.env);
|
|
528
|
-
const uuid = await findTamerStateDatabaseUuid(api, this.env);
|
|
529
|
-
if (!uuid) throw new Error(`Tamer state database "${name}" not found. Run: tamer bootstrap --env ${this.env}`);
|
|
530
|
-
this.tamerStateDbUuid = uuid;
|
|
531
|
-
const rowKey = stateRowKey(this.stackName);
|
|
532
|
-
const { rows } = await api.d1Query(uuid, `SELECT v FROM tamer_kv WHERE k = ?`, [rowKey]);
|
|
533
|
-
if (rows.length === 0) {
|
|
534
|
-
this.state = createEmptyCfiState(this.tenantId, this.env);
|
|
535
|
-
this.baselineRevision = this.state.revision ?? 0;
|
|
536
|
-
this.dirty = true;
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
const v = rows[0]["v"];
|
|
540
|
-
if (typeof v !== "string") throw new Error(`tamer_kv.${rowKey} must be a string column`);
|
|
541
|
-
this.state = parseCfiStateJson(v);
|
|
542
|
-
this.baselineRevision = this.state.revision ?? 0;
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Stack identifier this manager is bound to (the `cfi_state:{name}` row
|
|
546
|
-
* key suffix). Exposed so `fetchStackImports` and diagnostics can show
|
|
547
|
-
* the operator which row this manager owns.
|
|
548
|
-
*/
|
|
549
|
-
getStackName() {
|
|
550
|
-
return this.stackName;
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Allocate empty in-memory state without touching D1. Use for read-only
|
|
554
|
-
* "what-would-state-look-like" snapshots (e.g. drift-aware plan refresh)
|
|
555
|
-
* where we want to drive the module `sync` hooks against a fresh slate
|
|
556
|
-
* and then discard the result. {@link persist} is unsafe afterwards
|
|
557
|
-
* because there is no D1 baseline to compare against.
|
|
558
|
-
*/
|
|
559
|
-
hydrateInMemory() {
|
|
560
|
-
if (this.state) return;
|
|
561
|
-
this.state = createEmptyCfiState(this.tenantId, this.env);
|
|
562
|
-
this.baselineRevision = this.state.revision ?? 0;
|
|
563
|
-
}
|
|
564
|
-
/** Clear cached state so the next {@link hydrate} reloads from D1. */
|
|
565
|
-
reset() {
|
|
566
|
-
this.state = null;
|
|
567
|
-
this.tamerStateDbUuid = null;
|
|
568
|
-
this.dirty = false;
|
|
569
|
-
this.baselineRevision = 0;
|
|
570
|
-
}
|
|
571
|
-
load() {
|
|
572
|
-
if (!this.state) throw new Error("StateManager: call await hydrate(api) before load()");
|
|
573
|
-
return this.state;
|
|
574
|
-
}
|
|
575
|
-
get(derivedName) {
|
|
576
|
-
return this.load().resources[derivedName];
|
|
577
|
-
}
|
|
578
|
-
set(derivedName, entry) {
|
|
579
|
-
const s = this.load();
|
|
580
|
-
s.resources[derivedName] = entry;
|
|
581
|
-
s.syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
582
|
-
this.dirty = true;
|
|
583
|
-
}
|
|
584
|
-
delete(derivedName) {
|
|
585
|
-
const s = this.load();
|
|
586
|
-
delete s.resources[derivedName];
|
|
587
|
-
s.syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
588
|
-
this.dirty = true;
|
|
589
|
-
}
|
|
590
|
-
getAll() {
|
|
591
|
-
return this.load().resources;
|
|
592
|
-
}
|
|
593
|
-
getTenant(product, workspace) {
|
|
594
|
-
return this.load().tenants?.[tenantStateKey(product, workspace)];
|
|
595
|
-
}
|
|
596
|
-
setTenant(entry) {
|
|
597
|
-
const s = this.load();
|
|
598
|
-
if (!s.tenants) s.tenants = {};
|
|
599
|
-
s.tenants[tenantStateKey(entry.product, entry.workspace)] = entry;
|
|
600
|
-
s.syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
601
|
-
this.dirty = true;
|
|
602
|
-
}
|
|
603
|
-
deleteTenant(product, workspace) {
|
|
604
|
-
const s = this.load();
|
|
605
|
-
if (!s.tenants) return;
|
|
606
|
-
delete s.tenants[tenantStateKey(product, workspace)];
|
|
607
|
-
s.syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
608
|
-
this.dirty = true;
|
|
609
|
-
}
|
|
610
|
-
listTenants() {
|
|
611
|
-
return Object.values(this.load().tenants ?? {});
|
|
612
|
-
}
|
|
613
|
-
/** CloudFormation-style stack metadata (name, owner). Returns a copy. */
|
|
614
|
-
getStackMeta() {
|
|
615
|
-
const s = this.load().stack;
|
|
616
|
-
return s ? { ...s } : void 0;
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Set or merge stack metadata. Pass `undefined` fields to clear them; only
|
|
620
|
-
* provided keys are written, so callers can update one field at a time.
|
|
621
|
-
*/
|
|
622
|
-
setStackMeta(meta) {
|
|
623
|
-
const s = this.load();
|
|
624
|
-
s.stack = {
|
|
625
|
-
...s.stack ?? {},
|
|
626
|
-
...meta
|
|
627
|
-
};
|
|
628
|
-
s.syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
629
|
-
this.dirty = true;
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Resolved + persisted `outputs:` for this stack. Returns `{}` when none
|
|
633
|
-
* have been recorded yet (e.g. before the first successful `apply`). The
|
|
634
|
-
* returned object is a shallow copy — mutate via {@link replaceStackOutputs}.
|
|
635
|
-
*/
|
|
636
|
-
getStackOutputs() {
|
|
637
|
-
return { ...this.load().stackOutputs ?? {} };
|
|
638
|
-
}
|
|
639
|
-
/**
|
|
640
|
-
* Replace this stack's `stackOutputs` map wholesale. Pass `{}` to clear
|
|
641
|
-
* (e.g. when `outputs` is removed from `tamer.config.ts`); pass a fresh
|
|
642
|
-
* map keyed by output name to commit a successful apply's resolved values.
|
|
643
|
-
* No-op when the new map is structurally identical to the existing one
|
|
644
|
-
* (avoids gratuitous `revision` bumps on no-op applies).
|
|
645
|
-
*/
|
|
646
|
-
replaceStackOutputs(next) {
|
|
647
|
-
const s = this.load();
|
|
648
|
-
if (stackOutputsEqual(s.stackOutputs ?? {}, next)) return;
|
|
649
|
-
if (Object.keys(next).length === 0) delete s.stackOutputs;
|
|
650
|
-
else s.stackOutputs = { ...next };
|
|
651
|
-
s.syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
652
|
-
this.dirty = true;
|
|
653
|
-
}
|
|
654
|
-
getLastOperation() {
|
|
655
|
-
return this.load().lastOperation;
|
|
656
|
-
}
|
|
657
|
-
/** Completed operations (`succeeded` / `failed` only), newest first. */
|
|
658
|
-
getOperationHistory() {
|
|
659
|
-
const h = this.load().operationHistory;
|
|
660
|
-
return h ? h.map((e) => ({ ...e })) : [];
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Begin recording a CloudFormation-style operation marker. Sets `status:
|
|
664
|
-
* "in_progress"` and `startedAt`; pair with {@link finishOperation} on
|
|
665
|
-
* success or {@link failOperation} on error. Persist between calls if the
|
|
666
|
-
* operation may take a long time and you want concurrent operators to see
|
|
667
|
-
* the in-progress marker.
|
|
668
|
-
*/
|
|
669
|
-
beginOperation(command, detail) {
|
|
670
|
-
const s = this.load();
|
|
671
|
-
s.lastOperation = {
|
|
672
|
-
command,
|
|
673
|
-
status: "in_progress",
|
|
674
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
675
|
-
detail
|
|
676
|
-
};
|
|
677
|
-
s.syncedAt = s.lastOperation.startedAt;
|
|
678
|
-
this.dirty = true;
|
|
679
|
-
}
|
|
680
|
-
finishOperation(detail) {
|
|
681
|
-
const s = this.load();
|
|
682
|
-
if (!s.lastOperation) return;
|
|
683
|
-
s.lastOperation.status = "succeeded";
|
|
684
|
-
s.lastOperation.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
685
|
-
if (detail !== void 0) s.lastOperation.detail = detail;
|
|
686
|
-
s.syncedAt = s.lastOperation.completedAt;
|
|
687
|
-
this.appendTerminalOperationToHistory(s.lastOperation);
|
|
688
|
-
this.dirty = true;
|
|
689
|
-
}
|
|
690
|
-
failOperation(errorMessage) {
|
|
691
|
-
const s = this.load();
|
|
692
|
-
if (!s.lastOperation) return;
|
|
693
|
-
s.lastOperation.status = "failed";
|
|
694
|
-
s.lastOperation.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
695
|
-
s.lastOperation.errorMessage = errorMessage;
|
|
696
|
-
s.syncedAt = s.lastOperation.completedAt;
|
|
697
|
-
this.appendTerminalOperationToHistory(s.lastOperation);
|
|
698
|
-
this.dirty = true;
|
|
699
|
-
}
|
|
700
|
-
appendTerminalOperationToHistory(op) {
|
|
701
|
-
if (op.status !== "succeeded" && op.status !== "failed") return;
|
|
702
|
-
const s = this.load();
|
|
703
|
-
s.operationHistory = [{
|
|
704
|
-
command: op.command,
|
|
705
|
-
status: op.status,
|
|
706
|
-
startedAt: op.startedAt,
|
|
707
|
-
completedAt: op.completedAt,
|
|
708
|
-
errorMessage: op.errorMessage,
|
|
709
|
-
detail: op.detail
|
|
710
|
-
}, ...s.operationHistory ?? []].slice(0, OPERATION_HISTORY_CAP);
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* Persist to D1 (no-op for local). Uses optimistic concurrency: re-reads
|
|
714
|
-
* `revision` before write; throws {@link StateConflictError} if another
|
|
715
|
-
* writer advanced the row since {@link hydrate}.
|
|
716
|
-
*/
|
|
717
|
-
async persist(api) {
|
|
718
|
-
if (this.env === "local") {
|
|
719
|
-
this.dirty = false;
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
if (!this.dirty || !this.state || !this.tamerStateDbUuid) return;
|
|
723
|
-
const rowKey = stateRowKey(this.stackName);
|
|
724
|
-
const { rows } = await api.d1Query(this.tamerStateDbUuid, `SELECT v FROM tamer_kv WHERE k = ?`, [rowKey]);
|
|
725
|
-
let remoteRev = 0;
|
|
726
|
-
if (rows.length > 0) {
|
|
727
|
-
const v = rows[0]["v"];
|
|
728
|
-
if (typeof v === "string") remoteRev = parseCfiStateJson(v).revision ?? 0;
|
|
729
|
-
}
|
|
730
|
-
if (remoteRev !== this.baselineRevision) throw new StateConflictError(`Tamer state conflict (stack=${this.stackName}): remote revision ${remoteRev} !== expected ${this.baselineRevision}. Re-run after refresh.`);
|
|
731
|
-
this.state.revision = remoteRev + 1;
|
|
732
|
-
this.state.syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
733
|
-
const json = JSON.stringify(this.state);
|
|
734
|
-
await api.d1Query(this.tamerStateDbUuid, `INSERT INTO tamer_kv (k, v) VALUES (?, ?)
|
|
735
|
-
ON CONFLICT(k) DO UPDATE SET v = excluded.v`, [rowKey, json]);
|
|
736
|
-
this.baselineRevision = this.state.revision;
|
|
737
|
-
this.dirty = false;
|
|
738
|
-
}
|
|
739
|
-
/** Mark clean without writing (e.g. before deleting the state database). */
|
|
740
|
-
clearDirty() {
|
|
741
|
-
this.dirty = false;
|
|
742
|
-
}
|
|
743
|
-
};
|
|
744
|
-
function stackOutputsEqual(a, b) {
|
|
745
|
-
const ak = Object.keys(a).sort();
|
|
746
|
-
const bk = Object.keys(b).sort();
|
|
747
|
-
if (ak.length !== bk.length) return false;
|
|
748
|
-
for (let i = 0; i < ak.length; i++) {
|
|
749
|
-
if (ak[i] !== bk[i]) return false;
|
|
750
|
-
const k = ak[i];
|
|
751
|
-
const av = a[k];
|
|
752
|
-
const bv = b[k];
|
|
753
|
-
if (av.value !== bv.value || av.source !== bv.source) return false;
|
|
754
|
-
}
|
|
755
|
-
return true;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
//#endregion
|
|
759
|
-
export { tenantStateKey as a, createEmptyCfiState as c, tamerStateDatabaseName as d, stackNameForConfig as f, tenantShardDatabaseName as i, destroyTamerStateDatabase as l, parseTenantShardRoles as n, effectiveDispatchNamespaceName as o, tenantDispatchScriptName as r, isEphemeralEnv as s, StateManager as t, ensureTamerStateDatabase as u };
|
|
760
|
-
//# sourceMappingURL=StateManager-JLBtz9V-.mjs.map
|