alchemy 0.37.2 → 0.38.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/bin/alchemy.mjs +44306 -48055
- package/bin/alchemy.ts +80 -85
- package/bin/commands/create.ts +263 -0
- package/bin/constants.ts +13 -0
- package/bin/errors.ts +11 -0
- package/bin/services/dependencies.ts +47 -0
- package/bin/services/get-package-version.ts +11 -0
- package/bin/services/package-manager.ts +113 -0
- package/bin/services/template-manager.ts +137 -0
- package/bin/types.ts +83 -0
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +12 -3
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.d.ts +2 -0
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +47 -12
- package/lib/apply.js.map +1 -1
- package/lib/build-date.d.ts +6 -0
- package/lib/build-date.d.ts.map +1 -0
- package/lib/build-date.js +8 -0
- package/lib/build-date.js.map +1 -0
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +2 -1
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/do-state-store/internal.js +5 -3
- package/lib/cloudflare/do-state-store/internal.js.map +1 -1
- package/lib/cloudflare/do-state-store/worker.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/worker.js +6 -1
- package/lib/cloudflare/do-state-store/worker.js.map +1 -1
- package/lib/cloudflare/pipeline.d.ts +1 -1
- package/lib/cloudflare/pipeline.d.ts.map +1 -1
- package/lib/cloudflare/pipeline.js +6 -1
- package/lib/cloudflare/pipeline.js.map +1 -1
- package/lib/cloudflare/worker-stub.d.ts +23 -2
- package/lib/cloudflare/worker-stub.d.ts.map +1 -1
- package/lib/cloudflare/worker-stub.js +16 -2
- package/lib/cloudflare/worker-stub.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +5 -4
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +4 -7
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/context.d.ts +2 -2
- package/lib/context.d.ts.map +1 -1
- package/lib/destroy.d.ts +8 -2
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +19 -5
- package/lib/destroy.js.map +1 -1
- package/lib/scope.d.ts +26 -4
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +104 -9
- package/lib/scope.js.map +1 -1
- package/lib/sentry/project.js +1 -1
- package/lib/sentry/project.js.map +1 -1
- package/lib/util/logger.js +1 -1
- package/lib/util/logger.js.map +1 -1
- package/lib/util/mutex.d.ts +9 -0
- package/lib/util/mutex.d.ts.map +1 -0
- package/lib/util/mutex.js +35 -0
- package/lib/util/mutex.js.map +1 -0
- package/package.json +16 -6
- package/src/alchemy.ts +17 -5
- package/src/apply.ts +67 -17
- package/src/build-date.ts +8 -0
- package/src/cloudflare/api.ts +2 -1
- package/src/cloudflare/do-state-store/internal.ts +5 -3
- package/src/cloudflare/do-state-store/worker.ts +13 -3
- package/src/cloudflare/pipeline.ts +6 -0
- package/src/cloudflare/worker-stub.ts +31 -2
- package/src/cloudflare/worker.ts +6 -15
- package/src/context.ts +2 -2
- package/src/destroy.ts +30 -7
- package/src/scope.ts +145 -10
- package/src/sentry/project.ts +1 -1
- package/src/util/logger.ts +1 -1
- package/src/util/mutex.ts +33 -0
- package/templates/astro/.vscode/extensions.json +4 -0
- package/templates/astro/.vscode/launch.json +11 -0
- package/templates/astro/README.md +48 -0
- package/templates/astro/_env.example +1 -0
- package/templates/astro/_gitignore +27 -0
- package/templates/astro/alchemy.run.ts +16 -0
- package/templates/astro/astro.config.mjs +8 -0
- package/templates/astro/package.json +23 -0
- package/templates/astro/public/favicon.svg +9 -0
- package/templates/astro/src/assets/astro.svg +1 -0
- package/templates/astro/src/assets/background.svg +1 -0
- package/templates/astro/src/components/Welcome.astro +210 -0
- package/templates/astro/src/layouts/Layout.astro +22 -0
- package/templates/astro/src/pages/api/hello.ts +19 -0
- package/templates/astro/src/pages/index.astro +11 -0
- package/templates/astro/tsconfig.json +18 -0
- package/templates/astro/types/env.d.ts +16 -0
- package/templates/astro/wrangler.jsonc +6 -0
- package/templates/nuxt/README.md +75 -0
- package/templates/nuxt/_env +1 -0
- package/templates/nuxt/_env.example +1 -0
- package/templates/nuxt/_gitignore +27 -0
- package/templates/nuxt/alchemy.run.ts +16 -0
- package/templates/nuxt/app.vue +6 -0
- package/templates/nuxt/nuxt.config.ts +13 -0
- package/templates/nuxt/package.json +26 -0
- package/templates/nuxt/public/favicon.ico +0 -0
- package/templates/nuxt/public/robots.txt +2 -0
- package/templates/nuxt/server/api/hello.ts +7 -0
- package/templates/nuxt/server/middleware/auth.ts +5 -0
- package/templates/nuxt/server/middleware/hello.ts +5 -0
- package/templates/nuxt/server/tsconfig.json +3 -0
- package/templates/nuxt/tsconfig.json +15 -0
- package/templates/nuxt/types/env.d.ts +16 -0
- package/templates/react-router/.vscode/settings.json +5 -0
- package/templates/react-router/README.md +79 -0
- package/templates/react-router/_env +1 -0
- package/templates/react-router/_env.example +1 -0
- package/templates/react-router/_gitignore +15 -0
- package/templates/react-router/alchemy.run.ts +17 -0
- package/templates/react-router/app/app.css +15 -0
- package/templates/react-router/app/entry.server.tsx +43 -0
- package/templates/react-router/app/root.tsx +75 -0
- package/templates/react-router/app/routes/home.tsx +17 -0
- package/templates/react-router/app/routes.ts +3 -0
- package/templates/react-router/app/welcome/logo-dark.svg +23 -0
- package/templates/react-router/app/welcome/logo-light.svg +23 -0
- package/templates/react-router/app/welcome/welcome.tsx +90 -0
- package/templates/react-router/package.json +34 -0
- package/templates/react-router/public/favicon.ico +0 -0
- package/templates/react-router/react-router.config.ts +8 -0
- package/templates/react-router/tsconfig.cloudflare.json +27 -0
- package/templates/react-router/tsconfig.json +17 -0
- package/templates/react-router/tsconfig.node.json +20 -0
- package/templates/react-router/types/env.d.ts +16 -0
- package/templates/react-router/vite.config.ts +14 -0
- package/templates/react-router/workers/app.ts +23 -0
- package/templates/react-router/wrangler.jsonc +6 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-interruptors.mdc +247 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-middleware.mdc +75 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-react.mdc +110 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-request-response.mdc +213 -0
- package/templates/rwsdk/.devcontainer/Dockerfile +8 -0
- package/templates/rwsdk/.devcontainer/devcontainer.json +21 -0
- package/templates/rwsdk/README.md +62 -0
- package/templates/rwsdk/_env +5 -0
- package/templates/rwsdk/_env.example +5 -0
- package/templates/rwsdk/_gitignore +71 -0
- package/templates/rwsdk/alchemy.run.ts +29 -0
- package/templates/rwsdk/package.json +61 -0
- package/templates/rwsdk/prisma/schema.prisma +43 -0
- package/templates/rwsdk/src/app/Document.tsx +16 -0
- package/templates/rwsdk/src/app/headers.ts +32 -0
- package/templates/rwsdk/src/app/pages/Home.tsx +13 -0
- package/templates/rwsdk/src/app/pages/user/Login.tsx +79 -0
- package/templates/rwsdk/src/app/pages/user/functions.ts +171 -0
- package/templates/rwsdk/src/app/pages/user/routes.ts +17 -0
- package/templates/rwsdk/src/app/shared/links.ts +3 -0
- package/templates/rwsdk/src/client.tsx +3 -0
- package/templates/rwsdk/src/db.ts +26 -0
- package/templates/rwsdk/src/scripts/seed.ts +21 -0
- package/templates/rwsdk/src/session/durableObject.ts +63 -0
- package/templates/rwsdk/src/session/store.ts +13 -0
- package/templates/rwsdk/src/worker.tsx +63 -0
- package/templates/rwsdk/tsconfig.json +47 -0
- package/templates/rwsdk/types/env.d.ts +16 -0
- package/templates/rwsdk/types/rw.d.ts +5 -0
- package/templates/rwsdk/types/vite.d.ts +4 -0
- package/templates/rwsdk/vite.config.mts +6 -0
- package/templates/rwsdk/wrangler.jsonc +28 -0
- package/templates/sveltekit/README.md +38 -0
- package/templates/sveltekit/_env +1 -0
- package/templates/sveltekit/_env.example +1 -0
- package/templates/sveltekit/_gitignore +26 -0
- package/templates/sveltekit/_npmrc +1 -0
- package/templates/sveltekit/_prettierrc +3 -0
- package/templates/sveltekit/alchemy.run.ts +16 -0
- package/templates/sveltekit/bun.lock +1099 -0
- package/templates/sveltekit/package.json +33 -0
- package/templates/sveltekit/src/app.css +1 -0
- package/templates/sveltekit/src/app.d.ts +13 -0
- package/templates/sveltekit/src/app.html +12 -0
- package/templates/sveltekit/src/routes/+layout.svelte +7 -0
- package/templates/sveltekit/src/routes/+page.svelte +2 -0
- package/templates/sveltekit/static/favicon.png +0 -0
- package/templates/sveltekit/svelte.config.js +12 -0
- package/templates/sveltekit/tsconfig.json +27 -0
- package/templates/sveltekit/types/env.d.ts +16 -0
- package/templates/sveltekit/vite.config.ts +6 -0
- package/templates/sveltekit/wrangler.jsonc +7 -0
- package/templates/tanstack-start/.vscode/settings.json +11 -0
- package/templates/tanstack-start/README.md +72 -0
- package/templates/tanstack-start/_env +1 -0
- package/templates/tanstack-start/_env.example +1 -0
- package/templates/tanstack-start/_gitignore +22 -0
- package/templates/tanstack-start/_prettierignore +4 -0
- package/templates/tanstack-start/alchemy.run.ts +16 -0
- package/templates/tanstack-start/package.json +37 -0
- package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
- package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
- package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
- package/templates/tanstack-start/public/favicon-16x16.png +0 -0
- package/templates/tanstack-start/public/favicon-32x32.png +0 -0
- package/templates/tanstack-start/public/favicon.ico +0 -0
- package/templates/tanstack-start/public/favicon.png +0 -0
- package/templates/tanstack-start/public/site.webmanifest +19 -0
- package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +53 -0
- package/templates/tanstack-start/src/components/NotFound.tsx +25 -0
- package/templates/tanstack-start/src/components/PostError.tsx +5 -0
- package/templates/tanstack-start/src/components/UserError.tsx +5 -0
- package/templates/tanstack-start/src/router.tsx +22 -0
- package/templates/tanstack-start/src/routes/__root.tsx +146 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +10 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +10 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +34 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +16 -0
- package/templates/tanstack-start/src/routes/api/users.$userId.ts +28 -0
- package/templates/tanstack-start/src/routes/api/users.ts +59 -0
- package/templates/tanstack-start/src/routes/customScript[.]js.ts +10 -0
- package/templates/tanstack-start/src/routes/deferred.tsx +62 -0
- package/templates/tanstack-start/src/routes/index.tsx +12 -0
- package/templates/tanstack-start/src/routes/posts.$postId.tsx +34 -0
- package/templates/tanstack-start/src/routes/posts.index.tsx +8 -0
- package/templates/tanstack-start/src/routes/posts.tsx +38 -0
- package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +29 -0
- package/templates/tanstack-start/src/routes/redirect.tsx +9 -0
- package/templates/tanstack-start/src/routes/users.$userId.tsx +44 -0
- package/templates/tanstack-start/src/routes/users.index.tsx +18 -0
- package/templates/tanstack-start/src/routes/users.tsx +49 -0
- package/templates/tanstack-start/src/styles/app.css +12 -0
- package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +41 -0
- package/templates/tanstack-start/src/utils/posts.tsx +40 -0
- package/templates/tanstack-start/src/utils/seo.ts +33 -0
- package/templates/tanstack-start/src/utils/users.tsx +5 -0
- package/templates/tanstack-start/tsconfig.json +31 -0
- package/templates/tanstack-start/types/env.d.ts +16 -0
- package/templates/tanstack-start/vite.config.ts +33 -0
- package/templates/tanstack-start/wrangler.jsonc +7 -0
- package/templates/typescript/README.md +15 -0
- package/templates/typescript/_gitignore +37 -0
- package/templates/typescript/alchemy.run.ts +15 -0
- package/templates/typescript/index.ts +1 -0
- package/templates/typescript/package.json +18 -0
- package/templates/typescript/src/worker.js +5 -0
- package/templates/typescript/src/worker.ts +7 -0
- package/templates/typescript/tsconfig.json +21 -0
- package/templates/typescript/types/env.d.ts +16 -0
- package/templates/vite/README.md +54 -0
- package/templates/vite/_env.example +1 -0
- package/templates/vite/_gitignore +27 -0
- package/templates/vite/alchemy.run.ts +17 -0
- package/templates/vite/eslint.config.js +28 -0
- package/templates/vite/index.html +13 -0
- package/templates/vite/package.json +35 -0
- package/templates/vite/public/vite.svg +1 -0
- package/templates/vite/src/App.css +42 -0
- package/templates/vite/src/App.tsx +35 -0
- package/templates/vite/src/assets/react.svg +1 -0
- package/templates/vite/src/index.css +68 -0
- package/templates/vite/src/main.tsx +10 -0
- package/templates/vite/src/vite-env.d.ts +1 -0
- package/templates/vite/tsconfig.json +34 -0
- package/templates/vite/types/env.d.ts +16 -0
- package/templates/vite/vite.config.ts +9 -0
- package/templates/vite/worker/index.ts +12 -0
- package/templates/vite/wrangler.jsonc +6 -0
- package/bin/create-alchemy.ts +0 -1220
package/src/apply.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type Resource,
|
|
13
13
|
type ResourceProps,
|
|
14
14
|
} from "./resource.ts";
|
|
15
|
-
import { Scope } from "./scope.ts";
|
|
15
|
+
import { Scope, type PendingDeletions } from "./scope.ts";
|
|
16
16
|
import { serialize } from "./serde.ts";
|
|
17
17
|
import type { State } from "./state.ts";
|
|
18
18
|
import { formatFQN } from "./util/cli.ts";
|
|
@@ -33,6 +33,8 @@ export function apply<Out extends Resource>(
|
|
|
33
33
|
return _apply(resource, props, options);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export class ReplacedSignal extends Error {}
|
|
37
|
+
|
|
36
38
|
async function _apply<Out extends Resource>(
|
|
37
39
|
resource: PendingResource<Out>,
|
|
38
40
|
props: ResourceProps | undefined,
|
|
@@ -88,6 +90,7 @@ async function _apply<Out extends Resource>(
|
|
|
88
90
|
};
|
|
89
91
|
await scope.state.set(resource[ResourceID], state);
|
|
90
92
|
}
|
|
93
|
+
const oldOutput = state.output;
|
|
91
94
|
|
|
92
95
|
const alwaysUpdate =
|
|
93
96
|
options?.alwaysUpdate ?? provider.options?.alwaysUpdate ?? false;
|
|
@@ -162,33 +165,83 @@ async function _apply<Out extends Resource>(
|
|
|
162
165
|
props: state.oldProps,
|
|
163
166
|
state,
|
|
164
167
|
replace: () => {
|
|
168
|
+
if (phase === "create") {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Resource ${resource[ResourceKind]} ${resource[ResourceFQN]} cannot be replaced in create phase.`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
165
173
|
if (isReplaced) {
|
|
166
174
|
logger.warn(
|
|
167
175
|
`Resource ${resource[ResourceKind]} ${resource[ResourceFQN]} is already marked as REPLACE`,
|
|
168
176
|
);
|
|
169
|
-
return;
|
|
170
177
|
}
|
|
178
|
+
|
|
171
179
|
isReplaced = true;
|
|
180
|
+
throw new ReplacedSignal();
|
|
172
181
|
},
|
|
173
182
|
});
|
|
174
183
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
let output: Resource<string>;
|
|
185
|
+
try {
|
|
186
|
+
output = await alchemy.run(
|
|
187
|
+
resource[ResourceID],
|
|
188
|
+
{
|
|
189
|
+
isResource: true,
|
|
190
|
+
parent: scope,
|
|
191
|
+
},
|
|
192
|
+
async (scope) => {
|
|
193
|
+
options?.resolveInnerScope?.(scope);
|
|
194
|
+
return provider.handler.bind(ctx)(resource[ResourceID], props);
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (error instanceof ReplacedSignal) {
|
|
199
|
+
if (scope.children.get(resource[ResourceID])?.children.size! > 0) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Resource ${resource[ResourceFQN]} has children and cannot be replaced.`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
output = await alchemy.run(
|
|
206
|
+
resource[ResourceID],
|
|
207
|
+
{ isResource: true, parent: scope },
|
|
208
|
+
async () =>
|
|
209
|
+
provider.handler.bind(
|
|
210
|
+
context({
|
|
211
|
+
scope,
|
|
212
|
+
phase: "create",
|
|
213
|
+
kind: resource[ResourceKind],
|
|
214
|
+
id: resource[ResourceID],
|
|
215
|
+
fqn: resource[ResourceFQN],
|
|
216
|
+
seq: resource[ResourceSeq],
|
|
217
|
+
props: state.props,
|
|
218
|
+
state,
|
|
219
|
+
replace: () => {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Resource ${resource[ResourceKind]} ${resource[ResourceFQN]} cannot be replaced in create phase.`,
|
|
222
|
+
);
|
|
223
|
+
},
|
|
224
|
+
}),
|
|
225
|
+
)(resource[ResourceID], props),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const pendingDeletions =
|
|
229
|
+
(await scope.get<PendingDeletions>("pendingDeletions")) ?? [];
|
|
230
|
+
pendingDeletions.push({
|
|
231
|
+
resource: oldOutput,
|
|
232
|
+
oldProps: state.oldProps,
|
|
233
|
+
});
|
|
234
|
+
await scope.set("pendingDeletions", pendingDeletions);
|
|
235
|
+
} else {
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
186
239
|
if (!quiet) {
|
|
187
240
|
logger.task(resource[ResourceFQN], {
|
|
188
241
|
prefix: phase === "create" ? "created" : "updated",
|
|
189
242
|
prefixColor: "greenBright",
|
|
190
243
|
resource: formatFQN(resource[ResourceFQN]),
|
|
191
|
-
message: `${phase === "create" ? "Created" : "Updated"} Resource`,
|
|
244
|
+
message: `${phase === "create" ? "Created" : isReplaced ? "Replaced" : "Updated"} Resource`,
|
|
192
245
|
status: "success",
|
|
193
246
|
});
|
|
194
247
|
}
|
|
@@ -213,9 +266,6 @@ async function _apply<Out extends Resource>(
|
|
|
213
266
|
props,
|
|
214
267
|
// deps: [...deps],
|
|
215
268
|
});
|
|
216
|
-
// if (output !== undefined) {
|
|
217
|
-
// resource[Provide](output as Out);
|
|
218
|
-
// }
|
|
219
269
|
return output as any;
|
|
220
270
|
} catch (error) {
|
|
221
271
|
scope.telemetryClient.record({
|
package/src/cloudflare/api.ts
CHANGED
|
@@ -196,7 +196,8 @@ export class CloudflareApi {
|
|
|
196
196
|
(error) =>
|
|
197
197
|
error instanceof InternalError ||
|
|
198
198
|
error instanceof TooManyRequestsError ||
|
|
199
|
-
error instanceof ForbiddenError
|
|
199
|
+
error instanceof ForbiddenError ||
|
|
200
|
+
error.code === "ECONNRESET",
|
|
200
201
|
10, // Maximum 10 attempts (1 initial + 9 retries)
|
|
201
202
|
1000, // Start with 1s delay, will exponentially increase
|
|
202
203
|
);
|
|
@@ -153,7 +153,7 @@ export async function upsertStateStoreWorker(
|
|
|
153
153
|
);
|
|
154
154
|
|
|
155
155
|
// Put the worker with migration tag v1
|
|
156
|
-
const response = await api.
|
|
156
|
+
const response = await api.put(
|
|
157
157
|
`/accounts/${api.accountId}/workers/scripts/${workerName}`,
|
|
158
158
|
formData,
|
|
159
159
|
);
|
|
@@ -209,7 +209,7 @@ export async function getAccountSubdomain(api: CloudflareApi) {
|
|
|
209
209
|
const res = await api.get(`/accounts/${api.accountId}/workers/subdomain`);
|
|
210
210
|
if (!res.ok) {
|
|
211
211
|
throw new Error(
|
|
212
|
-
`Failed to get account subdomain: ${res.status} ${res.statusText}`,
|
|
212
|
+
`Failed to get account subdomain: ${res.status} ${res.statusText}: ${await res.text().catch(() => "unknown error")}`,
|
|
213
213
|
);
|
|
214
214
|
}
|
|
215
215
|
const json: { result: { subdomain: string } } = await res.json();
|
|
@@ -237,5 +237,7 @@ async function bundleWorkerScript() {
|
|
|
237
237
|
if (!result.outputFiles?.[0]) {
|
|
238
238
|
throw new Error("Failed to bundle worker.ts");
|
|
239
239
|
}
|
|
240
|
-
return result.outputFiles[0].text
|
|
240
|
+
return new File([result.outputFiles[0].text], "worker.js", {
|
|
241
|
+
type: "application/javascript+module",
|
|
242
|
+
});
|
|
241
243
|
}
|
|
@@ -63,10 +63,20 @@ export default class extends WorkerEntrypoint<Env> {
|
|
|
63
63
|
if (!token) {
|
|
64
64
|
return false;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
|
|
67
|
+
// Convert both to fixed-length buffers
|
|
68
|
+
const expectedBuffer = Buffer.from(this.env.DOFS_TOKEN, "utf8");
|
|
69
|
+
const providedBuffer = Buffer.alloc(expectedBuffer.length);
|
|
70
|
+
|
|
71
|
+
// Copy provided data, truncating or padding as needed
|
|
72
|
+
Buffer.from(token, "utf8").copy(
|
|
73
|
+
providedBuffer,
|
|
74
|
+
0,
|
|
75
|
+
0,
|
|
76
|
+
expectedBuffer.length,
|
|
69
77
|
);
|
|
78
|
+
|
|
79
|
+
return timingSafeEqual(expectedBuffer, providedBuffer);
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
private getStub(request: Request): DurableObjectStub<DOFSStateStore> {
|
|
@@ -522,6 +522,7 @@ export async function createPipeline(
|
|
|
522
522
|
api: CloudflareApi,
|
|
523
523
|
pipelineName: string,
|
|
524
524
|
props: PipelineProps,
|
|
525
|
+
attempt = 0,
|
|
525
526
|
): Promise<CloudflarePipelineResponse> {
|
|
526
527
|
// Prepare the create payload
|
|
527
528
|
const createPayload = preparePipelinePayload(api, pipelineName, props);
|
|
@@ -532,6 +533,11 @@ export async function createPipeline(
|
|
|
532
533
|
);
|
|
533
534
|
|
|
534
535
|
if (!createResponse.ok) {
|
|
536
|
+
if (createResponse.status === 404 && attempt < 3) {
|
|
537
|
+
// bucket does not exist, this might be transient, let's retry
|
|
538
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * (1 + attempt)));
|
|
539
|
+
return await createPipeline(api, pipelineName, props, attempt + 1);
|
|
540
|
+
}
|
|
535
541
|
return await handleApiError(
|
|
536
542
|
createResponse,
|
|
537
543
|
"creating",
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type CloudflareApi,
|
|
8
8
|
type CloudflareApiOptions,
|
|
9
9
|
} from "./api.ts";
|
|
10
|
+
import { configureURL } from "./worker.ts";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Properties for creating a Worker stub
|
|
@@ -19,6 +20,14 @@ export interface WorkerStubProps<
|
|
|
19
20
|
*/
|
|
20
21
|
name: string;
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Whether to enable a workers.dev URL for this worker
|
|
25
|
+
*
|
|
26
|
+
* If true, the worker will be available at {name}.{subdomain}.workers.dev
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
url?: boolean;
|
|
30
|
+
|
|
22
31
|
/**
|
|
23
32
|
* The RPC class to use for the worker.
|
|
24
33
|
*
|
|
@@ -39,6 +48,12 @@ export interface WorkerStub<
|
|
|
39
48
|
*/
|
|
40
49
|
name: string;
|
|
41
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The worker's URL if enabled
|
|
53
|
+
* Format: {name}.{subdomain}.workers.dev
|
|
54
|
+
*/
|
|
55
|
+
url?: string;
|
|
56
|
+
|
|
42
57
|
/**
|
|
43
58
|
* Optional type branding for the worker's RPC entrypoint.
|
|
44
59
|
*
|
|
@@ -59,12 +74,21 @@ export function isWorkerStub(resource: Resource): resource is WorkerStub {
|
|
|
59
74
|
* exists and creates an empty one if needed.
|
|
60
75
|
*
|
|
61
76
|
* @example
|
|
62
|
-
* // Reserve a worker name without deploying code
|
|
77
|
+
* // Reserve a worker name without deploying code, with URL enabled (default)
|
|
63
78
|
* const workerStub = await WorkerStub("my-worker", {
|
|
64
79
|
* name: "my-reserved-worker"
|
|
65
80
|
* });
|
|
66
81
|
*
|
|
67
|
-
* console.log(`Worker ${workerStub.name}
|
|
82
|
+
* console.log(`Worker ${workerStub.name} is available at: ${workerStub.url}`);
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* // Reserve a worker name without enabling URL
|
|
86
|
+
* const workerStub = await WorkerStub("my-worker", {
|
|
87
|
+
* name: "my-reserved-worker",
|
|
88
|
+
* url: false
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* console.log(`Worker ${workerStub.name} created without URL`);
|
|
68
92
|
*/
|
|
69
93
|
export const WorkerStub = Resource("cloudflare::WorkerStub", async function <
|
|
70
94
|
RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
|
|
@@ -84,11 +108,16 @@ export const WorkerStub = Resource("cloudflare::WorkerStub", async function <
|
|
|
84
108
|
await createEmptyWorker(api, props.name);
|
|
85
109
|
}
|
|
86
110
|
|
|
111
|
+
// Configure URL if requested (defaults to true)
|
|
112
|
+
const enableUrl = props.url ?? true;
|
|
113
|
+
const workerUrl = await configureURL(this, api, props.name, enableUrl);
|
|
114
|
+
|
|
87
115
|
// Return the worker stub info
|
|
88
116
|
return this({
|
|
89
117
|
type: "service",
|
|
90
118
|
__rpc__: props.rpc as unknown as RPC,
|
|
91
119
|
...props,
|
|
120
|
+
url: workerUrl,
|
|
92
121
|
}) as WorkerStub<RPC>;
|
|
93
122
|
});
|
|
94
123
|
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { BUILD_DATE } from "../build-date.ts";
|
|
2
3
|
import type { Context } from "../context.ts";
|
|
3
4
|
import type { BundleProps } from "../esbuild/bundle.ts";
|
|
4
5
|
import { InnerResourceScope, Resource, ResourceKind } from "../resource.ts";
|
|
@@ -12,7 +13,6 @@ import type { type } from "../type.ts";
|
|
|
12
13
|
import { getContentType } from "../util/content-type.ts";
|
|
13
14
|
import { logger } from "../util/logger.ts";
|
|
14
15
|
import { withExponentialBackoff } from "../util/retry.ts";
|
|
15
|
-
import { slugify } from "../util/slugify.ts";
|
|
16
16
|
import { CloudflareApiError, handleApiError } from "./api-error.ts";
|
|
17
17
|
import {
|
|
18
18
|
type CloudflareApi,
|
|
@@ -178,7 +178,7 @@ export interface BaseWorkerProps<
|
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
180
|
* The compatibility date for the worker
|
|
181
|
-
* @default
|
|
181
|
+
* @default BUILD_DATE - automatically pinned to the package build date
|
|
182
182
|
*/
|
|
183
183
|
compatibilityDate?: string;
|
|
184
184
|
|
|
@@ -861,7 +861,7 @@ export function Worker<const B extends Bindings>(
|
|
|
861
861
|
});
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
-
export const DEFAULT_COMPATIBILITY_DATE =
|
|
864
|
+
export const DEFAULT_COMPATIBILITY_DATE = BUILD_DATE;
|
|
865
865
|
|
|
866
866
|
export const _Worker = Resource(
|
|
867
867
|
"cloudflare::Worker",
|
|
@@ -1063,7 +1063,7 @@ export const _Worker = Resource(
|
|
|
1063
1063
|
}
|
|
1064
1064
|
// We always "adopt" when publishing versions
|
|
1065
1065
|
} else if (!props.adopt) {
|
|
1066
|
-
await assertWorkerDoesNotExist(
|
|
1066
|
+
await assertWorkerDoesNotExist(api, workerName);
|
|
1067
1067
|
}
|
|
1068
1068
|
}
|
|
1069
1069
|
|
|
@@ -1429,8 +1429,7 @@ export async function checkWorkerExists(
|
|
|
1429
1429
|
return response.status === 200;
|
|
1430
1430
|
}
|
|
1431
1431
|
|
|
1432
|
-
export async function assertWorkerDoesNotExist
|
|
1433
|
-
ctx: Context<Worker<B>>,
|
|
1432
|
+
export async function assertWorkerDoesNotExist(
|
|
1434
1433
|
api: CloudflareApi,
|
|
1435
1434
|
workerName: string,
|
|
1436
1435
|
) {
|
|
@@ -1449,14 +1448,6 @@ export async function assertWorkerDoesNotExist<B extends Bindings>(
|
|
|
1449
1448
|
);
|
|
1450
1449
|
}
|
|
1451
1450
|
|
|
1452
|
-
if (
|
|
1453
|
-
metadata.default_environment?.script.tags.includes(
|
|
1454
|
-
`alchemy:id:${slugify(ctx.fqn)}`,
|
|
1455
|
-
)
|
|
1456
|
-
) {
|
|
1457
|
-
return true;
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
1451
|
throw new Error(
|
|
1461
1452
|
`Worker with name '${workerName}' already exists. Please use a unique name.`,
|
|
1462
1453
|
);
|
|
@@ -1467,7 +1458,7 @@ export async function assertWorkerDoesNotExist<B extends Bindings>(
|
|
|
1467
1458
|
}
|
|
1468
1459
|
|
|
1469
1460
|
export async function configureURL<B extends Bindings>(
|
|
1470
|
-
ctx: Context<Worker<B
|
|
1461
|
+
ctx: Context<Worker<B>> | Context<WorkerStub>,
|
|
1471
1462
|
api: CloudflareApi,
|
|
1472
1463
|
workerName: string,
|
|
1473
1464
|
url: boolean,
|
package/src/context.ts
CHANGED
|
@@ -53,7 +53,7 @@ export interface BaseContext<Out extends Resource> {
|
|
|
53
53
|
* Indicate that this resource is being replaced.
|
|
54
54
|
* This will cause the resource to be deleted at the end of the stack's CREATE phase.
|
|
55
55
|
*/
|
|
56
|
-
replace():
|
|
56
|
+
replace(): never;
|
|
57
57
|
/**
|
|
58
58
|
* Terminate the resource lifecycle handler and destroy the resource.
|
|
59
59
|
*
|
|
@@ -97,7 +97,7 @@ export function context<
|
|
|
97
97
|
seq: number;
|
|
98
98
|
props: Props;
|
|
99
99
|
state: State<Kind, Props, Out>;
|
|
100
|
-
replace: () =>
|
|
100
|
+
replace: () => never;
|
|
101
101
|
}): Context<Out> {
|
|
102
102
|
type InternalSymbols =
|
|
103
103
|
| typeof ResourceID
|
package/src/destroy.ts
CHANGED
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
ResourceFQN,
|
|
7
7
|
ResourceID,
|
|
8
8
|
ResourceKind,
|
|
9
|
+
type ResourceProps,
|
|
9
10
|
ResourceScope,
|
|
10
11
|
ResourceSeq,
|
|
11
12
|
} from "./resource.ts";
|
|
12
|
-
import { isScope, Scope } from "./scope.ts";
|
|
13
|
+
import { isScope, type PendingDeletions, Scope } from "./scope.ts";
|
|
13
14
|
import { formatFQN } from "./util/cli.ts";
|
|
14
15
|
import { logger } from "./util/logger.ts";
|
|
15
16
|
|
|
@@ -18,6 +19,10 @@ export class DestroyedSignal extends Error {}
|
|
|
18
19
|
export interface DestroyOptions {
|
|
19
20
|
quiet?: boolean;
|
|
20
21
|
strategy?: "sequential" | "parallel";
|
|
22
|
+
replace?: {
|
|
23
|
+
props?: ResourceProps | undefined;
|
|
24
|
+
output?: Resource<string>;
|
|
25
|
+
};
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
function isScopeArgs(a: any): a is [scope: Scope, options?: DestroyOptions] {
|
|
@@ -40,7 +45,8 @@ export async function destroy<Type extends string>(
|
|
|
40
45
|
} satisfies DestroyOptions;
|
|
41
46
|
|
|
42
47
|
await scope.run(async () => {
|
|
43
|
-
// destroy all active resources
|
|
48
|
+
// destroy all active and pending resources
|
|
49
|
+
await scope.destroyPendingDeletions();
|
|
44
50
|
await destroyAll(Array.from(scope.resources.values()), options);
|
|
45
51
|
|
|
46
52
|
// then detect orphans and destroy them
|
|
@@ -109,7 +115,7 @@ export async function destroy<Type extends string>(
|
|
|
109
115
|
id: instance[ResourceID],
|
|
110
116
|
fqn: instance[ResourceFQN],
|
|
111
117
|
seq: instance[ResourceSeq],
|
|
112
|
-
props: state.props,
|
|
118
|
+
props: options?.replace?.props ?? state.props,
|
|
113
119
|
state,
|
|
114
120
|
replace: () => {
|
|
115
121
|
throw new Error("Cannot replace a resource that is being deleted");
|
|
@@ -127,10 +133,10 @@ export async function destroy<Type extends string>(
|
|
|
127
133
|
parent: scope,
|
|
128
134
|
},
|
|
129
135
|
async (scope) => {
|
|
130
|
-
nestedScope = scope;
|
|
136
|
+
nestedScope = options?.replace?.props == null ? scope : undefined;
|
|
131
137
|
return await Provider.handler.bind(ctx)(
|
|
132
138
|
instance[ResourceID],
|
|
133
|
-
state.props!,
|
|
139
|
+
options?.replace?.props ?? state.props!,
|
|
134
140
|
);
|
|
135
141
|
},
|
|
136
142
|
);
|
|
@@ -146,7 +152,21 @@ export async function destroy<Type extends string>(
|
|
|
146
152
|
await destroy(nestedScope, options);
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
|
|
155
|
+
if (options?.replace == null) {
|
|
156
|
+
if (nestedScope) {
|
|
157
|
+
await destroy(nestedScope, options);
|
|
158
|
+
}
|
|
159
|
+
await scope.deleteResource(instance[ResourceID]);
|
|
160
|
+
} else {
|
|
161
|
+
let pendingDeletions =
|
|
162
|
+
await state.output[ResourceScope].get<PendingDeletions>(
|
|
163
|
+
"pendingDeletions",
|
|
164
|
+
);
|
|
165
|
+
pendingDeletions = pendingDeletions?.filter(
|
|
166
|
+
(deletion) => deletion.resource[ResourceID] !== instance[ResourceID],
|
|
167
|
+
);
|
|
168
|
+
await scope.set("pendingDeletions", pendingDeletions);
|
|
169
|
+
}
|
|
150
170
|
|
|
151
171
|
if (!quiet) {
|
|
152
172
|
logger.task(instance[ResourceFQN], {
|
|
@@ -165,11 +185,14 @@ export async function destroy<Type extends string>(
|
|
|
165
185
|
|
|
166
186
|
export async function destroyAll(
|
|
167
187
|
resources: Resource[],
|
|
168
|
-
options?: DestroyOptions,
|
|
188
|
+
options?: DestroyOptions & { force?: boolean },
|
|
169
189
|
) {
|
|
170
190
|
if (options?.strategy !== "parallel") {
|
|
171
191
|
const sorted = resources.sort((a, b) => b[ResourceSeq] - a[ResourceSeq]);
|
|
172
192
|
for (const resource of sorted) {
|
|
193
|
+
if (isScope(resource)) {
|
|
194
|
+
await resource.destroyPendingDeletions();
|
|
195
|
+
}
|
|
173
196
|
await destroy(resource, options);
|
|
174
197
|
}
|
|
175
198
|
} else {
|