@fragno-dev/cloudflare-fragment 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +16 -0
- package/README.md +92 -0
- package/dist/browser/client/react.d.ts +290 -0
- package/dist/browser/client/react.d.ts.map +1 -0
- package/dist/browser/client/react.js +166 -0
- package/dist/browser/client/react.js.map +1 -0
- package/dist/browser/client/solid.d.ts +292 -0
- package/dist/browser/client/solid.d.ts.map +1 -0
- package/dist/browser/client/solid.js +136 -0
- package/dist/browser/client/solid.js.map +1 -0
- package/dist/browser/client/svelte.d.ts +287 -0
- package/dist/browser/client/svelte.d.ts.map +1 -0
- package/dist/browser/client/svelte.js +134 -0
- package/dist/browser/client/svelte.js.map +1 -0
- package/dist/browser/client/vanilla.d.ts +316 -0
- package/dist/browser/client/vanilla.d.ts.map +1 -0
- package/dist/browser/client/vanilla.js +160 -0
- package/dist/browser/client/vanilla.js.map +1 -0
- package/dist/browser/client/vue.d.ts +290 -0
- package/dist/browser/client/vue.d.ts.map +1 -0
- package/dist/browser/client/vue.js +133 -0
- package/dist/browser/client/vue.js.map +1 -0
- package/dist/browser/client-Bk-J98pf.d.ts +679 -0
- package/dist/browser/client-Bk-J98pf.d.ts.map +1 -0
- package/dist/browser/index.d.ts +2027 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +27 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/schema-Bt-h9kGf.js +2348 -0
- package/dist/browser/schema-Bt-h9kGf.js.map +1 -0
- package/dist/node/cloudflare-api.d.ts +106 -0
- package/dist/node/cloudflare-api.d.ts.map +1 -0
- package/dist/node/cloudflare-api.js +146 -0
- package/dist/node/cloudflare-api.js.map +1 -0
- package/dist/node/contracts.d.ts +288 -0
- package/dist/node/contracts.d.ts.map +1 -0
- package/dist/node/contracts.js +66 -0
- package/dist/node/contracts.js.map +1 -0
- package/dist/node/definition.d.ts +339 -0
- package/dist/node/definition.d.ts.map +1 -0
- package/dist/node/definition.js +417 -0
- package/dist/node/definition.js.map +1 -0
- package/dist/node/deployment-tag.d.ts +13 -0
- package/dist/node/deployment-tag.d.ts.map +1 -0
- package/dist/node/deployment-tag.js +73 -0
- package/dist/node/deployment-tag.js.map +1 -0
- package/dist/node/index.d.ts +786 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +35 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/routes.d.ts +520 -0
- package/dist/node/routes.d.ts.map +1 -0
- package/dist/node/routes.js +100 -0
- package/dist/node/routes.js.map +1 -0
- package/dist/node/schema.d.ts +11 -0
- package/dist/node/schema.d.ts.map +1 -0
- package/dist/node/schema.js +24 -0
- package/dist/node/schema.js.map +1 -0
- package/dist/node/script-name.d.ts +13 -0
- package/dist/node/script-name.d.ts.map +1 -0
- package/dist/node/script-name.js +35 -0
- package/dist/node/script-name.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { createCloudflareApiClient, getCloudflareApiError, reconcileCloudflareWorkerDeployment, resolveCloudflareDispatchNamespaceName } from "./cloudflare-api.js";
|
|
2
|
+
import { SUPPORTED_DEPLOYMENT_FORMAT } from "./contracts.js";
|
|
3
|
+
import { cloudflareSchema } from "./schema.js";
|
|
4
|
+
import { resolveCloudflareScriptName } from "./script-name.js";
|
|
5
|
+
import { defineFragment } from "@fragno-dev/core";
|
|
6
|
+
import { ExponentialBackoffRetryPolicy, withDatabase } from "@fragno-dev/db";
|
|
7
|
+
|
|
8
|
+
//#region src/definition.ts
|
|
9
|
+
const textEncoder = new TextEncoder();
|
|
10
|
+
const hookWriteRetryPolicy = new ExponentialBackoffRetryPolicy({
|
|
11
|
+
maxRetries: 5,
|
|
12
|
+
initialDelayMs: 10,
|
|
13
|
+
maxDelayMs: 250
|
|
14
|
+
});
|
|
15
|
+
const resolveDispatcherConfig = (config) => {
|
|
16
|
+
if ("dispatcher" in config && config.dispatcher !== void 0) return config.dispatcher;
|
|
17
|
+
if ("dispatchNamespace" in config && config.dispatchNamespace !== void 0) return config.dispatchNamespace;
|
|
18
|
+
throw new Error("Cloudflare fragment requires `dispatcher` or `dispatchNamespace` to resolve the dispatch namespace name.");
|
|
19
|
+
};
|
|
20
|
+
const normalizeStringList = (value) => {
|
|
21
|
+
if (!Array.isArray(value)) return [];
|
|
22
|
+
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
23
|
+
};
|
|
24
|
+
const toIsoDateTime = (value) => {
|
|
25
|
+
return value ? value.toISOString() : null;
|
|
26
|
+
};
|
|
27
|
+
const buildDeploymentSummary = (deployment, appId) => {
|
|
28
|
+
const cloudflare = deployment.cloudflareEtag || deployment.cloudflareModifiedOn ? {
|
|
29
|
+
etag: deployment.cloudflareEtag,
|
|
30
|
+
modifiedOn: deployment.cloudflareModifiedOn
|
|
31
|
+
} : null;
|
|
32
|
+
return {
|
|
33
|
+
id: deployment.id.valueOf(),
|
|
34
|
+
appId,
|
|
35
|
+
scriptName: deployment.scriptName,
|
|
36
|
+
status: deployment.status === "queued" || deployment.status === "deploying" || deployment.status === "succeeded" || deployment.status === "failed" ? deployment.status : "failed",
|
|
37
|
+
format: deployment.format === SUPPORTED_DEPLOYMENT_FORMAT ? SUPPORTED_DEPLOYMENT_FORMAT : "esmodule",
|
|
38
|
+
entrypoint: deployment.entrypoint,
|
|
39
|
+
sourceByteLength: deployment.sourceByteLength,
|
|
40
|
+
compatibilityDate: deployment.compatibilityDate,
|
|
41
|
+
compatibilityFlags: normalizeStringList(deployment.compatibilityFlags),
|
|
42
|
+
attemptCount: deployment.attemptCount,
|
|
43
|
+
queuedAt: deployment.createdAt.toISOString(),
|
|
44
|
+
startedAt: toIsoDateTime(deployment.startedAt),
|
|
45
|
+
completedAt: toIsoDateTime(deployment.completedAt),
|
|
46
|
+
errorCode: deployment.errorCode,
|
|
47
|
+
errorMessage: deployment.errorMessage,
|
|
48
|
+
cloudflare,
|
|
49
|
+
createdAt: deployment.createdAt.toISOString(),
|
|
50
|
+
updatedAt: deployment.updatedAt.toISOString()
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const buildDeploymentDetail = (deployment, appId) => {
|
|
54
|
+
return {
|
|
55
|
+
...buildDeploymentSummary(deployment, appId),
|
|
56
|
+
sourceCode: deployment.sourceCode ?? ""
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
const buildAppSummary = (app, latestDeployment) => {
|
|
60
|
+
return {
|
|
61
|
+
id: app.id.valueOf(),
|
|
62
|
+
scriptName: app.scriptName,
|
|
63
|
+
latestDeployment: latestDeployment ? buildDeploymentSummary(latestDeployment, app.id.valueOf()) : null,
|
|
64
|
+
createdAt: app.createdAt.toISOString(),
|
|
65
|
+
updatedAt: app.updatedAt.toISOString()
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const formatDeployError = (error) => {
|
|
69
|
+
const cloudflareError = getCloudflareApiError(error);
|
|
70
|
+
if (cloudflareError) return {
|
|
71
|
+
code: cloudflareError.code,
|
|
72
|
+
message: cloudflareError.message
|
|
73
|
+
};
|
|
74
|
+
if (error instanceof Error) return {
|
|
75
|
+
code: error.name || "Error",
|
|
76
|
+
message: error.message
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
code: "unknown",
|
|
80
|
+
message: String(error)
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const readCloudflareSummary = (response) => {
|
|
84
|
+
return {
|
|
85
|
+
etag: response.etag ?? null,
|
|
86
|
+
modifiedOn: response.modified_on ?? null,
|
|
87
|
+
raw: response
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const createLivePointerGuard = (input) => {
|
|
91
|
+
if (input.expectedLiveEtag !== null) return {
|
|
92
|
+
kind: "etag",
|
|
93
|
+
expectedLiveEtag: input.expectedLiveEtag
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
kind: "first-deploy",
|
|
97
|
+
leaseDeploymentId: input.deploymentId
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const canPromoteDeploymentToLive = (app, deploymentId, livePointerGuard) => {
|
|
101
|
+
if (livePointerGuard.kind === "first-deploy") return app.liveCloudflareEtag === null && app.firstDeploymentLeaseId === livePointerGuard.leaseDeploymentId;
|
|
102
|
+
return app.liveCloudflareEtag === livePointerGuard.expectedLiveEtag || app.liveDeploymentId === deploymentId;
|
|
103
|
+
};
|
|
104
|
+
const cloudflareFragmentDefinition = defineFragment("cloudflare-fragment").extend(withDatabase(cloudflareSchema)).withDependencies(({ config }) => {
|
|
105
|
+
const dispatchNamespace = resolveCloudflareDispatchNamespaceName(resolveDispatcherConfig(config));
|
|
106
|
+
return {
|
|
107
|
+
cloudflare: "cloudflare" in config && config.cloudflare ? config.cloudflare : createCloudflareApiClient({
|
|
108
|
+
apiToken: config.apiToken,
|
|
109
|
+
fetchImplementation: config.fetchImplementation
|
|
110
|
+
}),
|
|
111
|
+
dispatchNamespace
|
|
112
|
+
};
|
|
113
|
+
}).providesService("cloudflare", ({ deps }) => ({ getClient: () => deps.cloudflare })).provideHooks(({ defineHook, deps, config }) => ({ deployWorker: defineHook(async function(input) {
|
|
114
|
+
const livePointerGuard = createLivePointerGuard(input);
|
|
115
|
+
let remoteResult = null;
|
|
116
|
+
let remoteError = null;
|
|
117
|
+
let remoteUploadAttempted = false;
|
|
118
|
+
const prepareFirstDeployment = async () => {
|
|
119
|
+
if (livePointerGuard.kind !== "first-deploy") return null;
|
|
120
|
+
return await this.handlerTx({ retryPolicy: hookWriteRetryPolicy }).retrieve(({ forSchema }) => forSchema(cloudflareSchema).findFirst("deployment", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.deploymentId)).join((j) => j.app()))).mutate(({ forSchema, retrieveResult: [deployment] }) => {
|
|
121
|
+
if (!deployment?.app) return { action: "missing" };
|
|
122
|
+
const uow = forSchema(cloudflareSchema);
|
|
123
|
+
const now = uow.now();
|
|
124
|
+
const deploymentId = deployment.id.valueOf();
|
|
125
|
+
const app = deployment.app;
|
|
126
|
+
if (app.liveCloudflareEtag !== null) {
|
|
127
|
+
if (app.firstDeploymentLeaseId === deploymentId) uow.update("app", app.id, (b) => b.set({
|
|
128
|
+
updatedAt: now,
|
|
129
|
+
firstDeploymentLeaseId: null
|
|
130
|
+
}).check());
|
|
131
|
+
return {
|
|
132
|
+
action: "superseded",
|
|
133
|
+
currentDeploymentId: app.liveDeploymentId,
|
|
134
|
+
currentDeploymentTag: app.liveDeploymentId ?? "current-live-deployment",
|
|
135
|
+
currentEtag: app.liveCloudflareEtag,
|
|
136
|
+
currentModifiedOn: null
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (app.firstDeploymentLeaseId !== null && app.firstDeploymentLeaseId !== deploymentId) return { action: "retry" };
|
|
140
|
+
if (app.firstDeploymentLeaseId !== deploymentId) uow.update("app", app.id, (b) => b.set({
|
|
141
|
+
updatedAt: now,
|
|
142
|
+
firstDeploymentLeaseId: deploymentId
|
|
143
|
+
}).check());
|
|
144
|
+
return { action: "continue" };
|
|
145
|
+
}).execute();
|
|
146
|
+
};
|
|
147
|
+
const firstDeploymentPreparation = await prepareFirstDeployment();
|
|
148
|
+
if (firstDeploymentPreparation?.action === "missing") return;
|
|
149
|
+
if (firstDeploymentPreparation?.action === "retry") throw new Error(`Initial Cloudflare deploy for '${input.scriptName}' is already in progress.`);
|
|
150
|
+
if (firstDeploymentPreparation?.action === "superseded") remoteResult = firstDeploymentPreparation;
|
|
151
|
+
if (!remoteResult) {
|
|
152
|
+
remoteUploadAttempted = true;
|
|
153
|
+
try {
|
|
154
|
+
remoteResult = await reconcileCloudflareWorkerDeployment(deps.cloudflare, {
|
|
155
|
+
accountId: config.accountId,
|
|
156
|
+
dispatchNamespace: deps.dispatchNamespace,
|
|
157
|
+
appId: input.appId,
|
|
158
|
+
deploymentId: input.deploymentId,
|
|
159
|
+
expectedLiveEtag: input.expectedLiveEtag,
|
|
160
|
+
deploymentTagPrefix: config.deploymentTagPrefix,
|
|
161
|
+
scriptName: input.scriptName,
|
|
162
|
+
entrypoint: input.entrypoint,
|
|
163
|
+
moduleContent: input.moduleContent,
|
|
164
|
+
compatibilityDate: input.compatibilityDate,
|
|
165
|
+
compatibilityFlags: input.compatibilityFlags,
|
|
166
|
+
scriptTags: config.scriptTags
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
remoteError = error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const remoteDeploymentId = remoteResult?.action === "superseded" || remoteResult?.action === "already-deployed" ? remoteResult.currentDeploymentId : null;
|
|
173
|
+
await (remoteDeploymentId && remoteDeploymentId !== input.deploymentId ? this.handlerTx({ retryPolicy: hookWriteRetryPolicy }).retrieve(({ forSchema }) => forSchema(cloudflareSchema).findFirst("deployment", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.deploymentId)).join((j) => j.app())).findFirst("deployment", (b) => b.whereIndex("primary", (eb) => eb("id", "=", remoteDeploymentId)))).transformRetrieve(([deployment, remoteDeployment]) => ({
|
|
174
|
+
deployment,
|
|
175
|
+
remoteDeployment: remoteDeployment ?? null
|
|
176
|
+
})) : this.handlerTx({ retryPolicy: hookWriteRetryPolicy }).retrieve(({ forSchema }) => forSchema(cloudflareSchema).findFirst("deployment", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.deploymentId)).join((j) => j.app()))).transformRetrieve(([deployment]) => ({
|
|
177
|
+
deployment,
|
|
178
|
+
remoteDeployment: null
|
|
179
|
+
}))).mutate(({ forSchema, retrieveResult: { deployment, remoteDeployment } }) => {
|
|
180
|
+
if (!deployment || !deployment.app) return;
|
|
181
|
+
const uow = forSchema(cloudflareSchema);
|
|
182
|
+
const now = uow.now();
|
|
183
|
+
const appId = deployment.app.id;
|
|
184
|
+
const deploymentId = deployment.id.valueOf();
|
|
185
|
+
const ownsFirstDeploymentLease = deployment.app.firstDeploymentLeaseId === deploymentId;
|
|
186
|
+
const updateApp = (changes = {}) => {
|
|
187
|
+
uow.update("app", appId, (b) => b.set({
|
|
188
|
+
updatedAt: now,
|
|
189
|
+
...changes,
|
|
190
|
+
...ownsFirstDeploymentLease ? { firstDeploymentLeaseId: null } : {}
|
|
191
|
+
}).check());
|
|
192
|
+
};
|
|
193
|
+
if (remoteResult?.action === "already-deployed") {
|
|
194
|
+
const etag = remoteResult.currentEtag ?? deployment.cloudflareEtag ?? (deployment.app.liveDeploymentId === deploymentId ? deployment.app.liveCloudflareEtag : null);
|
|
195
|
+
const modifiedOn = remoteResult.currentModifiedOn ?? deployment.cloudflareModifiedOn;
|
|
196
|
+
uow.update("deployment", deployment.id, (b) => b.set({
|
|
197
|
+
status: "succeeded",
|
|
198
|
+
updatedAt: now,
|
|
199
|
+
startedAt: deployment.startedAt ?? now,
|
|
200
|
+
completedAt: deployment.completedAt ?? now,
|
|
201
|
+
attemptCount: Math.max(deployment.attemptCount, 1),
|
|
202
|
+
errorCode: null,
|
|
203
|
+
errorMessage: null,
|
|
204
|
+
cloudflareEtag: etag,
|
|
205
|
+
cloudflareModifiedOn: modifiedOn
|
|
206
|
+
}).check());
|
|
207
|
+
updateApp({
|
|
208
|
+
liveDeploymentId: deploymentId,
|
|
209
|
+
liveCloudflareEtag: etag
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (remoteResult?.action === "uploaded") {
|
|
214
|
+
const cloudflare = readCloudflareSummary(remoteResult.response);
|
|
215
|
+
const canMoveLivePointer = canPromoteDeploymentToLive(deployment.app, deploymentId, livePointerGuard);
|
|
216
|
+
uow.update("deployment", deployment.id, (b) => b.set({
|
|
217
|
+
status: "succeeded",
|
|
218
|
+
updatedAt: now,
|
|
219
|
+
startedAt: deployment.startedAt ?? now,
|
|
220
|
+
completedAt: now,
|
|
221
|
+
attemptCount: deployment.attemptCount + 1,
|
|
222
|
+
errorCode: null,
|
|
223
|
+
errorMessage: null,
|
|
224
|
+
cloudflareEtag: cloudflare.etag,
|
|
225
|
+
cloudflareModifiedOn: cloudflare.modifiedOn,
|
|
226
|
+
cloudflareResponse: cloudflare.raw
|
|
227
|
+
}).check());
|
|
228
|
+
updateApp(canMoveLivePointer ? {
|
|
229
|
+
liveDeploymentId: deploymentId,
|
|
230
|
+
liveCloudflareEtag: cloudflare.etag
|
|
231
|
+
} : {});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (remoteResult?.action === "superseded") {
|
|
235
|
+
const supersededBy = remoteDeployment?.id.valueOf() ?? remoteResult.currentDeploymentId ?? remoteResult.currentDeploymentTag;
|
|
236
|
+
const winnerEtag = remoteResult.currentEtag ?? remoteDeployment?.cloudflareEtag ?? (deployment.app.liveDeploymentId === remoteResult.currentDeploymentId ? deployment.app.liveCloudflareEtag : null);
|
|
237
|
+
const winnerModifiedOn = remoteResult.currentModifiedOn ?? remoteDeployment?.cloudflareModifiedOn ?? null;
|
|
238
|
+
if (remoteDeployment) uow.update("deployment", remoteDeployment.id, (b) => b.set({
|
|
239
|
+
status: "succeeded",
|
|
240
|
+
updatedAt: now,
|
|
241
|
+
startedAt: remoteDeployment.startedAt ?? now,
|
|
242
|
+
completedAt: remoteDeployment.completedAt ?? now,
|
|
243
|
+
attemptCount: Math.max(remoteDeployment.attemptCount, 1),
|
|
244
|
+
errorCode: null,
|
|
245
|
+
errorMessage: null,
|
|
246
|
+
cloudflareEtag: winnerEtag,
|
|
247
|
+
cloudflareModifiedOn: winnerModifiedOn
|
|
248
|
+
}).check());
|
|
249
|
+
updateApp({
|
|
250
|
+
liveDeploymentId: remoteResult.currentDeploymentId,
|
|
251
|
+
liveCloudflareEtag: winnerEtag
|
|
252
|
+
});
|
|
253
|
+
if (deployment.status === "succeeded") return;
|
|
254
|
+
uow.update("deployment", deployment.id, (b) => b.set({
|
|
255
|
+
status: "failed",
|
|
256
|
+
updatedAt: now,
|
|
257
|
+
startedAt: deployment.startedAt ?? now,
|
|
258
|
+
completedAt: now,
|
|
259
|
+
attemptCount: deployment.attemptCount + (remoteUploadAttempted ? 1 : 0),
|
|
260
|
+
errorCode: "DEPLOYMENT_SUPERSEDED",
|
|
261
|
+
errorMessage: `Deployment was superseded by '${supersededBy}'.`
|
|
262
|
+
}).check());
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (deployment.status === "succeeded") {
|
|
266
|
+
updateApp();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const formatted = formatDeployError(remoteError);
|
|
270
|
+
uow.update("deployment", deployment.id, (b) => b.set({
|
|
271
|
+
status: "failed",
|
|
272
|
+
updatedAt: now,
|
|
273
|
+
startedAt: deployment.startedAt ?? now,
|
|
274
|
+
completedAt: now,
|
|
275
|
+
attemptCount: deployment.attemptCount + (remoteUploadAttempted ? 1 : 0),
|
|
276
|
+
errorCode: formatted.code,
|
|
277
|
+
errorMessage: formatted.message
|
|
278
|
+
}).check());
|
|
279
|
+
updateApp();
|
|
280
|
+
}).execute();
|
|
281
|
+
}) })).providesBaseService(({ defineService, config }) => {
|
|
282
|
+
return defineService({
|
|
283
|
+
upsertApp: function(appId) {
|
|
284
|
+
return this.serviceTx(cloudflareSchema).retrieve((uow) => uow.findFirst("app", (b) => b.whereIndex("primary", (eb) => eb("id", "=", appId)))).mutate(({ uow, retrieveResult: [existingApp] }) => {
|
|
285
|
+
if (existingApp) return buildAppSummary(existingApp, null);
|
|
286
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
287
|
+
const scriptName = resolveCloudflareScriptName(appId, config);
|
|
288
|
+
return {
|
|
289
|
+
id: uow.create("app", {
|
|
290
|
+
id: appId,
|
|
291
|
+
scriptName,
|
|
292
|
+
liveDeploymentId: null,
|
|
293
|
+
liveCloudflareEtag: null,
|
|
294
|
+
firstDeploymentLeaseId: null,
|
|
295
|
+
createdAt,
|
|
296
|
+
updatedAt: createdAt
|
|
297
|
+
}).valueOf(),
|
|
298
|
+
scriptName,
|
|
299
|
+
latestDeployment: null,
|
|
300
|
+
createdAt: createdAt.toISOString(),
|
|
301
|
+
updatedAt: createdAt.toISOString()
|
|
302
|
+
};
|
|
303
|
+
}).build();
|
|
304
|
+
},
|
|
305
|
+
queueDeployment: function(appId, request) {
|
|
306
|
+
return this.serviceTx(cloudflareSchema).retrieve((uow) => uow.findFirst("app", (b) => b.whereIndex("primary", (eb) => eb("id", "=", appId)))).mutate(({ uow, retrieveResult: [existingApp] }) => {
|
|
307
|
+
const now = /* @__PURE__ */ new Date();
|
|
308
|
+
const compatibilityDate = request.compatibilityDate ?? config.compatibilityDate;
|
|
309
|
+
const compatibilityFlags = normalizeStringList(request.compatibilityFlags ?? config.compatibilityFlags);
|
|
310
|
+
const scriptName = existingApp?.scriptName ?? resolveCloudflareScriptName(appId, config);
|
|
311
|
+
const sourceByteLength = textEncoder.encode(request.script.content).byteLength;
|
|
312
|
+
const ensuredAppId = existingApp?.id ?? uow.create("app", {
|
|
313
|
+
id: appId,
|
|
314
|
+
scriptName,
|
|
315
|
+
liveDeploymentId: null,
|
|
316
|
+
liveCloudflareEtag: null,
|
|
317
|
+
firstDeploymentLeaseId: null,
|
|
318
|
+
createdAt: now,
|
|
319
|
+
updatedAt: now
|
|
320
|
+
});
|
|
321
|
+
if (existingApp) uow.update("app", existingApp.id, (b) => b.set({ updatedAt: now }).check());
|
|
322
|
+
const deploymentIdValue = uow.create("deployment", {
|
|
323
|
+
appId: ensuredAppId,
|
|
324
|
+
status: "queued",
|
|
325
|
+
format: SUPPORTED_DEPLOYMENT_FORMAT,
|
|
326
|
+
entrypoint: request.script.entrypoint,
|
|
327
|
+
scriptName,
|
|
328
|
+
sourceCode: request.script.content,
|
|
329
|
+
sourceByteLength,
|
|
330
|
+
compatibilityDate,
|
|
331
|
+
compatibilityFlags,
|
|
332
|
+
attemptCount: 0,
|
|
333
|
+
startedAt: null,
|
|
334
|
+
completedAt: null,
|
|
335
|
+
errorCode: null,
|
|
336
|
+
errorMessage: null,
|
|
337
|
+
cloudflareEtag: null,
|
|
338
|
+
cloudflareModifiedOn: null,
|
|
339
|
+
cloudflareResponse: null,
|
|
340
|
+
createdAt: now,
|
|
341
|
+
updatedAt: now
|
|
342
|
+
}).valueOf();
|
|
343
|
+
const expectedLiveEtag = existingApp?.liveCloudflareEtag ?? null;
|
|
344
|
+
uow.triggerHook("deployWorker", {
|
|
345
|
+
deploymentId: deploymentIdValue,
|
|
346
|
+
appId: ensuredAppId.valueOf(),
|
|
347
|
+
expectedLiveEtag,
|
|
348
|
+
scriptName,
|
|
349
|
+
entrypoint: request.script.entrypoint,
|
|
350
|
+
moduleContent: request.script.content,
|
|
351
|
+
compatibilityDate,
|
|
352
|
+
compatibilityFlags
|
|
353
|
+
});
|
|
354
|
+
return {
|
|
355
|
+
id: deploymentIdValue,
|
|
356
|
+
appId: ensuredAppId.valueOf(),
|
|
357
|
+
scriptName,
|
|
358
|
+
status: "queued",
|
|
359
|
+
format: SUPPORTED_DEPLOYMENT_FORMAT,
|
|
360
|
+
entrypoint: request.script.entrypoint,
|
|
361
|
+
sourceByteLength,
|
|
362
|
+
compatibilityDate,
|
|
363
|
+
compatibilityFlags,
|
|
364
|
+
attemptCount: 0,
|
|
365
|
+
queuedAt: now.toISOString(),
|
|
366
|
+
startedAt: null,
|
|
367
|
+
completedAt: null,
|
|
368
|
+
errorCode: null,
|
|
369
|
+
errorMessage: null,
|
|
370
|
+
cloudflare: null,
|
|
371
|
+
createdAt: now.toISOString(),
|
|
372
|
+
updatedAt: now.toISOString()
|
|
373
|
+
};
|
|
374
|
+
}).build();
|
|
375
|
+
},
|
|
376
|
+
getDeployment: function(deploymentId) {
|
|
377
|
+
return this.serviceTx(cloudflareSchema).retrieve((uow) => uow.findFirst("deployment", (b) => b.whereIndex("primary", (eb) => eb("id", "=", deploymentId)).join((j) => j.app()))).transformRetrieve(([deployment]) => {
|
|
378
|
+
return deployment?.app ? buildDeploymentDetail(deployment, deployment.app.id.valueOf()) : null;
|
|
379
|
+
}).build();
|
|
380
|
+
},
|
|
381
|
+
getAppState: function(appId) {
|
|
382
|
+
return this.serviceTx(cloudflareSchema).retrieve((uow) => uow.findFirst("app", (b) => b.whereIndex("primary", (eb) => eb("id", "=", appId))).findFirst("deployment", (b) => b.whereIndex("idx_deployment_app_createdAt", (eb) => eb("appId", "=", appId)).orderByIndex("idx_deployment_app_createdAt", "desc"))).transformRetrieve(([app, latestDeployment]) => {
|
|
383
|
+
return app ? {
|
|
384
|
+
...buildAppSummary(app, latestDeployment),
|
|
385
|
+
liveDeploymentId: app.liveDeploymentId,
|
|
386
|
+
liveCloudflareEtag: app.liveCloudflareEtag
|
|
387
|
+
} : null;
|
|
388
|
+
}).build();
|
|
389
|
+
},
|
|
390
|
+
listApps: function() {
|
|
391
|
+
return this.serviceTx(cloudflareSchema).retrieve((uow) => uow.find("app", (b) => b.whereIndex("primary")).find("deployment", (b) => b.whereIndex("primary").join((j) => j.app()))).transformRetrieve(([apps, deployments]) => {
|
|
392
|
+
const latestDeploymentsByAppId = /* @__PURE__ */ new Map();
|
|
393
|
+
for (const deployment of deployments) {
|
|
394
|
+
if (!deployment.app) continue;
|
|
395
|
+
const deploymentAppId = deployment.app.id.valueOf();
|
|
396
|
+
const existingDeployment = latestDeploymentsByAppId.get(deploymentAppId);
|
|
397
|
+
if (!existingDeployment || deployment.createdAt.getTime() > existingDeployment.createdAt.getTime()) latestDeploymentsByAppId.set(deploymentAppId, deployment);
|
|
398
|
+
}
|
|
399
|
+
return apps.map((app) => buildAppSummary(app, latestDeploymentsByAppId.get(app.id.valueOf()) ?? null)).sort((left, right) => {
|
|
400
|
+
const updatedAtCompare = right.updatedAt.localeCompare(left.updatedAt);
|
|
401
|
+
if (updatedAtCompare !== 0) return updatedAtCompare;
|
|
402
|
+
return left.id.localeCompare(right.id);
|
|
403
|
+
});
|
|
404
|
+
}).build();
|
|
405
|
+
},
|
|
406
|
+
listAppDeployments: function(appId) {
|
|
407
|
+
return this.serviceTx(cloudflareSchema).retrieve((uow) => uow.findFirst("app", (b) => b.whereIndex("primary", (eb) => eb("id", "=", appId))).find("deployment", (b) => b.whereIndex("idx_deployment_app_createdAt", (eb) => eb("appId", "=", appId)).orderByIndex("idx_deployment_app_createdAt", "desc"))).transformRetrieve(([app, deployments]) => {
|
|
408
|
+
if (!app) return null;
|
|
409
|
+
return deployments.map((deployment) => buildDeploymentSummary(deployment, app.id.valueOf()));
|
|
410
|
+
}).build();
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}).build();
|
|
414
|
+
|
|
415
|
+
//#endregion
|
|
416
|
+
export { cloudflareFragmentDefinition };
|
|
417
|
+
//# sourceMappingURL=definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition.js","names":[],"sources":["../../src/definition.ts"],"sourcesContent":["import type { ClientOptions as CloudflareClientOptions } from \"cloudflare\";\n\nimport { defineFragment } from \"@fragno-dev/core\";\nimport { ExponentialBackoffRetryPolicy, withDatabase, type HookFn } from \"@fragno-dev/db\";\n\nimport {\n createCloudflareApiClient,\n getCloudflareApiError,\n reconcileCloudflareWorkerDeployment,\n resolveCloudflareDispatchNamespaceName,\n type CloudflareApiClient,\n type CloudflareDispatcherConfig,\n type CloudflareScriptUpdateResponse,\n} from \"./cloudflare-api\";\nimport {\n SUPPORTED_DEPLOYMENT_FORMAT,\n type CloudflareAppSummary,\n type CloudflareDeployRequest,\n type CloudflareDeploymentDetail,\n type CloudflareDeploymentSummary,\n} from \"./contracts\";\nimport { cloudflareSchema } from \"./schema\";\nimport { resolveCloudflareScriptName, type CloudflareScriptNameConfig } from \"./script-name\";\n\ntype DeploymentRow = {\n id: { valueOf(): string };\n status: string;\n format: string;\n entrypoint: string;\n scriptName: string;\n sourceByteLength: number;\n compatibilityDate: string;\n compatibilityFlags: unknown;\n attemptCount: number;\n startedAt: Date | null;\n completedAt: Date | null;\n errorCode: string | null;\n errorMessage: string | null;\n cloudflareEtag: string | null;\n cloudflareModifiedOn: string | null;\n createdAt: Date;\n updatedAt: Date;\n};\n\ntype AppRow = {\n id: { valueOf(): string };\n scriptName: string;\n liveDeploymentId: string | null;\n liveCloudflareEtag: string | null;\n firstDeploymentLeaseId: string | null;\n createdAt: Date;\n updatedAt: Date;\n};\n\ntype CloudflareLiveDeploymentSnapshot = {\n currentDeploymentTag: string;\n currentDeploymentId: string | null;\n currentEtag: string | null;\n currentModifiedOn: string | null;\n};\n\ntype DeployWorkerRemoteResult =\n | Awaited<ReturnType<typeof reconcileCloudflareWorkerDeployment>>\n | ({ action: \"superseded\" } & CloudflareLiveDeploymentSnapshot);\n\ntype FirstDeploymentPreparation =\n | { action: \"missing\" }\n | { action: \"retry\" }\n | { action: \"continue\" }\n | ({ action: \"superseded\" } & CloudflareLiveDeploymentSnapshot);\n\ntype LivePointerGuard =\n | {\n kind: \"etag\";\n expectedLiveEtag: string;\n }\n | {\n kind: \"first-deploy\";\n leaseDeploymentId: string;\n };\n\nconst textEncoder = new TextEncoder();\nconst hookWriteRetryPolicy = new ExponentialBackoffRetryPolicy({\n maxRetries: 5,\n initialDelayMs: 10,\n maxDelayMs: 250,\n});\n\ntype CloudflareFragmentSharedConfig = CloudflareScriptNameConfig & {\n accountId: string;\n compatibilityDate: string;\n compatibilityFlags?: string[];\n scriptTags?: string[];\n deploymentTagPrefix?: string;\n};\n\ntype CloudflareFragmentApiTokenConfig = {\n apiToken: string;\n cloudflare?: never;\n fetchImplementation?: CloudflareClientOptions[\"fetch\"];\n};\n\ntype CloudflareFragmentClientConfig = {\n cloudflare: CloudflareApiClient;\n apiToken?: never;\n fetchImplementation?: never;\n};\n\nexport type CloudflareFragmentConfig = CloudflareFragmentSharedConfig &\n (CloudflareFragmentApiTokenConfig | CloudflareFragmentClientConfig) &\n (\n | {\n dispatcher: CloudflareDispatcherConfig;\n dispatchNamespace?: never;\n }\n | {\n dispatchNamespace: string;\n dispatcher?: never;\n }\n );\n\nexport type DeployWorkerHookInput = {\n deploymentId: string;\n appId: string;\n expectedLiveEtag: string | null;\n scriptName: string;\n entrypoint: string;\n moduleContent: string;\n compatibilityDate: string;\n compatibilityFlags: string[];\n};\n\nexport type CloudflareHooksMap = {\n deployWorker: HookFn<DeployWorkerHookInput>;\n};\n\nconst resolveDispatcherConfig = (config: CloudflareFragmentConfig): CloudflareDispatcherConfig => {\n if (\"dispatcher\" in config && config.dispatcher !== undefined) {\n return config.dispatcher;\n }\n\n if (\"dispatchNamespace\" in config && config.dispatchNamespace !== undefined) {\n return config.dispatchNamespace;\n }\n\n throw new Error(\n \"Cloudflare fragment requires `dispatcher` or `dispatchNamespace` to resolve the dispatch namespace name.\",\n );\n};\n\nconst normalizeStringList = (value: unknown) => {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.filter((entry): entry is string => typeof entry === \"string\" && entry.length > 0);\n};\n\nconst toIsoDateTime = (value: Date | null) => {\n return value ? value.toISOString() : null;\n};\n\nconst buildDeploymentSummary = (\n deployment: DeploymentRow,\n appId: string,\n): CloudflareDeploymentSummary => {\n const cloudflare =\n deployment.cloudflareEtag || deployment.cloudflareModifiedOn\n ? {\n etag: deployment.cloudflareEtag,\n modifiedOn: deployment.cloudflareModifiedOn,\n }\n : null;\n\n return {\n id: deployment.id.valueOf(),\n appId,\n scriptName: deployment.scriptName,\n status:\n deployment.status === \"queued\" ||\n deployment.status === \"deploying\" ||\n deployment.status === \"succeeded\" ||\n deployment.status === \"failed\"\n ? deployment.status\n : \"failed\",\n format:\n deployment.format === SUPPORTED_DEPLOYMENT_FORMAT ? SUPPORTED_DEPLOYMENT_FORMAT : \"esmodule\",\n entrypoint: deployment.entrypoint,\n sourceByteLength: deployment.sourceByteLength,\n compatibilityDate: deployment.compatibilityDate,\n compatibilityFlags: normalizeStringList(deployment.compatibilityFlags),\n attemptCount: deployment.attemptCount,\n queuedAt: deployment.createdAt.toISOString(),\n startedAt: toIsoDateTime(deployment.startedAt),\n completedAt: toIsoDateTime(deployment.completedAt),\n errorCode: deployment.errorCode,\n errorMessage: deployment.errorMessage,\n cloudflare,\n createdAt: deployment.createdAt.toISOString(),\n updatedAt: deployment.updatedAt.toISOString(),\n };\n};\n\nconst buildDeploymentDetail = (\n deployment: DeploymentRow & { sourceCode?: string },\n appId: string,\n): CloudflareDeploymentDetail => {\n return {\n ...buildDeploymentSummary(deployment, appId),\n sourceCode: deployment.sourceCode ?? \"\",\n };\n};\n\nconst buildAppSummary = (\n app: AppRow,\n latestDeployment: DeploymentRow | null,\n): CloudflareAppSummary => {\n return {\n id: app.id.valueOf(),\n scriptName: app.scriptName,\n latestDeployment: latestDeployment\n ? buildDeploymentSummary(latestDeployment, app.id.valueOf())\n : null,\n createdAt: app.createdAt.toISOString(),\n updatedAt: app.updatedAt.toISOString(),\n };\n};\n\nconst formatDeployError = (error: unknown) => {\n const cloudflareError = getCloudflareApiError(error);\n if (cloudflareError) {\n return {\n code: cloudflareError.code,\n message: cloudflareError.message,\n };\n }\n\n if (error instanceof Error) {\n return {\n code: error.name || \"Error\",\n message: error.message,\n };\n }\n\n return {\n code: \"unknown\",\n message: String(error),\n };\n};\n\nconst readCloudflareSummary = (response: CloudflareScriptUpdateResponse) => {\n return {\n etag: response.etag ?? null,\n modifiedOn: response.modified_on ?? null,\n raw: response,\n };\n};\n\nconst createLivePointerGuard = (input: DeployWorkerHookInput): LivePointerGuard => {\n if (input.expectedLiveEtag !== null) {\n return {\n kind: \"etag\",\n expectedLiveEtag: input.expectedLiveEtag,\n };\n }\n\n return {\n kind: \"first-deploy\",\n leaseDeploymentId: input.deploymentId,\n };\n};\n\nconst canPromoteDeploymentToLive = (\n app: AppRow,\n deploymentId: string,\n livePointerGuard: LivePointerGuard,\n) => {\n if (livePointerGuard.kind === \"first-deploy\") {\n return (\n app.liveCloudflareEtag === null &&\n app.firstDeploymentLeaseId === livePointerGuard.leaseDeploymentId\n );\n }\n\n return (\n app.liveCloudflareEtag === livePointerGuard.expectedLiveEtag ||\n app.liveDeploymentId === deploymentId\n );\n};\n\nexport const cloudflareFragmentDefinition = defineFragment<CloudflareFragmentConfig>(\n \"cloudflare-fragment\",\n)\n .extend(withDatabase(cloudflareSchema))\n .withDependencies(({ config }) => {\n const dispatchNamespace = resolveCloudflareDispatchNamespaceName(\n resolveDispatcherConfig(config),\n );\n const cloudflare =\n \"cloudflare\" in config && config.cloudflare\n ? config.cloudflare\n : createCloudflareApiClient({\n apiToken: config.apiToken,\n fetchImplementation: config.fetchImplementation,\n });\n\n return {\n cloudflare,\n dispatchNamespace,\n };\n })\n .providesService(\"cloudflare\", ({ deps }) => ({\n getClient: () => deps.cloudflare,\n }))\n .provideHooks<CloudflareHooksMap>(({ defineHook, deps, config }) => ({\n deployWorker: defineHook(async function (input) {\n // Cloudflare CAS starts once the app has a live etag. The first deploy uses a local\n // lease so we still get compare-and-swap semantics before that point.\n const livePointerGuard = createLivePointerGuard(input);\n let remoteResult: DeployWorkerRemoteResult | null = null;\n let remoteError: unknown = null;\n let remoteUploadAttempted = false;\n\n const prepareFirstDeployment = async (): Promise<FirstDeploymentPreparation | null> => {\n if (livePointerGuard.kind !== \"first-deploy\") {\n return null;\n }\n\n return await this.handlerTx({ retryPolicy: hookWriteRetryPolicy })\n .retrieve(({ forSchema }) =>\n forSchema(cloudflareSchema).findFirst(\"deployment\", (b) =>\n b\n .whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.deploymentId))\n .join((j) => j.app()),\n ),\n )\n .mutate(({ forSchema, retrieveResult: [deployment] }) => {\n if (!deployment?.app) {\n return { action: \"missing\" as const };\n }\n\n const uow = forSchema(cloudflareSchema);\n const now = uow.now();\n const deploymentId = deployment.id.valueOf();\n const app = deployment.app;\n\n if (app.liveCloudflareEtag !== null) {\n if (app.firstDeploymentLeaseId === deploymentId) {\n uow.update(\"app\", app.id, (b) =>\n b\n .set({\n updatedAt: now,\n firstDeploymentLeaseId: null,\n })\n .check(),\n );\n }\n\n return {\n action: \"superseded\" as const,\n currentDeploymentId: app.liveDeploymentId,\n currentDeploymentTag: app.liveDeploymentId ?? \"current-live-deployment\",\n currentEtag: app.liveCloudflareEtag,\n currentModifiedOn: null,\n };\n }\n\n if (\n app.firstDeploymentLeaseId !== null &&\n app.firstDeploymentLeaseId !== deploymentId\n ) {\n return { action: \"retry\" as const };\n }\n\n if (app.firstDeploymentLeaseId !== deploymentId) {\n uow.update(\"app\", app.id, (b) =>\n b\n .set({\n updatedAt: now,\n firstDeploymentLeaseId: deploymentId,\n })\n .check(),\n );\n }\n\n return { action: \"continue\" as const };\n })\n .execute();\n };\n\n const firstDeploymentPreparation = await prepareFirstDeployment();\n if (firstDeploymentPreparation?.action === \"missing\") {\n return;\n }\n\n if (firstDeploymentPreparation?.action === \"retry\") {\n throw new Error(\n `Initial Cloudflare deploy for '${input.scriptName}' is already in progress.`,\n );\n }\n\n if (firstDeploymentPreparation?.action === \"superseded\") {\n remoteResult = firstDeploymentPreparation;\n }\n\n if (!remoteResult) {\n remoteUploadAttempted = true;\n\n try {\n remoteResult = await reconcileCloudflareWorkerDeployment(deps.cloudflare, {\n accountId: config.accountId,\n dispatchNamespace: deps.dispatchNamespace,\n appId: input.appId,\n deploymentId: input.deploymentId,\n expectedLiveEtag: input.expectedLiveEtag,\n deploymentTagPrefix: config.deploymentTagPrefix,\n scriptName: input.scriptName,\n entrypoint: input.entrypoint,\n moduleContent: input.moduleContent,\n compatibilityDate: input.compatibilityDate,\n compatibilityFlags: input.compatibilityFlags,\n scriptTags: config.scriptTags,\n });\n } catch (error) {\n remoteError = error;\n }\n }\n\n const remoteDeploymentId =\n remoteResult?.action === \"superseded\" || remoteResult?.action === \"already-deployed\"\n ? remoteResult.currentDeploymentId\n : null;\n const finalizeDeploymentStateTx =\n remoteDeploymentId && remoteDeploymentId !== input.deploymentId\n ? this.handlerTx({ retryPolicy: hookWriteRetryPolicy })\n .retrieve(({ forSchema }) =>\n forSchema(cloudflareSchema)\n .findFirst(\"deployment\", (b) =>\n b\n .whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.deploymentId))\n .join((j) => j.app()),\n )\n .findFirst(\"deployment\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", remoteDeploymentId)),\n ),\n )\n .transformRetrieve(([deployment, remoteDeployment]) => ({\n deployment,\n remoteDeployment: remoteDeployment ?? null,\n }))\n : this.handlerTx({ retryPolicy: hookWriteRetryPolicy })\n .retrieve(({ forSchema }) =>\n forSchema(cloudflareSchema).findFirst(\"deployment\", (b) =>\n b\n .whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.deploymentId))\n .join((j) => j.app()),\n ),\n )\n .transformRetrieve(([deployment]) => ({\n deployment,\n remoteDeployment: null,\n }));\n\n await finalizeDeploymentStateTx\n .mutate(({ forSchema, retrieveResult: { deployment, remoteDeployment } }) => {\n if (!deployment || !deployment.app) {\n return;\n }\n\n const uow = forSchema(cloudflareSchema);\n const now = uow.now();\n const appId = deployment.app.id;\n const deploymentId = deployment.id.valueOf();\n const ownsFirstDeploymentLease = deployment.app.firstDeploymentLeaseId === deploymentId;\n const updateApp = (\n changes: Partial<{\n liveDeploymentId: string | null;\n liveCloudflareEtag: string | null;\n }> = {},\n ) => {\n uow.update(\"app\", appId, (b) =>\n b\n .set({\n updatedAt: now,\n ...changes,\n ...(ownsFirstDeploymentLease ? { firstDeploymentLeaseId: null } : {}),\n })\n .check(),\n );\n };\n\n if (remoteResult?.action === \"already-deployed\") {\n const etag =\n remoteResult.currentEtag ??\n deployment.cloudflareEtag ??\n (deployment.app.liveDeploymentId === deploymentId\n ? deployment.app.liveCloudflareEtag\n : null);\n const modifiedOn = remoteResult.currentModifiedOn ?? deployment.cloudflareModifiedOn;\n\n uow.update(\"deployment\", deployment.id, (b) =>\n b\n .set({\n status: \"succeeded\",\n updatedAt: now,\n startedAt: deployment.startedAt ?? now,\n completedAt: deployment.completedAt ?? now,\n attemptCount: Math.max(deployment.attemptCount, 1),\n errorCode: null,\n errorMessage: null,\n cloudflareEtag: etag,\n cloudflareModifiedOn: modifiedOn,\n })\n .check(),\n );\n updateApp({\n liveDeploymentId: deploymentId,\n liveCloudflareEtag: etag,\n });\n return;\n }\n\n if (remoteResult?.action === \"uploaded\") {\n const cloudflare = readCloudflareSummary(remoteResult.response);\n const canMoveLivePointer = canPromoteDeploymentToLive(\n deployment.app,\n deploymentId,\n livePointerGuard,\n );\n\n uow.update(\"deployment\", deployment.id, (b) =>\n b\n .set({\n status: \"succeeded\",\n updatedAt: now,\n startedAt: deployment.startedAt ?? now,\n completedAt: now,\n attemptCount: deployment.attemptCount + 1,\n errorCode: null,\n errorMessage: null,\n cloudflareEtag: cloudflare.etag,\n cloudflareModifiedOn: cloudflare.modifiedOn,\n cloudflareResponse: cloudflare.raw,\n })\n .check(),\n );\n updateApp(\n canMoveLivePointer\n ? {\n liveDeploymentId: deploymentId,\n liveCloudflareEtag: cloudflare.etag,\n }\n : {},\n );\n return;\n }\n\n if (remoteResult?.action === \"superseded\") {\n const supersededBy =\n remoteDeployment?.id.valueOf() ??\n remoteResult.currentDeploymentId ??\n remoteResult.currentDeploymentTag;\n const winnerEtag =\n remoteResult.currentEtag ??\n remoteDeployment?.cloudflareEtag ??\n (deployment.app.liveDeploymentId === remoteResult.currentDeploymentId\n ? deployment.app.liveCloudflareEtag\n : null);\n const winnerModifiedOn =\n remoteResult.currentModifiedOn ?? remoteDeployment?.cloudflareModifiedOn ?? null;\n\n if (remoteDeployment) {\n uow.update(\"deployment\", remoteDeployment.id, (b) =>\n b\n .set({\n status: \"succeeded\",\n updatedAt: now,\n startedAt: remoteDeployment.startedAt ?? now,\n completedAt: remoteDeployment.completedAt ?? now,\n attemptCount: Math.max(remoteDeployment.attemptCount, 1),\n errorCode: null,\n errorMessage: null,\n cloudflareEtag: winnerEtag,\n cloudflareModifiedOn: winnerModifiedOn,\n })\n .check(),\n );\n }\n\n updateApp({\n liveDeploymentId: remoteResult.currentDeploymentId,\n liveCloudflareEtag: winnerEtag,\n });\n\n if (deployment.status === \"succeeded\") {\n return;\n }\n\n uow.update(\"deployment\", deployment.id, (b) =>\n b\n .set({\n status: \"failed\",\n updatedAt: now,\n startedAt: deployment.startedAt ?? now,\n completedAt: now,\n attemptCount: deployment.attemptCount + (remoteUploadAttempted ? 1 : 0),\n errorCode: \"DEPLOYMENT_SUPERSEDED\",\n errorMessage: `Deployment was superseded by '${supersededBy}'.`,\n })\n .check(),\n );\n return;\n }\n\n if (deployment.status === \"succeeded\") {\n updateApp();\n return;\n }\n\n const formatted = formatDeployError(remoteError);\n\n uow.update(\"deployment\", deployment.id, (b) =>\n b\n .set({\n status: \"failed\",\n updatedAt: now,\n startedAt: deployment.startedAt ?? now,\n completedAt: now,\n attemptCount: deployment.attemptCount + (remoteUploadAttempted ? 1 : 0),\n errorCode: formatted.code,\n errorMessage: formatted.message,\n })\n .check(),\n );\n updateApp();\n })\n .execute();\n }),\n }))\n .providesBaseService(({ defineService, config }) => {\n return defineService({\n upsertApp: function (appId: string) {\n return this.serviceTx(cloudflareSchema)\n .retrieve((uow) =>\n uow.findFirst(\"app\", (b) => b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", appId))),\n )\n .mutate(({ uow, retrieveResult: [existingApp] }) => {\n if (existingApp) {\n return buildAppSummary(existingApp, null);\n }\n\n const createdAt = new Date();\n const scriptName = resolveCloudflareScriptName(appId, config);\n const createdId = uow.create(\"app\", {\n id: appId,\n scriptName,\n liveDeploymentId: null,\n liveCloudflareEtag: null,\n firstDeploymentLeaseId: null,\n createdAt,\n updatedAt: createdAt,\n });\n\n return {\n id: createdId.valueOf(),\n scriptName,\n latestDeployment: null,\n createdAt: createdAt.toISOString(),\n updatedAt: createdAt.toISOString(),\n };\n })\n .build();\n },\n queueDeployment: function (appId: string, request: CloudflareDeployRequest) {\n return this.serviceTx(cloudflareSchema)\n .retrieve((uow) =>\n uow.findFirst(\"app\", (b) => b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", appId))),\n )\n .mutate(({ uow, retrieveResult: [existingApp] }) => {\n const now = new Date();\n const compatibilityDate = request.compatibilityDate ?? config.compatibilityDate;\n const compatibilityFlags = normalizeStringList(\n request.compatibilityFlags ?? config.compatibilityFlags,\n );\n const scriptName =\n existingApp?.scriptName ?? resolveCloudflareScriptName(appId, config);\n const sourceByteLength = textEncoder.encode(request.script.content).byteLength;\n const ensuredAppId =\n existingApp?.id ??\n uow.create(\"app\", {\n id: appId,\n scriptName,\n liveDeploymentId: null,\n liveCloudflareEtag: null,\n firstDeploymentLeaseId: null,\n createdAt: now,\n updatedAt: now,\n });\n\n if (existingApp) {\n uow.update(\"app\", existingApp.id, (b) => b.set({ updatedAt: now }).check());\n }\n\n const deploymentId = uow.create(\"deployment\", {\n appId: ensuredAppId,\n status: \"queued\",\n format: SUPPORTED_DEPLOYMENT_FORMAT,\n entrypoint: request.script.entrypoint,\n scriptName,\n sourceCode: request.script.content,\n sourceByteLength,\n compatibilityDate,\n compatibilityFlags,\n attemptCount: 0,\n startedAt: null,\n completedAt: null,\n errorCode: null,\n errorMessage: null,\n cloudflareEtag: null,\n cloudflareModifiedOn: null,\n cloudflareResponse: null,\n createdAt: now,\n updatedAt: now,\n });\n const deploymentIdValue = deploymentId.valueOf();\n const expectedLiveEtag = existingApp?.liveCloudflareEtag ?? null;\n\n // The hook payload carries the immutable deploy snapshot plus the live etag it was\n // queued against so Cloudflare can enforce compare-and-swap updates for us.\n uow.triggerHook(\"deployWorker\", {\n deploymentId: deploymentIdValue,\n appId: ensuredAppId.valueOf(),\n expectedLiveEtag,\n scriptName,\n entrypoint: request.script.entrypoint,\n moduleContent: request.script.content,\n compatibilityDate,\n compatibilityFlags,\n });\n\n return {\n id: deploymentIdValue,\n appId: ensuredAppId.valueOf(),\n scriptName,\n status: \"queued\" as const,\n format: SUPPORTED_DEPLOYMENT_FORMAT as \"esmodule\",\n entrypoint: request.script.entrypoint,\n sourceByteLength,\n compatibilityDate,\n compatibilityFlags,\n attemptCount: 0,\n queuedAt: now.toISOString(),\n startedAt: null,\n completedAt: null,\n errorCode: null,\n errorMessage: null,\n cloudflare: null,\n createdAt: now.toISOString(),\n updatedAt: now.toISOString(),\n };\n })\n .build();\n },\n getDeployment: function (deploymentId: string) {\n return this.serviceTx(cloudflareSchema)\n .retrieve((uow) =>\n uow.findFirst(\"deployment\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", deploymentId)).join((j) => j.app()),\n ),\n )\n .transformRetrieve(([deployment]) => {\n return deployment?.app\n ? buildDeploymentDetail(deployment, deployment.app.id.valueOf())\n : null;\n })\n .build();\n },\n getAppState: function (appId: string) {\n return this.serviceTx(cloudflareSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"app\", (b) => b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", appId)))\n .findFirst(\"deployment\", (b) =>\n b\n .whereIndex(\"idx_deployment_app_createdAt\", (eb) => eb(\"appId\", \"=\", appId))\n .orderByIndex(\"idx_deployment_app_createdAt\", \"desc\"),\n ),\n )\n .transformRetrieve(([app, latestDeployment]) => {\n return app\n ? {\n ...buildAppSummary(app, latestDeployment),\n liveDeploymentId: app.liveDeploymentId,\n liveCloudflareEtag: app.liveCloudflareEtag,\n }\n : null;\n })\n .build();\n },\n listApps: function () {\n return this.serviceTx(cloudflareSchema)\n .retrieve((uow) =>\n uow\n .find(\"app\", (b) => b.whereIndex(\"primary\"))\n .find(\"deployment\", (b) => b.whereIndex(\"primary\").join((j) => j.app())),\n )\n .transformRetrieve(([apps, deployments]) => {\n const latestDeploymentsByAppId = new Map<string, (typeof deployments)[number]>();\n\n for (const deployment of deployments) {\n if (!deployment.app) {\n continue;\n }\n\n const deploymentAppId = deployment.app.id.valueOf();\n const existingDeployment = latestDeploymentsByAppId.get(deploymentAppId);\n\n if (\n !existingDeployment ||\n deployment.createdAt.getTime() > existingDeployment.createdAt.getTime()\n ) {\n latestDeploymentsByAppId.set(deploymentAppId, deployment);\n }\n }\n\n return apps\n .map((app) =>\n buildAppSummary(app, latestDeploymentsByAppId.get(app.id.valueOf()) ?? null),\n )\n .sort((left, right) => {\n const updatedAtCompare = right.updatedAt.localeCompare(left.updatedAt);\n if (updatedAtCompare !== 0) {\n return updatedAtCompare;\n }\n\n return left.id.localeCompare(right.id);\n });\n })\n .build();\n },\n listAppDeployments: function (appId: string) {\n return this.serviceTx(cloudflareSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"app\", (b) => b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", appId)))\n .find(\"deployment\", (b) =>\n b\n .whereIndex(\"idx_deployment_app_createdAt\", (eb) => eb(\"appId\", \"=\", appId))\n .orderByIndex(\"idx_deployment_app_createdAt\", \"desc\"),\n ),\n )\n .transformRetrieve(([app, deployments]) => {\n if (!app) {\n return null;\n }\n\n return deployments.map((deployment) =>\n buildDeploymentSummary(deployment, app.id.valueOf()),\n );\n })\n .build();\n },\n });\n })\n .build();\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/deployment-tag.d.ts
|
|
2
|
+
declare const DEFAULT_DEPLOYMENT_TAG_PREFIX = "fragno";
|
|
3
|
+
declare const sanitizeCloudflareTag: (value: string) => string;
|
|
4
|
+
declare const normalizeCloudflareDeploymentTagPrefix: (prefix?: string) => string;
|
|
5
|
+
declare const buildCloudflareAppTag: (appId: string, prefix?: string) => string;
|
|
6
|
+
declare const buildCloudflareDeploymentTag: (deploymentId: string, prefix?: string) => string;
|
|
7
|
+
declare const getCloudflareAppIdFromTag: (tag: string, prefix?: string) => string | null;
|
|
8
|
+
declare const getCloudflareDeploymentIdFromTag: (tag: string, prefix?: string) => string | null;
|
|
9
|
+
declare const findCloudflareAppTag: (tags: string[], prefix?: string) => string | null;
|
|
10
|
+
declare const findCloudflareDeploymentTag: (tags: string[], prefix?: string) => string | null;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { DEFAULT_DEPLOYMENT_TAG_PREFIX, buildCloudflareAppTag, buildCloudflareDeploymentTag, findCloudflareAppTag, findCloudflareDeploymentTag, getCloudflareAppIdFromTag, getCloudflareDeploymentIdFromTag, normalizeCloudflareDeploymentTagPrefix, sanitizeCloudflareTag };
|
|
13
|
+
//# sourceMappingURL=deployment-tag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-tag.d.ts","names":[],"sources":["../../src/deployment-tag.ts"],"mappings":";cAAa,6BAAA;AAAA,cAMA,qBAAA,GAAyB,KAAA;AAAA,cASzB,sCAAA,GAA0C,MAAA;AAAA,cA2G1C,qBAAA,GAAyB,KAAA,UAAe,MAAA;AAAA,cAIxC,4BAAA,GAAgC,YAAA,UAAsB,MAAA;AAAA,cAItD,yBAAA,GAA6B,GAAA,UAAa,MAAA;AAAA,cAI1C,gCAAA,GAAoC,GAAA,UAAa,MAAA;AAAA,cAIjD,oBAAA,GAAwB,IAAA,YAAgB,MAAA;AAAA,cAIxC,2BAAA,GAA+B,IAAA,YAAgB,MAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/deployment-tag.ts
|
|
2
|
+
const DEFAULT_DEPLOYMENT_TAG_PREFIX = "fragno";
|
|
3
|
+
const MAX_TAG_LENGTH = 63;
|
|
4
|
+
const APP_TAG_SEGMENT = "app";
|
|
5
|
+
const DEPLOYMENT_TAG_SEGMENT = "dep";
|
|
6
|
+
const sanitizeCloudflareTag = (value) => {
|
|
7
|
+
return value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+/, "").replace(/-+$/, "").replace(/--+/g, "-");
|
|
8
|
+
};
|
|
9
|
+
const normalizeCloudflareDeploymentTagPrefix = (prefix) => {
|
|
10
|
+
const normalized = sanitizeCloudflareTag(prefix ?? DEFAULT_DEPLOYMENT_TAG_PREFIX);
|
|
11
|
+
if (!normalized) throw new Error("Cloudflare deployment tag prefix must contain at least one alphanumeric character.");
|
|
12
|
+
return normalized;
|
|
13
|
+
};
|
|
14
|
+
const trimTrailingHyphens = (value) => value.replace(/-+$/, "");
|
|
15
|
+
const capCloudflareTagPrefix = (prefix, kind, normalizedId, label) => {
|
|
16
|
+
const maxPrefixLength = MAX_TAG_LENGTH - `-${kind}-`.length - normalizedId.length;
|
|
17
|
+
if (maxPrefixLength < 1) throw new Error(`Cloudflare ${label} tag '${normalizedId}' exceeds the ${MAX_TAG_LENGTH} character limit even with the shortest possible prefix.`);
|
|
18
|
+
const cappedPrefix = trimTrailingHyphens(prefix.slice(0, maxPrefixLength));
|
|
19
|
+
if (!cappedPrefix) throw new Error(`Cloudflare ${label} tag prefix must retain at least one alphanumeric character after capping.`);
|
|
20
|
+
return cappedPrefix;
|
|
21
|
+
};
|
|
22
|
+
const buildCloudflareScopedTag = (id, prefix, kind, label) => {
|
|
23
|
+
const normalizedPrefix = normalizeCloudflareDeploymentTagPrefix(prefix);
|
|
24
|
+
const normalizedId = sanitizeCloudflareTag(id);
|
|
25
|
+
if (!normalizedId) throw new Error(`Cloudflare ${label} id must contain at least one alphanumeric character.`);
|
|
26
|
+
return `${capCloudflareTagPrefix(normalizedPrefix, kind, normalizedId, label)}-${kind}-${normalizedId}`;
|
|
27
|
+
};
|
|
28
|
+
const readCloudflareScopedTagId = (tag, prefix, kind, label) => {
|
|
29
|
+
const normalizedTag = sanitizeCloudflareTag(tag);
|
|
30
|
+
const marker = `-${kind}-`;
|
|
31
|
+
const normalizedPrefix = normalizeCloudflareDeploymentTagPrefix(prefix);
|
|
32
|
+
let markerIndex = normalizedTag.indexOf(marker);
|
|
33
|
+
while (markerIndex >= 1) {
|
|
34
|
+
const normalizedId = normalizedTag.slice(markerIndex + marker.length);
|
|
35
|
+
if (normalizedId) try {
|
|
36
|
+
const expectedPrefix = capCloudflareTagPrefix(normalizedPrefix, kind, normalizedId, label);
|
|
37
|
+
if (normalizedTag.slice(0, markerIndex) === expectedPrefix) return normalizedId;
|
|
38
|
+
} catch {}
|
|
39
|
+
markerIndex = normalizedTag.indexOf(marker, markerIndex + 1);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
};
|
|
43
|
+
const findCloudflareScopedTag = (tags, prefix, kind, label) => {
|
|
44
|
+
const marker = `-${kind}-`;
|
|
45
|
+
for (const tag of tags) {
|
|
46
|
+
const normalizedTag = sanitizeCloudflareTag(tag);
|
|
47
|
+
if (!normalizedTag.includes(marker)) continue;
|
|
48
|
+
if (readCloudflareScopedTagId(normalizedTag, prefix, kind, label) !== null) return normalizedTag;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
const buildCloudflareAppTag = (appId, prefix) => {
|
|
53
|
+
return buildCloudflareScopedTag(appId, prefix, APP_TAG_SEGMENT, "app");
|
|
54
|
+
};
|
|
55
|
+
const buildCloudflareDeploymentTag = (deploymentId, prefix) => {
|
|
56
|
+
return buildCloudflareScopedTag(deploymentId, prefix, DEPLOYMENT_TAG_SEGMENT, "deployment");
|
|
57
|
+
};
|
|
58
|
+
const getCloudflareAppIdFromTag = (tag, prefix) => {
|
|
59
|
+
return readCloudflareScopedTagId(tag, prefix, APP_TAG_SEGMENT, "app");
|
|
60
|
+
};
|
|
61
|
+
const getCloudflareDeploymentIdFromTag = (tag, prefix) => {
|
|
62
|
+
return readCloudflareScopedTagId(tag, prefix, DEPLOYMENT_TAG_SEGMENT, "deployment");
|
|
63
|
+
};
|
|
64
|
+
const findCloudflareAppTag = (tags, prefix) => {
|
|
65
|
+
return findCloudflareScopedTag(tags, prefix, APP_TAG_SEGMENT, "app");
|
|
66
|
+
};
|
|
67
|
+
const findCloudflareDeploymentTag = (tags, prefix) => {
|
|
68
|
+
return findCloudflareScopedTag(tags, prefix, DEPLOYMENT_TAG_SEGMENT, "deployment");
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { DEFAULT_DEPLOYMENT_TAG_PREFIX, buildCloudflareAppTag, buildCloudflareDeploymentTag, findCloudflareAppTag, findCloudflareDeploymentTag, getCloudflareAppIdFromTag, getCloudflareDeploymentIdFromTag, normalizeCloudflareDeploymentTagPrefix, sanitizeCloudflareTag };
|
|
73
|
+
//# sourceMappingURL=deployment-tag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-tag.js","names":[],"sources":["../../src/deployment-tag.ts"],"sourcesContent":["export const DEFAULT_DEPLOYMENT_TAG_PREFIX = \"fragno\";\n\nconst MAX_TAG_LENGTH = 63;\nconst APP_TAG_SEGMENT = \"app\";\nconst DEPLOYMENT_TAG_SEGMENT = \"dep\";\n\nexport const sanitizeCloudflareTag = (value: string) => {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/^-+/, \"\")\n .replace(/-+$/, \"\")\n .replace(/--+/g, \"-\");\n};\n\nexport const normalizeCloudflareDeploymentTagPrefix = (prefix?: string) => {\n const normalized = sanitizeCloudflareTag(prefix ?? DEFAULT_DEPLOYMENT_TAG_PREFIX);\n if (!normalized) {\n throw new Error(\n \"Cloudflare deployment tag prefix must contain at least one alphanumeric character.\",\n );\n }\n\n return normalized;\n};\n\nconst trimTrailingHyphens = (value: string) => value.replace(/-+$/, \"\");\n\nconst capCloudflareTagPrefix = (\n prefix: string,\n kind: string,\n normalizedId: string,\n label: string,\n) => {\n const maxPrefixLength = MAX_TAG_LENGTH - `-${kind}-`.length - normalizedId.length;\n if (maxPrefixLength < 1) {\n throw new Error(\n `Cloudflare ${label} tag '${normalizedId}' exceeds the ${MAX_TAG_LENGTH} character limit even with the shortest possible prefix.`,\n );\n }\n\n const cappedPrefix = trimTrailingHyphens(prefix.slice(0, maxPrefixLength));\n if (!cappedPrefix) {\n throw new Error(\n `Cloudflare ${label} tag prefix must retain at least one alphanumeric character after capping.`,\n );\n }\n\n return cappedPrefix;\n};\n\nconst buildCloudflareScopedTag = (\n id: string,\n prefix: string | undefined,\n kind: string,\n label: string,\n) => {\n const normalizedPrefix = normalizeCloudflareDeploymentTagPrefix(prefix);\n const normalizedId = sanitizeCloudflareTag(id);\n\n if (!normalizedId) {\n throw new Error(`Cloudflare ${label} id must contain at least one alphanumeric character.`);\n }\n\n const cappedPrefix = capCloudflareTagPrefix(normalizedPrefix, kind, normalizedId, label);\n return `${cappedPrefix}-${kind}-${normalizedId}`;\n};\n\nconst readCloudflareScopedTagId = (\n tag: string,\n prefix: string | undefined,\n kind: string,\n label: string,\n) => {\n const normalizedTag = sanitizeCloudflareTag(tag);\n const marker = `-${kind}-`;\n const normalizedPrefix = normalizeCloudflareDeploymentTagPrefix(prefix);\n let markerIndex = normalizedTag.indexOf(marker);\n\n while (markerIndex >= 1) {\n const normalizedId = normalizedTag.slice(markerIndex + marker.length);\n if (normalizedId) {\n try {\n const expectedPrefix = capCloudflareTagPrefix(normalizedPrefix, kind, normalizedId, label);\n const tagPrefix = normalizedTag.slice(0, markerIndex);\n if (tagPrefix === expectedPrefix) {\n return normalizedId;\n }\n } catch {\n // Ignore unrelated tags that happen to contain the same marker.\n }\n }\n\n markerIndex = normalizedTag.indexOf(marker, markerIndex + 1);\n }\n\n return null;\n};\n\nconst findCloudflareScopedTag = (\n tags: string[],\n prefix: string | undefined,\n kind: string,\n label: string,\n) => {\n const marker = `-${kind}-`;\n\n for (const tag of tags) {\n const normalizedTag = sanitizeCloudflareTag(tag);\n if (!normalizedTag.includes(marker)) {\n continue;\n }\n\n const id = readCloudflareScopedTagId(normalizedTag, prefix, kind, label);\n if (id !== null) {\n return normalizedTag;\n }\n }\n\n return null;\n};\n\nexport const buildCloudflareAppTag = (appId: string, prefix?: string) => {\n return buildCloudflareScopedTag(appId, prefix, APP_TAG_SEGMENT, \"app\");\n};\n\nexport const buildCloudflareDeploymentTag = (deploymentId: string, prefix?: string) => {\n return buildCloudflareScopedTag(deploymentId, prefix, DEPLOYMENT_TAG_SEGMENT, \"deployment\");\n};\n\nexport const getCloudflareAppIdFromTag = (tag: string, prefix?: string) => {\n return readCloudflareScopedTagId(tag, prefix, APP_TAG_SEGMENT, \"app\");\n};\n\nexport const getCloudflareDeploymentIdFromTag = (tag: string, prefix?: string) => {\n return readCloudflareScopedTagId(tag, prefix, DEPLOYMENT_TAG_SEGMENT, \"deployment\");\n};\n\nexport const findCloudflareAppTag = (tags: string[], prefix?: string) => {\n return findCloudflareScopedTag(tags, prefix, APP_TAG_SEGMENT, \"app\");\n};\n\nexport const findCloudflareDeploymentTag = (tags: string[], prefix?: string) => {\n return findCloudflareScopedTag(tags, prefix, DEPLOYMENT_TAG_SEGMENT, \"deployment\");\n};\n"],"mappings":";AAAA,MAAa,gCAAgC;AAE7C,MAAM,iBAAiB;AACvB,MAAM,kBAAkB;AACxB,MAAM,yBAAyB;AAE/B,MAAa,yBAAyB,UAAkB;AACtD,QAAO,MACJ,aAAa,CACb,QAAQ,gBAAgB,IAAI,CAC5B,QAAQ,OAAO,GAAG,CAClB,QAAQ,OAAO,GAAG,CAClB,QAAQ,QAAQ,IAAI;;AAGzB,MAAa,0CAA0C,WAAoB;CACzE,MAAM,aAAa,sBAAsB,UAAU,8BAA8B;AACjF,KAAI,CAAC,WACH,OAAM,IAAI,MACR,qFACD;AAGH,QAAO;;AAGT,MAAM,uBAAuB,UAAkB,MAAM,QAAQ,OAAO,GAAG;AAEvE,MAAM,0BACJ,QACA,MACA,cACA,UACG;CACH,MAAM,kBAAkB,iBAAiB,IAAI,KAAK,GAAG,SAAS,aAAa;AAC3E,KAAI,kBAAkB,EACpB,OAAM,IAAI,MACR,cAAc,MAAM,QAAQ,aAAa,gBAAgB,eAAe,0DACzE;CAGH,MAAM,eAAe,oBAAoB,OAAO,MAAM,GAAG,gBAAgB,CAAC;AAC1E,KAAI,CAAC,aACH,OAAM,IAAI,MACR,cAAc,MAAM,4EACrB;AAGH,QAAO;;AAGT,MAAM,4BACJ,IACA,QACA,MACA,UACG;CACH,MAAM,mBAAmB,uCAAuC,OAAO;CACvE,MAAM,eAAe,sBAAsB,GAAG;AAE9C,KAAI,CAAC,aACH,OAAM,IAAI,MAAM,cAAc,MAAM,uDAAuD;AAI7F,QAAO,GADc,uBAAuB,kBAAkB,MAAM,cAAc,MAAM,CACjE,GAAG,KAAK,GAAG;;AAGpC,MAAM,6BACJ,KACA,QACA,MACA,UACG;CACH,MAAM,gBAAgB,sBAAsB,IAAI;CAChD,MAAM,SAAS,IAAI,KAAK;CACxB,MAAM,mBAAmB,uCAAuC,OAAO;CACvE,IAAI,cAAc,cAAc,QAAQ,OAAO;AAE/C,QAAO,eAAe,GAAG;EACvB,MAAM,eAAe,cAAc,MAAM,cAAc,OAAO,OAAO;AACrE,MAAI,aACF,KAAI;GACF,MAAM,iBAAiB,uBAAuB,kBAAkB,MAAM,cAAc,MAAM;AAE1F,OADkB,cAAc,MAAM,GAAG,YAAY,KACnC,eAChB,QAAO;UAEH;AAKV,gBAAc,cAAc,QAAQ,QAAQ,cAAc,EAAE;;AAG9D,QAAO;;AAGT,MAAM,2BACJ,MACA,QACA,MACA,UACG;CACH,MAAM,SAAS,IAAI,KAAK;AAExB,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,gBAAgB,sBAAsB,IAAI;AAChD,MAAI,CAAC,cAAc,SAAS,OAAO,CACjC;AAIF,MADW,0BAA0B,eAAe,QAAQ,MAAM,MAAM,KAC7D,KACT,QAAO;;AAIX,QAAO;;AAGT,MAAa,yBAAyB,OAAe,WAAoB;AACvE,QAAO,yBAAyB,OAAO,QAAQ,iBAAiB,MAAM;;AAGxE,MAAa,gCAAgC,cAAsB,WAAoB;AACrF,QAAO,yBAAyB,cAAc,QAAQ,wBAAwB,aAAa;;AAG7F,MAAa,6BAA6B,KAAa,WAAoB;AACzE,QAAO,0BAA0B,KAAK,QAAQ,iBAAiB,MAAM;;AAGvE,MAAa,oCAAoC,KAAa,WAAoB;AAChF,QAAO,0BAA0B,KAAK,QAAQ,wBAAwB,aAAa;;AAGrF,MAAa,wBAAwB,MAAgB,WAAoB;AACvE,QAAO,wBAAwB,MAAM,QAAQ,iBAAiB,MAAM;;AAGtE,MAAa,+BAA+B,MAAgB,WAAoB;AAC9E,QAAO,wBAAwB,MAAM,QAAQ,wBAAwB,aAAa"}
|