alchemy 0.45.5 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/alchemy.mjs +19285 -15137
- package/bin/alchemy.ts +14 -56
- package/bin/commands/create.ts +307 -137
- package/bin/commands/deploy.ts +23 -0
- package/bin/commands/destroy.ts +19 -0
- package/bin/commands/dev.ts +21 -0
- package/bin/commands/login.ts +66 -0
- package/bin/commands/run.ts +29 -0
- package/bin/services/dependencies.ts +0 -5
- package/bin/services/execute-alchemy.ts +160 -0
- package/bin/services/git.ts +27 -0
- package/bin/services/github-workflow.ts +229 -0
- package/bin/services/package-manager.ts +51 -68
- package/bin/services/template-manager.ts +10 -21
- package/bin/trpc.ts +3 -0
- package/bin/types.ts +4 -2
- package/lib/apply.d.ts +2 -0
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +26 -8
- package/lib/apply.js.map +1 -1
- package/lib/build-date.d.ts +1 -1
- package/lib/build-date.js +1 -1
- package/lib/cloudflare/ai.d.ts +8 -4
- package/lib/cloudflare/ai.d.ts.map +1 -1
- package/lib/cloudflare/ai.js +6 -8
- package/lib/cloudflare/ai.js.map +1 -1
- package/lib/cloudflare/analytics-engine.d.ts +11 -7
- package/lib/cloudflare/analytics-engine.d.ts.map +1 -1
- package/lib/cloudflare/analytics-engine.js +11 -9
- package/lib/cloudflare/analytics-engine.js.map +1 -1
- package/lib/cloudflare/{types.d.ts → api-response.d.ts} +6 -1
- package/lib/cloudflare/api-response.d.ts.map +1 -0
- package/lib/cloudflare/api-response.js +38 -0
- package/lib/cloudflare/api-response.js.map +1 -0
- package/lib/cloudflare/auth-wrangler.d.ts +25 -0
- package/lib/cloudflare/auth-wrangler.d.ts.map +1 -0
- package/lib/cloudflare/auth-wrangler.js +224 -0
- package/lib/cloudflare/auth-wrangler.js.map +1 -0
- package/lib/cloudflare/auth.d.ts.map +1 -1
- package/lib/cloudflare/auth.js +7 -116
- package/lib/cloudflare/auth.js.map +1 -1
- package/lib/cloudflare/browser-rendering.d.ts +12 -3
- package/lib/cloudflare/browser-rendering.d.ts.map +1 -1
- package/lib/cloudflare/browser-rendering.js +12 -2
- package/lib/cloudflare/browser-rendering.js.map +1 -1
- package/lib/cloudflare/bundle/fs.d.ts.map +1 -1
- package/lib/cloudflare/bundle/fs.js +3 -0
- package/lib/cloudflare/bundle/fs.js.map +1 -1
- package/lib/cloudflare/bundle/internal-worker-bundle.d.ts +6 -0
- package/lib/cloudflare/bundle/internal-worker-bundle.d.ts.map +1 -0
- package/lib/cloudflare/bundle/internal-worker-bundle.js +14 -0
- package/lib/cloudflare/bundle/internal-worker-bundle.js.map +1 -0
- package/lib/cloudflare/compatibility-presets.d.ts +22 -0
- package/lib/cloudflare/compatibility-presets.d.ts.map +1 -0
- package/lib/cloudflare/compatibility-presets.js +27 -0
- package/lib/cloudflare/compatibility-presets.js.map +1 -0
- package/lib/cloudflare/container.d.ts +6 -2
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +48 -4
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/dispatch-namespace.d.ts +5 -0
- package/lib/cloudflare/dispatch-namespace.d.ts.map +1 -1
- package/lib/cloudflare/dispatch-namespace.js +8 -0
- package/lib/cloudflare/dispatch-namespace.js.map +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts +23 -15
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.js +22 -21
- package/lib/cloudflare/durable-object-namespace.js.map +1 -1
- package/lib/cloudflare/hyperdrive.d.ts +19 -5
- package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
- package/lib/cloudflare/hyperdrive.js +14 -0
- package/lib/cloudflare/hyperdrive.js.map +1 -1
- package/lib/cloudflare/images.d.ts +27 -17
- package/lib/cloudflare/images.d.ts.map +1 -1
- package/lib/cloudflare/images.js +13 -8
- package/lib/cloudflare/images.js.map +1 -1
- package/lib/cloudflare/index.d.ts +1 -3
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -3
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare.d.ts +3 -3
- package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare.js +7 -7
- package/lib/cloudflare/miniflare/miniflare.js.map +1 -1
- package/lib/cloudflare/miniflare/{mixed-mode.d.ts → remote-binding-proxy.d.ts} +5 -5
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -0
- package/lib/cloudflare/miniflare/{mixed-mode.js → remote-binding-proxy.js} +16 -52
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -0
- package/lib/cloudflare/state.d.ts +26 -0
- package/lib/cloudflare/state.d.ts.map +1 -0
- package/lib/cloudflare/state.js +14 -0
- package/lib/cloudflare/state.js.map +1 -0
- package/lib/cloudflare/user.d.ts +2 -2
- package/lib/cloudflare/user.d.ts.map +1 -1
- package/lib/cloudflare/user.js +7 -21
- package/lib/cloudflare/user.js.map +1 -1
- package/lib/cloudflare/vectorize-index.d.ts.map +1 -1
- package/lib/cloudflare/vectorize-index.js +5 -2
- package/lib/cloudflare/vectorize-index.js.map +1 -1
- package/lib/cloudflare/version-metadata.d.ts +15 -3
- package/lib/cloudflare/version-metadata.d.ts.map +1 -1
- package/lib/cloudflare/version-metadata.js +11 -2
- package/lib/cloudflare/version-metadata.js.map +1 -1
- package/lib/cloudflare/vite.d.ts.map +1 -1
- package/lib/cloudflare/vite.js +2 -1
- package/lib/cloudflare/vite.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +13 -2
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +10 -15
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker-subdomain.d.ts +7 -0
- package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
- package/lib/cloudflare/worker-subdomain.js +17 -22
- package/lib/cloudflare/worker-subdomain.js.map +1 -1
- package/lib/cloudflare/{worker/tail.d.ts → worker-tail.d.ts} +2 -2
- package/lib/cloudflare/worker-tail.d.ts.map +1 -0
- package/lib/cloudflare/{worker/tail.js → worker-tail.js} +11 -9
- package/lib/cloudflare/worker-tail.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +18 -8
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +32 -8
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/workflow.d.ts +22 -11
- package/lib/cloudflare/workflow.d.ts.map +1 -1
- package/lib/cloudflare/workflow.js +23 -18
- package/lib/cloudflare/workflow.js.map +1 -1
- package/lib/context.d.ts +2 -2
- package/lib/context.d.ts.map +1 -1
- package/lib/docker/api.d.ts.map +1 -1
- package/lib/docker/api.js +30 -8
- package/lib/docker/api.js.map +1 -1
- package/lib/fs/file-system-state-store.d.ts +5 -20
- package/lib/fs/file-system-state-store.d.ts.map +1 -1
- package/lib/fs/file-system-state-store.js +5 -121
- package/lib/fs/file-system-state-store.js.map +1 -1
- package/lib/fs/index.d.ts +0 -1
- package/lib/fs/index.d.ts.map +1 -1
- package/lib/fs/index.js +0 -1
- package/lib/fs/index.js.map +1 -1
- package/lib/llms.cloudflare.txt +6 -5
- package/lib/planetscale/password.js +1 -1
- package/lib/planetscale/password.js.map +1 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +3 -6
- package/lib/scope.js.map +1 -1
- package/lib/state/cloudflare-state-store.d.ts +34 -0
- package/lib/state/cloudflare-state-store.d.ts.map +1 -0
- package/lib/state/cloudflare-state-store.js +116 -0
- package/lib/state/cloudflare-state-store.js.map +1 -0
- package/lib/state/d1-state-store.d.ts +15 -0
- package/lib/state/d1-state-store.d.ts.map +1 -0
- package/lib/{cloudflare → state}/d1-state-store.js +31 -27
- package/lib/state/d1-state-store.js.map +1 -0
- package/lib/{cloudflare/do-state-store/types.d.ts → state/dofs-state-store/api.d.ts} +1 -1
- package/lib/state/dofs-state-store/api.d.ts.map +1 -0
- package/lib/state/dofs-state-store/api.js +1 -0
- package/lib/state/dofs-state-store/api.js.map +1 -0
- package/lib/{cloudflare/do-state-store/store.d.ts → state/dofs-state-store/index.d.ts} +10 -5
- package/lib/state/dofs-state-store/index.d.ts.map +1 -0
- package/lib/{cloudflare/do-state-store/store.js → state/dofs-state-store/index.js} +15 -11
- package/lib/state/dofs-state-store/index.js.map +1 -0
- package/lib/{cloudflare/do-state-store/internal.d.ts → state/dofs-state-store/store.d.ts} +4 -5
- package/lib/state/dofs-state-store/store.d.ts.map +1 -0
- package/lib/{cloudflare/do-state-store/internal.js → state/dofs-state-store/store.js} +7 -22
- package/lib/state/dofs-state-store/store.js.map +1 -0
- package/lib/state/file-system-state-store.d.ts +21 -0
- package/lib/state/file-system-state-store.d.ts.map +1 -0
- package/lib/state/file-system-state-store.js +122 -0
- package/lib/state/file-system-state-store.js.map +1 -0
- package/lib/state/index.d.ts +7 -0
- package/lib/state/index.d.ts.map +1 -0
- package/lib/state/index.js +7 -0
- package/lib/state/index.js.map +1 -0
- package/lib/state/migrations.d.ts +2 -0
- package/lib/state/migrations.d.ts.map +1 -0
- package/lib/state/migrations.js +3 -0
- package/lib/state/migrations.js.map +1 -0
- package/lib/state/operations.d.ts +36 -0
- package/lib/state/operations.d.ts.map +1 -0
- package/lib/state/operations.js +126 -0
- package/lib/state/operations.js.map +1 -0
- package/lib/state/proxy.d.ts +53 -0
- package/lib/state/proxy.d.ts.map +1 -0
- package/lib/state/proxy.js +65 -0
- package/lib/state/proxy.js.map +1 -0
- package/lib/{cloudflare → state}/r2-rest-state-store.d.ts +3 -1
- package/lib/state/r2-rest-state-store.d.ts.map +1 -0
- package/lib/{cloudflare → state}/r2-rest-state-store.js +6 -4
- package/lib/state/r2-rest-state-store.js.map +1 -0
- package/lib/state/schema.d.ts.map +1 -0
- package/lib/state/schema.js.map +1 -0
- package/lib/{sqlite → state}/sqlite-state-store.d.ts +5 -3
- package/lib/state/sqlite-state-store.d.ts.map +1 -0
- package/lib/{sqlite → state}/sqlite-state-store.js +15 -6
- package/lib/state/sqlite-state-store.js.map +1 -0
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +3 -6
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/detect-node-runtime.d.ts +3 -0
- package/lib/util/detect-node-runtime.d.ts.map +1 -0
- package/lib/util/detect-node-runtime.js +10 -0
- package/lib/util/detect-node-runtime.js.map +1 -0
- package/lib/util/detect-package-manager.d.ts +2 -2
- package/lib/util/detect-package-manager.d.ts.map +1 -1
- package/lib/util/detect-package-manager.js +15 -5
- package/lib/util/detect-package-manager.js.map +1 -1
- package/lib/util/http-server.d.ts.map +1 -0
- package/lib/{cloudflare/miniflare → util}/http-server.js +1 -1
- package/lib/util/http-server.js.map +1 -0
- package/lib/util/memoize.d.ts +2 -0
- package/lib/util/memoize.d.ts.map +1 -1
- package/lib/util/memoize.js +14 -7
- package/lib/util/memoize.js.map +1 -1
- package/lib/util/neverthrow.d.ts +9 -0
- package/lib/util/neverthrow.d.ts.map +1 -0
- package/lib/util/neverthrow.js +29 -0
- package/lib/util/neverthrow.js.map +1 -0
- package/package.json +20 -20
- package/src/apply.ts +31 -10
- package/src/build-date.ts +1 -1
- package/src/cloudflare/ai.ts +10 -5
- package/src/cloudflare/analytics-engine.ts +20 -11
- package/src/cloudflare/{types.ts → api-response.ts} +31 -0
- package/src/cloudflare/auth-wrangler.ts +314 -0
- package/src/cloudflare/auth.ts +8 -156
- package/src/cloudflare/browser-rendering.ts +16 -2
- package/src/cloudflare/bundle/fs.ts +5 -0
- package/src/cloudflare/bundle/internal-worker-bundle.ts +21 -0
- package/src/cloudflare/compatibility-presets.ts +34 -0
- package/src/cloudflare/container.ts +77 -6
- package/src/cloudflare/dispatch-namespace.ts +28 -0
- package/src/cloudflare/durable-object-namespace.ts +38 -27
- package/src/cloudflare/hyperdrive.ts +19 -5
- package/src/cloudflare/images.ts +32 -17
- package/src/cloudflare/index.ts +1 -3
- package/src/cloudflare/miniflare/miniflare.ts +10 -7
- package/src/cloudflare/miniflare/{mixed-mode.ts → remote-binding-proxy.ts} +25 -75
- package/src/cloudflare/state.ts +35 -0
- package/src/cloudflare/user.ts +55 -70
- package/src/cloudflare/vectorize-index.ts +7 -2
- package/src/cloudflare/version-metadata.ts +19 -2
- package/src/cloudflare/vite.ts +2 -1
- package/src/cloudflare/worker-metadata.ts +18 -43
- package/src/cloudflare/worker-subdomain.ts +43 -37
- package/src/cloudflare/{worker/tail.ts → worker-tail.ts} +13 -10
- package/src/cloudflare/worker.ts +66 -20
- package/src/cloudflare/workflow.ts +38 -19
- package/src/context.ts +2 -2
- package/src/docker/api.ts +33 -8
- package/src/fs/file-system-state-store.ts +5 -148
- package/src/fs/index.ts +0 -1
- package/src/llms.cloudflare.txt +6 -5
- package/src/planetscale/password.ts +1 -1
- package/src/scope.ts +3 -6
- package/src/state/cloudflare-state-store.ts +170 -0
- package/src/state/d1-state-store.ts +102 -0
- package/src/{cloudflare/do-state-store/store.ts → state/dofs-state-store/index.ts} +20 -13
- package/src/{cloudflare/do-state-store/internal.ts → state/dofs-state-store/store.ts} +9 -27
- package/src/state/file-system-state-store.ts +149 -0
- package/src/state/index.ts +6 -0
- package/src/state/migrations.ts +9 -0
- package/src/state/operations.ts +152 -0
- package/src/state/proxy.ts +132 -0
- package/src/{cloudflare → state}/r2-rest-state-store.ts +11 -8
- package/src/{sqlite → state}/sqlite-state-store.ts +18 -10
- package/src/test/vitest.ts +8 -6
- package/src/util/detect-node-runtime.ts +12 -0
- package/src/util/detect-package-manager.ts +19 -6
- package/src/{cloudflare/miniflare → util}/http-server.ts +1 -1
- package/src/util/memoize.ts +18 -7
- package/src/util/neverthrow.ts +36 -0
- package/templates/astro/package.json +3 -3
- package/templates/nuxt/package.json +3 -3
- package/templates/react-router/package.json +3 -3
- package/templates/rwsdk/alchemy.run.ts +1 -1
- package/templates/rwsdk/package.json +3 -3
- package/templates/sveltekit/package.json +3 -3
- package/templates/tanstack-start/package.json +3 -3
- package/templates/typescript/package.json +3 -2
- package/templates/typescript/tsconfig.json +1 -0
- package/templates/vite/package.json +3 -3
- package/workers/cloudflare-state-store.js +5638 -0
- package/workers/cloudflare-state-store.ts +108 -0
- package/workers/{do-state-store.js → dofs-state-store.js} +4 -4
- package/workers/{do-state-store.ts → dofs-state-store.ts} +12 -8
- package/workers/{mixed-mode-proxy-worker.js → remote-binding-proxy.js} +3 -3
- package/workers/remote-binding-proxy.ts +45 -0
- package/lib/cloudflare/d1-state-store.d.ts +0 -10
- package/lib/cloudflare/d1-state-store.d.ts.map +0 -1
- package/lib/cloudflare/d1-state-store.js.map +0 -1
- package/lib/cloudflare/do-state-store/index.d.ts +0 -2
- package/lib/cloudflare/do-state-store/index.d.ts.map +0 -1
- package/lib/cloudflare/do-state-store/index.js +0 -2
- package/lib/cloudflare/do-state-store/index.js.map +0 -1
- package/lib/cloudflare/do-state-store/internal.d.ts.map +0 -1
- package/lib/cloudflare/do-state-store/internal.js.map +0 -1
- package/lib/cloudflare/do-state-store/store.d.ts.map +0 -1
- package/lib/cloudflare/do-state-store/store.js.map +0 -1
- package/lib/cloudflare/do-state-store/types.d.ts.map +0 -1
- package/lib/cloudflare/do-state-store/types.js +0 -1
- package/lib/cloudflare/do-state-store/types.js.map +0 -1
- package/lib/cloudflare/miniflare/http-server.d.ts.map +0 -1
- package/lib/cloudflare/miniflare/http-server.js.map +0 -1
- package/lib/cloudflare/miniflare/mixed-mode.d.ts.map +0 -1
- package/lib/cloudflare/miniflare/mixed-mode.js.map +0 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts.map +0 -1
- package/lib/cloudflare/r2-rest-state-store.js.map +0 -1
- package/lib/cloudflare/types.d.ts.map +0 -1
- package/lib/cloudflare/types.js +0 -19
- package/lib/cloudflare/types.js.map +0 -1
- package/lib/cloudflare/worker/shared.d.ts +0 -4
- package/lib/cloudflare/worker/shared.d.ts.map +0 -1
- package/lib/cloudflare/worker/shared.js +0 -21
- package/lib/cloudflare/worker/shared.js.map +0 -1
- package/lib/cloudflare/worker/tail.d.ts.map +0 -1
- package/lib/cloudflare/worker/tail.js.map +0 -1
- package/lib/sqlite/base-sqlite-state-store.d.ts +0 -31
- package/lib/sqlite/base-sqlite-state-store.d.ts.map +0 -1
- package/lib/sqlite/base-sqlite-state-store.js +0 -124
- package/lib/sqlite/base-sqlite-state-store.js.map +0 -1
- package/lib/sqlite/index.d.ts +0 -2
- package/lib/sqlite/index.d.ts.map +0 -1
- package/lib/sqlite/index.js +0 -2
- package/lib/sqlite/index.js.map +0 -1
- package/lib/sqlite/schema.d.ts.map +0 -1
- package/lib/sqlite/schema.js.map +0 -1
- package/lib/sqlite/sqlite-state-store.d.ts.map +0 -1
- package/lib/sqlite/sqlite-state-store.js.map +0 -1
- package/src/cloudflare/d1-state-store.ts +0 -105
- package/src/cloudflare/do-state-store/index.ts +0 -1
- package/src/cloudflare/worker/shared.ts +0 -30
- package/src/sqlite/base-sqlite-state-store.ts +0 -199
- package/src/sqlite/index.ts +0 -1
- package/templates/typescript/src/worker.js +0 -5
- package/workers/mixed-mode-proxy-worker.ts +0 -46
- /package/drizzle/{0000_busy_mauler.sql → default/0000_busy_mauler.sql} +0 -0
- /package/drizzle/{0001_concerned_iceman.sql → default/0001_concerned_iceman.sql} +0 -0
- /package/drizzle/{meta → default/meta}/0000_snapshot.json +0 -0
- /package/drizzle/{meta → default/meta}/0001_snapshot.json +0 -0
- /package/drizzle/{meta → default/meta}/_journal.json +0 -0
- /package/lib/{sqlite → state}/schema.d.ts +0 -0
- /package/lib/{sqlite → state}/schema.js +0 -0
- /package/lib/{cloudflare/miniflare → util}/http-server.d.ts +0 -0
- /package/src/{cloudflare/do-state-store/types.ts → state/dofs-state-store/api.ts} +0 -0
- /package/src/{sqlite → state}/schema.ts +0 -0
|
@@ -1,149 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { ResourceScope } from "../resource.ts";
|
|
4
|
-
import type { Scope } from "../scope.ts";
|
|
5
|
-
import { deserialize, serialize } from "../serde.ts";
|
|
6
|
-
import type { State, StateStore } from "../state.ts";
|
|
7
|
-
import { ignore } from "../util/ignore.ts";
|
|
1
|
+
import * as state from "../state/file-system-state-store.ts";
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export
|
|
13
|
-
public readonly dir: string;
|
|
14
|
-
private initialized = false;
|
|
15
|
-
constructor(
|
|
16
|
-
public readonly scope: Scope,
|
|
17
|
-
options?: {
|
|
18
|
-
rootDir?: string;
|
|
19
|
-
},
|
|
20
|
-
) {
|
|
21
|
-
this.dir = path.join(options?.rootDir ?? stateRootDir, ...scope.chain);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async init(): Promise<void> {
|
|
25
|
-
if (this.initialized) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
this.initialized = true;
|
|
29
|
-
await fs.promises.mkdir(this.dir, { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async deinit(): Promise<void> {
|
|
33
|
-
await ignore("ENOENT", async () => {
|
|
34
|
-
const entries = await fs.promises.readdir(this.dir, {
|
|
35
|
-
withFileTypes: true,
|
|
36
|
-
});
|
|
37
|
-
const files = entries.filter((entry) => entry.isFile());
|
|
38
|
-
if (files.length > 0) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`Cannot deinit: directory "${this.dir}" contains files and cannot be deleted.`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
// Only folders (or empty), delete recursively
|
|
44
|
-
await fs.promises.rm(this.dir, { recursive: true, force: true });
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async count(): Promise<number> {
|
|
49
|
-
return Object.keys(await this.list()).length;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async list(): Promise<string[]> {
|
|
53
|
-
try {
|
|
54
|
-
const files = await fs.promises.readdir(this.dir, {
|
|
55
|
-
withFileTypes: true,
|
|
56
|
-
});
|
|
57
|
-
return files
|
|
58
|
-
.filter((dirent) => dirent.isFile() && dirent.name.endsWith(".json"))
|
|
59
|
-
.map((dirent) => dirent.name.replace(/\.json$/, ""))
|
|
60
|
-
.map((key) => key.replaceAll(":", "/"));
|
|
61
|
-
} catch (error: any) {
|
|
62
|
-
if (error.code === "ENOENT") {
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async get(key: string): Promise<State | undefined> {
|
|
70
|
-
try {
|
|
71
|
-
const content = await fs.promises.readFile(this.getPath(key), "utf8");
|
|
72
|
-
const state = (await deserialize(
|
|
73
|
-
this.scope,
|
|
74
|
-
JSON.parse(content),
|
|
75
|
-
)) as State;
|
|
76
|
-
if (state.output === undefined) {
|
|
77
|
-
state.output = {} as any;
|
|
78
|
-
}
|
|
79
|
-
state.output[ResourceScope] = this.scope;
|
|
80
|
-
return state;
|
|
81
|
-
} catch (error: any) {
|
|
82
|
-
if (error.code === "ENOENT") {
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async set(key: string, value: State): Promise<void> {
|
|
90
|
-
await this.init();
|
|
91
|
-
const file = this.getPath(key);
|
|
92
|
-
await fs.promises.mkdir(path.dirname(file), { recursive: true });
|
|
93
|
-
await fs.promises.writeFile(
|
|
94
|
-
file,
|
|
95
|
-
JSON.stringify(await serialize(this.scope, value), null, 2),
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async delete(key: string): Promise<void> {
|
|
100
|
-
try {
|
|
101
|
-
return await fs.promises.unlink(this.getPath(key));
|
|
102
|
-
} catch (error: any) {
|
|
103
|
-
if (error.code === "ENOENT") {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async all(): Promise<Record<string, State>> {
|
|
111
|
-
return this.getBatch(await this.list());
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async getBatch(ids: string[]): Promise<Record<string, State>> {
|
|
115
|
-
return Object.fromEntries(
|
|
116
|
-
(
|
|
117
|
-
await Promise.all(
|
|
118
|
-
Array.from(ids).flatMap(async (id) => {
|
|
119
|
-
const s = await this.get(id);
|
|
120
|
-
if (s === undefined) {
|
|
121
|
-
return [] as const;
|
|
122
|
-
}
|
|
123
|
-
return [[id, s]] as const;
|
|
124
|
-
}),
|
|
125
|
-
)
|
|
126
|
-
).flat(),
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private getPath(key: string): string {
|
|
131
|
-
if (key.includes(":")) {
|
|
132
|
-
throw new Error(`ID cannot include colons: ${key}`);
|
|
133
|
-
}
|
|
134
|
-
if (key.includes("/")) {
|
|
135
|
-
//todo(michael): remove this next time we do a breaking change
|
|
136
|
-
//* windows doesn't support ":" in file paths, but we already use ":"
|
|
137
|
-
//* so now we use both to prevent breaking changes
|
|
138
|
-
key = key.replaceAll("/", ALCHEMY_SEPERATOR_CHAR);
|
|
139
|
-
}
|
|
140
|
-
//todo(michael): remove this next time we do a breaking change
|
|
141
|
-
//* windows doesn't support "*" in file paths, but we already use "*"
|
|
142
|
-
//* when making cloudflare routes containing "*"
|
|
143
|
-
//* so now we use "+" on windows but "*" on mac/linux to prevent breaking changes
|
|
144
|
-
if (process.platform === "win32") {
|
|
145
|
-
key = key.replaceAll("*", "+");
|
|
146
|
-
}
|
|
147
|
-
return path.join(this.dir, `${key}.json`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Use `FileSystemStateStore` from `alchemy/state` instead.
|
|
5
|
+
*/
|
|
6
|
+
export const FileSystemStateStore = state.FileSystemStateStore;
|
package/src/fs/index.ts
CHANGED
package/src/llms.cloudflare.txt
CHANGED
|
@@ -41,8 +41,9 @@ ALCHEMY_STATE_STORE=cloudflare
|
|
|
41
41
|
|
|
42
42
|
### Commands
|
|
43
43
|
```bash
|
|
44
|
-
bun
|
|
45
|
-
bun
|
|
44
|
+
bun alchemy deploy # Deploy
|
|
45
|
+
bun alchemy dev # Local development
|
|
46
|
+
bun alchemy destroy # Destroy all resources
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
## Workers
|
|
@@ -107,7 +108,7 @@ declare module "cloudflare:workers" {
|
|
|
107
108
|
### Creating Durable Object Namespace
|
|
108
109
|
```typescript
|
|
109
110
|
// Use 'new' instead of 'await' for DurableObjectNamespace
|
|
110
|
-
const counter =
|
|
111
|
+
const counter = DurableObjectNamespace("counter", {
|
|
111
112
|
className: "Counter",
|
|
112
113
|
sqlite: true, // Enable SQLite storage
|
|
113
114
|
});
|
|
@@ -161,7 +162,7 @@ export default {
|
|
|
161
162
|
const sharedCounter = host.bindings.SHARED_COUNTER;
|
|
162
163
|
|
|
163
164
|
// Method 2: Reference by worker script name
|
|
164
|
-
const counter =
|
|
165
|
+
const counter = DurableObjectNamespace("counter", {
|
|
165
166
|
className: "Counter",
|
|
166
167
|
scriptName: "provider-worker"
|
|
167
168
|
});
|
|
@@ -353,7 +354,7 @@ describe("Worker Tests", () => {
|
|
|
353
354
|
## Common Issues
|
|
354
355
|
|
|
355
356
|
- **Missing finalize()**: Resources won't be cleaned up properly
|
|
356
|
-
- **Wrong DurableObject syntax**: Use `
|
|
357
|
+
- **Wrong DurableObject syntax**: Use `DurableObjectNamespace()` not `await`
|
|
357
358
|
- **Type errors**: Set up env.d.ts file for binding types
|
|
358
359
|
- **State conflicts**: Use different stages/prefixes for different environments
|
|
359
360
|
- **Resource adoption**: Use `adopt: true` when migrating existing resources
|
|
@@ -306,7 +306,7 @@ export const Password = Resource(
|
|
|
306
306
|
this.output.cidrs.length === props.cidrs.length &&
|
|
307
307
|
this.output.cidrs.every((cidr, i) => cidr === props.cidrs![i])))
|
|
308
308
|
) {
|
|
309
|
-
return this.replace();
|
|
309
|
+
return await this.replace();
|
|
310
310
|
}
|
|
311
311
|
const updateResponse = await api.patch(
|
|
312
312
|
`/organizations/${props.organizationId}/databases/${databaseName}/branches/${branchName}/passwords/${this.output.id}`,
|
package/src/scope.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import util from "node:util";
|
|
3
3
|
import type { Phase } from "./alchemy.ts";
|
|
4
|
-
import { D1StateStore } from "./cloudflare/d1-state-store.ts";
|
|
5
|
-
import { DOStateStore } from "./cloudflare/do-state-store/index.ts";
|
|
6
4
|
import { destroy, destroyAll } from "./destroy.ts";
|
|
7
|
-
import { FileSystemStateStore } from "./fs/file-system-state-store.ts";
|
|
8
5
|
import {
|
|
9
6
|
ResourceFQN,
|
|
10
7
|
ResourceID,
|
|
@@ -16,6 +13,8 @@ import {
|
|
|
16
13
|
type ResourceProps,
|
|
17
14
|
} from "./resource.ts";
|
|
18
15
|
import type { State, StateStore, StateStoreType } from "./state.ts";
|
|
16
|
+
import { D1StateStore } from "./state/d1-state-store.ts";
|
|
17
|
+
import { FileSystemStateStore } from "./state/file-system-state-store.ts";
|
|
19
18
|
import {
|
|
20
19
|
createDummyLogger,
|
|
21
20
|
createLoggerInstance,
|
|
@@ -499,10 +498,8 @@ export class Scope {
|
|
|
499
498
|
|
|
500
499
|
const defaultStateStore: StateStoreType = (scope: Scope) => {
|
|
501
500
|
switch (process.env.ALCHEMY_STATE_STORE) {
|
|
502
|
-
case "
|
|
501
|
+
case "d1":
|
|
503
502
|
return new D1StateStore(scope);
|
|
504
|
-
case "cloudflare":
|
|
505
|
-
return new DOStateStore(scope);
|
|
506
503
|
default:
|
|
507
504
|
return new FileSystemStateStore(scope);
|
|
508
505
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { alchemy } from "../alchemy.ts";
|
|
2
|
+
import { BUILD_DATE } from "../build-date.ts";
|
|
3
|
+
import type { CloudflareApiOptions } from "../cloudflare/api.ts";
|
|
4
|
+
import { createCloudflareApi } from "../cloudflare/api.ts";
|
|
5
|
+
import { getInternalWorkerBundle } from "../cloudflare/bundle/internal-worker-bundle.ts";
|
|
6
|
+
import { DurableObjectNamespace } from "../cloudflare/durable-object-namespace.ts";
|
|
7
|
+
import { getWorkerSettings } from "../cloudflare/worker-metadata.ts";
|
|
8
|
+
import {
|
|
9
|
+
enableWorkerSubdomain,
|
|
10
|
+
getAccountSubdomain,
|
|
11
|
+
getWorkerSubdomain,
|
|
12
|
+
} from "../cloudflare/worker-subdomain.ts";
|
|
13
|
+
import { putWorker } from "../cloudflare/worker.ts";
|
|
14
|
+
import type { Scope } from "../scope.ts";
|
|
15
|
+
import type { Secret } from "../secret.ts";
|
|
16
|
+
import { logger } from "../util/logger.ts";
|
|
17
|
+
import { memoize } from "../util/memoize.ts";
|
|
18
|
+
import { StateStoreProxy } from "./proxy.ts";
|
|
19
|
+
|
|
20
|
+
export interface CloudflareStateStoreOptions extends CloudflareApiOptions {
|
|
21
|
+
/**
|
|
22
|
+
* The name of the script to use for the state store.
|
|
23
|
+
* @default "alchemy-state-service"
|
|
24
|
+
*/
|
|
25
|
+
scriptName?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to force the worker to be updated.
|
|
28
|
+
* This may be useful if you've lost the token for the state store and need to overwrite it.
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
forceUpdate?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The token to use for the state store.
|
|
34
|
+
* @default process.env.ALCHEMY_STATE_TOKEN
|
|
35
|
+
* @note You must use the same token for all deployments on your Cloudflare account.
|
|
36
|
+
*/
|
|
37
|
+
stateToken?: Secret<string>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A state store backed by a SQLite database in a Cloudflare Durable Object.
|
|
42
|
+
*
|
|
43
|
+
* @see {@link https://alchemy.run/guides/do-state-store DOStateStore}
|
|
44
|
+
*/
|
|
45
|
+
export class CloudflareStateStore extends StateStoreProxy {
|
|
46
|
+
constructor(
|
|
47
|
+
scope: Scope,
|
|
48
|
+
private readonly options: CloudflareStateStoreOptions = {},
|
|
49
|
+
) {
|
|
50
|
+
super(scope);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async provision(): Promise<StateStoreProxy.Dispatch> {
|
|
54
|
+
const { url, token } = await provision(this.options);
|
|
55
|
+
return async (method, params) => {
|
|
56
|
+
const request: StateStoreProxy.Request<
|
|
57
|
+
typeof method,
|
|
58
|
+
{ chain: string[] }
|
|
59
|
+
> = {
|
|
60
|
+
method,
|
|
61
|
+
params,
|
|
62
|
+
context: { chain: this.scope.chain },
|
|
63
|
+
};
|
|
64
|
+
const response = await fetch(url, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
Authorization: `Bearer ${token}`,
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify(request),
|
|
71
|
+
});
|
|
72
|
+
if (!response.headers.get("Content-Type")?.includes("application/json")) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`[CloudflareStateStore] "${method}" request failed with status ${response.status}: Expected JSON response, but got ${response.headers.get("Content-Type")}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const json = (await response.json()) as StateStoreProxy.Response<
|
|
78
|
+
typeof method
|
|
79
|
+
>;
|
|
80
|
+
if (!json.success) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`[CloudflareStateStore] "${method}" request failed with status ${response.status}: ${json.error}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return json.result;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const provision = memoize(async (options: CloudflareStateStoreOptions) => {
|
|
91
|
+
const scriptName = options.scriptName ?? "alchemy-state-service";
|
|
92
|
+
const token =
|
|
93
|
+
options.stateToken ??
|
|
94
|
+
(await alchemy.secret.env(
|
|
95
|
+
"ALCHEMY_STATE_TOKEN",
|
|
96
|
+
undefined,
|
|
97
|
+
"Missing token for DOStateStore. Please set ALCHEMY_STATE_TOKEN in the environment or set the `stateToken` option in the DOStateStore constructor.",
|
|
98
|
+
));
|
|
99
|
+
|
|
100
|
+
const api = await createCloudflareApi(options);
|
|
101
|
+
const [bundle, settings, subdomain] = await Promise.all([
|
|
102
|
+
getInternalWorkerBundle("cloudflare-state-store"),
|
|
103
|
+
getWorkerSettings(api, scriptName),
|
|
104
|
+
getWorkerSubdomain(api, scriptName),
|
|
105
|
+
]);
|
|
106
|
+
if (!settings || !settings.tags.includes(bundle.tag) || options.forceUpdate) {
|
|
107
|
+
logger.log(
|
|
108
|
+
`[CloudflareStateStore] ${settings ? "Updating" : "Creating"}...`,
|
|
109
|
+
);
|
|
110
|
+
await putWorker(api, {
|
|
111
|
+
workerName: scriptName,
|
|
112
|
+
compatibilityDate: BUILD_DATE,
|
|
113
|
+
format: "esm",
|
|
114
|
+
scriptBundle: {
|
|
115
|
+
entrypoint: bundle.file.name,
|
|
116
|
+
files: [bundle.file],
|
|
117
|
+
hash: bundle.tag,
|
|
118
|
+
},
|
|
119
|
+
compatibilityFlags: [],
|
|
120
|
+
bindings: {
|
|
121
|
+
STORE: DurableObjectNamespace(scriptName, {
|
|
122
|
+
className: "Store",
|
|
123
|
+
sqlite: true,
|
|
124
|
+
}),
|
|
125
|
+
STATE_TOKEN: token,
|
|
126
|
+
},
|
|
127
|
+
tags: [bundle.tag],
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (!subdomain.enabled) {
|
|
131
|
+
await enableWorkerSubdomain(api, scriptName);
|
|
132
|
+
}
|
|
133
|
+
const url = `https://${scriptName}.${await getAccountSubdomain(api)}.workers.dev`;
|
|
134
|
+
await pollUntilReady(() =>
|
|
135
|
+
fetch(url, {
|
|
136
|
+
method: "HEAD",
|
|
137
|
+
headers: { Authorization: `Bearer ${token.unencrypted}` },
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
return { url, token: token.unencrypted };
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
async function pollUntilReady(fn: () => Promise<Response>) {
|
|
144
|
+
// This ensures the token is correct and the worker is ready to use.
|
|
145
|
+
let last: Response | undefined;
|
|
146
|
+
let delay = 1000;
|
|
147
|
+
for (let i = 0; i < 20; i++) {
|
|
148
|
+
const res = await fn();
|
|
149
|
+
if (res.ok) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (res.status === 401) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"[CloudflareStateStore] The token is invalid. Please check your ALCHEMY_STATE_TOKEN environment variable, or set `forceUpdate: true` in the CloudflareStateStore constructor to overwrite the current token.",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (!last) {
|
|
158
|
+
logger.log("[CloudflareStateStore] Waiting for deployment...");
|
|
159
|
+
}
|
|
160
|
+
last = res;
|
|
161
|
+
// Exponential backoff with jitter
|
|
162
|
+
const jitter = Math.random() * 0.1 * delay;
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
164
|
+
delay *= 1.5; // Increase the delay for next attempt
|
|
165
|
+
delay = Math.min(delay, 10000); // Cap at 10 seconds
|
|
166
|
+
}
|
|
167
|
+
throw new Error(
|
|
168
|
+
`[CloudflareStateStore] Failed to reach state store: ${last?.status} ${last?.statusText}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { drizzle, type RemoteCallback } from "drizzle-orm/sqlite-proxy";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { extractCloudflareResult } from "../cloudflare/api-response.ts";
|
|
4
|
+
import type { CloudflareApi, CloudflareApiOptions } from "../cloudflare/api.ts";
|
|
5
|
+
import type { Scope } from "../scope.ts";
|
|
6
|
+
import { memoize } from "../util/memoize.ts";
|
|
7
|
+
import { MIGRATIONS_DIRECTORY } from "./migrations.ts";
|
|
8
|
+
import { SQLiteStateStoreOperations } from "./operations.ts";
|
|
9
|
+
import { StateStoreProxy } from "./proxy.ts";
|
|
10
|
+
import * as schema from "./schema.ts";
|
|
11
|
+
|
|
12
|
+
export interface D1StateStoreOptions extends CloudflareApiOptions {
|
|
13
|
+
databaseName?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use `CloudflareStateStore` from `alchemy/state` instead.
|
|
18
|
+
*/
|
|
19
|
+
export class D1StateStore extends StateStoreProxy {
|
|
20
|
+
constructor(
|
|
21
|
+
scope: Scope,
|
|
22
|
+
private options: D1StateStoreOptions = {},
|
|
23
|
+
) {
|
|
24
|
+
super(scope);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async provision(): Promise<StateStoreProxy.Dispatch> {
|
|
28
|
+
const db = await createDatabaseClient(this.options);
|
|
29
|
+
const operations = new SQLiteStateStoreOperations(db, {
|
|
30
|
+
chain: this.scope.chain,
|
|
31
|
+
});
|
|
32
|
+
return operations.dispatch.bind(operations);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const createDatabaseClient = memoize(async (options: D1StateStoreOptions) => {
|
|
37
|
+
const { createCloudflareApi } = await import("../cloudflare/api.ts");
|
|
38
|
+
const api = await createCloudflareApi(options);
|
|
39
|
+
const database = await upsertDatabase(
|
|
40
|
+
api,
|
|
41
|
+
options.databaseName ?? "alchemy-state",
|
|
42
|
+
);
|
|
43
|
+
const remoteCallback: RemoteCallback = async (sql, params) => {
|
|
44
|
+
const [result] = await extractCloudflareResult<
|
|
45
|
+
{
|
|
46
|
+
results: { columns: string[]; rows: any[][] };
|
|
47
|
+
}[]
|
|
48
|
+
>(
|
|
49
|
+
"execute D1 query",
|
|
50
|
+
api.post(
|
|
51
|
+
`/accounts/${api.accountId}/d1/database/${database.id}/raw`,
|
|
52
|
+
{ sql, params },
|
|
53
|
+
{
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
assert(result, "Missing result");
|
|
61
|
+
return {
|
|
62
|
+
rows: Object.values(result.results.rows),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
return drizzle(remoteCallback, {
|
|
66
|
+
schema,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const upsertDatabase = async (api: CloudflareApi, databaseName: string) => {
|
|
71
|
+
const { listDatabases, createDatabase } = await import(
|
|
72
|
+
"../cloudflare/d1-database.ts"
|
|
73
|
+
);
|
|
74
|
+
const { applyMigrations, listMigrationsFiles } = await import(
|
|
75
|
+
"../cloudflare/d1-migrations.ts"
|
|
76
|
+
);
|
|
77
|
+
const migrate = async (databaseId: string) => {
|
|
78
|
+
await applyMigrations({
|
|
79
|
+
migrationsFiles: await listMigrationsFiles(MIGRATIONS_DIRECTORY),
|
|
80
|
+
migrationsTable: "migrations",
|
|
81
|
+
accountId: api.accountId,
|
|
82
|
+
databaseId,
|
|
83
|
+
api,
|
|
84
|
+
quiet: true,
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
const databases = await listDatabases(api, databaseName);
|
|
88
|
+
if (databases[0]) {
|
|
89
|
+
await migrate(databases[0].id);
|
|
90
|
+
return {
|
|
91
|
+
id: databases[0].id,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const res = await createDatabase(api, databaseName, {
|
|
95
|
+
readReplication: { mode: "disabled" },
|
|
96
|
+
});
|
|
97
|
+
assert(res.result.uuid, "Missing UUID for database");
|
|
98
|
+
await migrate(res.result.uuid);
|
|
99
|
+
return {
|
|
100
|
+
id: res.result.uuid,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { alchemy } from "../../alchemy.ts";
|
|
2
|
+
import {
|
|
3
|
+
createCloudflareApi,
|
|
4
|
+
type CloudflareApiOptions,
|
|
5
|
+
} from "../../cloudflare/api.ts";
|
|
6
|
+
import { getAccountSubdomain } from "../../cloudflare/worker-subdomain.ts";
|
|
2
7
|
import { ResourceScope } from "../../resource.ts";
|
|
3
8
|
import type { Scope } from "../../scope.ts";
|
|
4
|
-
import { serialize } from "../../serde.ts";
|
|
9
|
+
import { deserialize, serialize } from "../../serde.ts";
|
|
5
10
|
import type { State, StateStore } from "../../state.ts";
|
|
6
|
-
import {
|
|
7
|
-
import { createCloudflareApi, type CloudflareApiOptions } from "../api.ts";
|
|
8
|
-
import { getAccountSubdomain } from "../worker/shared.ts";
|
|
9
|
-
import { DOStateStoreClient, upsertStateStoreWorker } from "./internal.ts";
|
|
11
|
+
import { DOFSStateStoreClient, upsertStateStoreWorker } from "./store.ts";
|
|
10
12
|
|
|
11
|
-
export interface
|
|
13
|
+
export interface DOFSStateStoreOptions extends CloudflareApiOptions {
|
|
12
14
|
/**
|
|
13
15
|
* The prefix to use for state keys.
|
|
14
16
|
* Each app and stage has its own state store, so this is primarily for testing.
|
|
@@ -45,13 +47,18 @@ export interface DOStateStoreOptions extends CloudflareApiOptions {
|
|
|
45
47
|
};
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
/**
|
|
51
|
+
* A state store backed by a {@link https://www.npmjs.com/package/dofs Durable Object File System (DOFS)}.
|
|
52
|
+
*
|
|
53
|
+
* @deprecated This state store is no longer recommended for production use. Please use {@link https://alchemy.run/guides/cloudflare-state-store/ CloudflareStateStore} from `alchemy/state` instead, which offers better reliability and performance.
|
|
54
|
+
*/
|
|
55
|
+
export class DOFSStateStore implements StateStore {
|
|
49
56
|
private prefix: string;
|
|
50
|
-
private client?: Promise<
|
|
57
|
+
private client?: Promise<DOFSStateStoreClient>;
|
|
51
58
|
|
|
52
59
|
constructor(
|
|
53
60
|
private readonly scope: Scope,
|
|
54
|
-
private readonly options:
|
|
61
|
+
private readonly options: DOFSStateStoreOptions = {},
|
|
55
62
|
) {
|
|
56
63
|
this.prefix = [options.prefix ?? "alchemy", ...scope.chain, ""].join("/");
|
|
57
64
|
}
|
|
@@ -61,7 +68,7 @@ export class DOStateStore implements StateStore {
|
|
|
61
68
|
this.options.worker?.token ??
|
|
62
69
|
(await alchemy.secret.env.ALCHEMY_STATE_TOKEN).unencrypted;
|
|
63
70
|
if (this.options.worker && "url" in this.options.worker) {
|
|
64
|
-
const client = new
|
|
71
|
+
const client = new DOFSStateStoreClient({
|
|
65
72
|
app: this.scope.appName ?? "alchemy",
|
|
66
73
|
stage: this.scope.stage,
|
|
67
74
|
url: this.options.worker.url,
|
|
@@ -75,11 +82,11 @@ export class DOStateStore implements StateStore {
|
|
|
75
82
|
}
|
|
76
83
|
if (res.status === 401) {
|
|
77
84
|
throw new Error(
|
|
78
|
-
"A worker URL was provided to
|
|
85
|
+
"A worker URL was provided to DOFSStateStore, but the token is incorrect. Correct the token or remove the worker URL to create a new worker.",
|
|
79
86
|
);
|
|
80
87
|
}
|
|
81
88
|
throw new Error(
|
|
82
|
-
`A worker URL was provided to
|
|
89
|
+
`A worker URL was provided to DOFSStateStore, but the worker status is ${res.status} ${res.statusText}.`,
|
|
83
90
|
);
|
|
84
91
|
}
|
|
85
92
|
const workerName = this.options.worker?.name ?? "alchemy-state";
|
|
@@ -98,7 +105,7 @@ export class DOStateStore implements StateStore {
|
|
|
98
105
|
"Failed to access state store worker because the workers.dev subdomain is not available.",
|
|
99
106
|
);
|
|
100
107
|
}
|
|
101
|
-
const client = new
|
|
108
|
+
const client = new DOFSStateStoreClient({
|
|
102
109
|
app: this.scope.appName ?? "alchemy",
|
|
103
110
|
stage: this.scope.stage,
|
|
104
111
|
url: `https://${workerName}.${subdomain}.workers.dev`,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { handleApiError } from "../../cloudflare/api-error.ts";
|
|
2
|
+
import type { CloudflareApi } from "../../cloudflare/api.ts";
|
|
3
|
+
import { getInternalWorkerBundle } from "../../cloudflare/bundle/internal-worker-bundle.ts";
|
|
4
|
+
import type { WorkerMetadata } from "../../cloudflare/worker-metadata.ts";
|
|
1
5
|
import { logger } from "../../util/logger.ts";
|
|
2
6
|
import { withExponentialBackoff } from "../../util/retry.ts";
|
|
3
|
-
import {
|
|
4
|
-
import type { CloudflareApi } from "../api.ts";
|
|
5
|
-
import type { WorkerMetadata } from "../worker-metadata.ts";
|
|
6
|
-
import { getWorkerTemplate } from "../worker/shared.ts";
|
|
7
|
-
import type { DOStateStoreAPI } from "./types.ts";
|
|
7
|
+
import type { DOStateStoreAPI } from "./api.ts";
|
|
8
8
|
|
|
9
9
|
interface DOStateStoreClientOptions {
|
|
10
10
|
app: string;
|
|
@@ -22,7 +22,7 @@ class StateStoreError extends Error {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export class
|
|
25
|
+
export class DOFSStateStoreClient {
|
|
26
26
|
constructor(private readonly options: DOStateStoreClientOptions) {}
|
|
27
27
|
|
|
28
28
|
async rpc<T extends keyof DOStateStoreAPI.API>(
|
|
@@ -141,13 +141,13 @@ export async function upsertStateStoreWorker(
|
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
const formData = new FormData();
|
|
144
|
-
const
|
|
145
|
-
formData.append(
|
|
144
|
+
const { file } = await getInternalWorkerBundle("dofs-state-store");
|
|
145
|
+
formData.append(file.name, file);
|
|
146
146
|
formData.append(
|
|
147
147
|
"metadata",
|
|
148
148
|
new Blob([
|
|
149
149
|
JSON.stringify({
|
|
150
|
-
main_module:
|
|
150
|
+
main_module: file.name,
|
|
151
151
|
compatibility_date: "2025-06-01",
|
|
152
152
|
compatibility_flags: ["nodejs_compat"],
|
|
153
153
|
bindings: [
|
|
@@ -222,21 +222,3 @@ async function getWorkerStatus(api: CloudflareApi, workerName: string) {
|
|
|
222
222
|
tag: json.result.tags.find((tag) => tag.startsWith("alchemy-state-store:")),
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
|
-
|
|
226
|
-
export async function getAccountSubdomain(api: CloudflareApi) {
|
|
227
|
-
const key = `subdomain:${api.accountId}`;
|
|
228
|
-
const cached = cache.get(key);
|
|
229
|
-
if (cached) {
|
|
230
|
-
return cached;
|
|
231
|
-
}
|
|
232
|
-
const res = await api.get(`/accounts/${api.accountId}/workers/subdomain`);
|
|
233
|
-
if (!res.ok) {
|
|
234
|
-
throw new Error(
|
|
235
|
-
`Failed to get account subdomain: ${res.status} ${res.statusText}: ${await res.text().catch(() => "unknown error")}`,
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
const json: { result: { subdomain: string } } = await res.json();
|
|
239
|
-
const subdomain = json.result.subdomain;
|
|
240
|
-
cache.set(key, subdomain);
|
|
241
|
-
return subdomain;
|
|
242
|
-
}
|