alchemy 0.44.3 → 0.45.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/bin/alchemy.mjs +0 -13
- package/bin/commands/create.ts +1 -10
- package/bin/services/vibe-rules.ts +0 -14
- package/lib/alchemy.d.ts +3 -3
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +5 -2
- package/lib/alchemy.js.map +1 -1
- package/lib/build-date.d.ts +1 -1
- package/lib/build-date.js +1 -1
- package/lib/cloudflare/auth.js +1 -1
- package/lib/cloudflare/auth.js.map +1 -1
- package/lib/cloudflare/bundle/esbuild.d.ts +16 -0
- package/lib/cloudflare/bundle/esbuild.d.ts.map +1 -0
- package/lib/cloudflare/bundle/esbuild.js +159 -0
- package/lib/cloudflare/bundle/esbuild.js.map +1 -0
- package/lib/cloudflare/bundle/fs.d.ts +18 -0
- package/lib/cloudflare/bundle/fs.d.ts.map +1 -0
- package/lib/cloudflare/bundle/fs.js +75 -0
- package/lib/cloudflare/bundle/fs.js.map +1 -0
- package/lib/cloudflare/bundle/index.d.ts +3 -0
- package/lib/cloudflare/bundle/index.d.ts.map +1 -0
- package/lib/cloudflare/bundle/index.js +2 -0
- package/lib/cloudflare/bundle/index.js.map +1 -0
- package/lib/cloudflare/bundle/inline.d.ts +12 -0
- package/lib/cloudflare/bundle/inline.d.ts.map +1 -0
- package/lib/cloudflare/bundle/inline.js +32 -0
- package/lib/cloudflare/bundle/inline.js.map +1 -0
- package/lib/cloudflare/bundle/normalize.d.ts +21 -0
- package/lib/cloudflare/bundle/normalize.d.ts.map +1 -0
- package/lib/cloudflare/bundle/normalize.js +49 -0
- package/lib/cloudflare/bundle/normalize.js.map +1 -0
- package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.d.ts → plugin-compat-warning.d.ts} +2 -2
- package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +1 -0
- package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.js → plugin-compat-warning.js} +3 -4
- package/lib/cloudflare/bundle/plugin-compat-warning.js.map +1 -0
- package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +8 -0
- package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -0
- package/lib/cloudflare/bundle/plugin-hot-reload.js +19 -0
- package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -0
- package/lib/cloudflare/bundle/{nodejs-compat.d.ts → plugin-hybrid-node-compat.d.ts} +2 -2
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -0
- package/lib/cloudflare/bundle/{nodejs-compat.js → plugin-hybrid-node-compat.js} +10 -10
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -0
- package/lib/cloudflare/bundle/plugin-wasm.d.ts +3 -0
- package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -0
- package/lib/cloudflare/bundle/plugin-wasm.js +55 -0
- package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -0
- package/lib/cloudflare/bundle/shared.d.ts +30 -0
- package/lib/cloudflare/bundle/shared.d.ts.map +1 -0
- package/lib/cloudflare/bundle/shared.js +34 -0
- package/lib/cloudflare/bundle/shared.js.map +1 -0
- package/lib/cloudflare/bundle/{nodejs-compat-mode.d.ts → validate-node-compat.d.ts} +8 -4
- package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -0
- package/lib/cloudflare/bundle/{nodejs-compat-mode.js → validate-node-compat.js} +27 -7
- package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -0
- package/lib/cloudflare/container.d.ts +231 -0
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +7 -0
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/d1-migrations.d.ts +1 -0
- package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
- package/lib/cloudflare/d1-migrations.js +18 -6
- package/lib/cloudflare/d1-migrations.js.map +1 -1
- package/lib/cloudflare/d1-state-store.d.ts +10 -0
- package/lib/cloudflare/d1-state-store.d.ts.map +1 -0
- package/lib/cloudflare/d1-state-store.js +70 -0
- package/lib/cloudflare/d1-state-store.js.map +1 -0
- package/lib/cloudflare/do-state-store/internal.js +1 -1
- package/lib/cloudflare/do-state-store/internal.js.map +1 -1
- package/lib/cloudflare/do-state-store/store.js +1 -1
- package/lib/cloudflare/do-state-store/store.js.map +1 -1
- package/lib/cloudflare/index.d.ts +3 -2
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +3 -2
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +1 -0
- package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.js → local-dev-cloudflare-shim.js} +1 -1
- package/lib/cloudflare/local-dev-cloudflare-shim.js.map +1 -0
- package/lib/cloudflare/miniflare/http-server.d.ts.map +1 -0
- package/lib/cloudflare/miniflare/http-server.js.map +1 -0
- package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.d.ts +4 -3
- package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -0
- package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.js +6 -3
- package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -0
- package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -0
- package/lib/cloudflare/{worker → miniflare}/miniflare.js +1 -1
- package/lib/cloudflare/miniflare/miniflare.js.map +1 -0
- package/lib/cloudflare/miniflare/mixed-mode.d.ts.map +1 -0
- package/lib/cloudflare/{worker → miniflare}/mixed-mode.js +1 -2
- package/lib/cloudflare/miniflare/mixed-mode.js.map +1 -0
- package/lib/cloudflare/user.js +1 -1
- package/lib/cloudflare/user.js.map +1 -1
- package/lib/cloudflare/worker/shared.d.ts +4 -0
- package/lib/cloudflare/worker/shared.d.ts.map +1 -0
- package/lib/cloudflare/worker/{get-worker-template.js → shared.js} +10 -1
- package/lib/cloudflare/worker/shared.js.map +1 -0
- package/lib/cloudflare/worker/tail.d.ts +18 -0
- package/lib/cloudflare/worker/tail.d.ts.map +1 -0
- package/lib/cloudflare/worker/tail.js +140 -0
- package/lib/cloudflare/worker/tail.js.map +1 -0
- package/lib/cloudflare/worker-metadata.d.ts +6 -2
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +7 -23
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker-stub.d.ts.map +1 -1
- package/lib/cloudflare/worker-stub.js +8 -4
- package/lib/cloudflare/worker-stub.js.map +1 -1
- package/lib/cloudflare/worker-subdomain.d.ts +31 -0
- package/lib/cloudflare/worker-subdomain.d.ts.map +1 -0
- package/lib/cloudflare/worker-subdomain.js +55 -0
- package/lib/cloudflare/worker-subdomain.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +30 -28
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +641 -554
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/fs/file-system-state-store.js +1 -1
- package/lib/fs/file-system-state-store.js.map +1 -1
- package/lib/scope.d.ts +2 -2
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +23 -8
- package/lib/scope.js.map +1 -1
- package/lib/serde.d.ts +1 -1
- package/lib/serde.d.ts.map +1 -1
- package/lib/serde.js +4 -4
- package/lib/serde.js.map +1 -1
- package/lib/sqlite/base-sqlite-state-store.d.ts +31 -0
- package/lib/sqlite/base-sqlite-state-store.d.ts.map +1 -0
- package/lib/sqlite/base-sqlite-state-store.js +124 -0
- package/lib/sqlite/base-sqlite-state-store.js.map +1 -0
- package/lib/sqlite/index.d.ts +2 -0
- package/lib/sqlite/index.d.ts.map +1 -0
- package/lib/sqlite/index.js +2 -0
- package/lib/sqlite/index.js.map +1 -0
- package/lib/sqlite/schema.d.ts +198 -0
- package/lib/sqlite/schema.d.ts.map +1 -0
- package/lib/sqlite/schema.js +23 -0
- package/lib/sqlite/schema.js.map +1 -0
- package/lib/sqlite/sqlite-state-store.d.ts +60 -0
- package/lib/sqlite/sqlite-state-store.d.ts.map +1 -0
- package/lib/sqlite/sqlite-state-store.js +83 -0
- package/lib/sqlite/sqlite-state-store.js.map +1 -0
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +0 -6
- package/lib/test/bun.js.map +1 -1
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +19 -5
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/memoize.d.ts +1 -1
- package/lib/util/memoize.d.ts.map +1 -1
- package/lib/util/memoize.js +11 -1
- package/lib/util/memoize.js.map +1 -1
- package/package.json +12 -3
- package/src/alchemy.ts +8 -6
- package/src/build-date.ts +1 -1
- package/src/cloudflare/auth.ts +1 -1
- package/src/cloudflare/bundle/esbuild.ts +194 -0
- package/src/cloudflare/bundle/fs.ts +98 -0
- package/src/cloudflare/bundle/index.ts +6 -0
- package/src/cloudflare/bundle/inline.ts +42 -0
- package/src/cloudflare/bundle/normalize.ts +76 -0
- package/src/cloudflare/bundle/{nodejs-import-warning-plugin.ts → plugin-compat-warning.ts} +4 -3
- package/src/cloudflare/bundle/plugin-hot-reload.ts +27 -0
- package/src/cloudflare/bundle/{nodejs-compat.ts → plugin-hybrid-node-compat.ts} +10 -10
- package/src/cloudflare/bundle/plugin-wasm.ts +62 -0
- package/src/cloudflare/bundle/shared.ts +73 -0
- package/src/cloudflare/bundle/{nodejs-compat-mode.ts → validate-node-compat.ts} +41 -16
- package/src/cloudflare/container.ts +296 -0
- package/src/cloudflare/d1-migrations.ts +29 -16
- package/src/cloudflare/d1-state-store.ts +105 -0
- package/src/cloudflare/do-state-store/internal.ts +1 -1
- package/src/cloudflare/do-state-store/store.ts +1 -1
- package/src/cloudflare/index.ts +3 -2
- package/src/cloudflare/{bundle/local-dev-cloudflare-shim.ts → local-dev-cloudflare-shim.ts} +1 -1
- package/src/cloudflare/{worker → miniflare}/miniflare-worker-options.ts +14 -6
- package/src/cloudflare/{worker → miniflare}/miniflare.ts +1 -1
- package/src/cloudflare/{worker → miniflare}/mixed-mode.ts +1 -2
- package/src/cloudflare/user.ts +1 -1
- package/src/cloudflare/worker/{subdomain.ts → shared.ts} +14 -0
- package/src/cloudflare/worker/tail.ts +287 -0
- package/src/cloudflare/worker-metadata.ts +18 -26
- package/src/cloudflare/worker-stub.ts +9 -4
- package/src/cloudflare/worker-subdomain.ts +123 -0
- package/src/cloudflare/worker.ts +863 -734
- package/src/fs/file-system-state-store.ts +1 -1
- package/src/scope.ts +30 -11
- package/src/serde.ts +4 -4
- package/src/sqlite/base-sqlite-state-store.ts +199 -0
- package/src/sqlite/index.ts +1 -0
- package/src/sqlite/schema.ts +37 -0
- package/src/sqlite/sqlite-state-store.ts +174 -0
- package/src/test/bun.ts +0 -8
- package/src/test/vitest.ts +19 -8
- package/src/util/memoize.ts +13 -1
- package/templates/astro/types/env.d.ts +1 -1
- package/templates/nuxt/types/env.d.ts +1 -1
- package/templates/react-router/types/env.d.ts +1 -1
- package/templates/rwsdk/types/env.d.ts +1 -1
- package/templates/sveltekit/types/env.d.ts +1 -1
- package/templates/tanstack-start/types/env.d.ts +1 -1
- package/templates/typescript/types/env.d.ts +1 -1
- package/templates/vite/types/env.d.ts +1 -1
- package/lib/cloudflare/bundle/alias-plugin.d.ts +0 -6
- package/lib/cloudflare/bundle/alias-plugin.d.ts.map +0 -1
- package/lib/cloudflare/bundle/alias-plugin.js +0 -36
- package/lib/cloudflare/bundle/alias-plugin.js.map +0 -1
- package/lib/cloudflare/bundle/build-failures.d.ts +0 -12
- package/lib/cloudflare/bundle/build-failures.d.ts.map +0 -1
- package/lib/cloudflare/bundle/build-failures.js +0 -45
- package/lib/cloudflare/bundle/build-failures.js.map +0 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +0 -26
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +0 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.js +0 -88
- package/lib/cloudflare/bundle/bundle-worker-dev.js.map +0 -1
- package/lib/cloudflare/bundle/bundle-worker.d.ts +0 -13
- package/lib/cloudflare/bundle/bundle-worker.d.ts.map +0 -1
- package/lib/cloudflare/bundle/bundle-worker.js +0 -98
- package/lib/cloudflare/bundle/bundle-worker.js.map +0 -1
- package/lib/cloudflare/bundle/external.d.ts +0 -4
- package/lib/cloudflare/bundle/external.d.ts.map +0 -1
- package/lib/cloudflare/bundle/external.js +0 -40
- package/lib/cloudflare/bundle/external.js.map +0 -1
- package/lib/cloudflare/bundle/local-dev-cloudflare-shim.d.ts.map +0 -1
- package/lib/cloudflare/bundle/local-dev-cloudflare-shim.js.map +0 -1
- package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +0 -1
- package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +0 -1
- package/lib/cloudflare/bundle/nodejs-compat.d.ts.map +0 -1
- package/lib/cloudflare/bundle/nodejs-compat.js.map +0 -1
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts.map +0 -1
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js.map +0 -1
- package/lib/cloudflare/bundle/wasm-plugin.d.ts +0 -3
- package/lib/cloudflare/bundle/wasm-plugin.d.ts.map +0 -1
- package/lib/cloudflare/bundle/wasm-plugin.js +0 -53
- package/lib/cloudflare/bundle/wasm-plugin.js.map +0 -1
- package/lib/cloudflare/worker/get-worker-template.d.ts +0 -2
- package/lib/cloudflare/worker/get-worker-template.d.ts.map +0 -1
- package/lib/cloudflare/worker/get-worker-template.js.map +0 -1
- package/lib/cloudflare/worker/http-server.d.ts.map +0 -1
- package/lib/cloudflare/worker/http-server.js.map +0 -1
- package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +0 -1
- package/lib/cloudflare/worker/miniflare-worker-options.js.map +0 -1
- package/lib/cloudflare/worker/miniflare.d.ts.map +0 -1
- package/lib/cloudflare/worker/miniflare.js.map +0 -1
- package/lib/cloudflare/worker/mixed-mode.d.ts.map +0 -1
- package/lib/cloudflare/worker/mixed-mode.js.map +0 -1
- package/lib/cloudflare/worker/subdomain.d.ts +0 -3
- package/lib/cloudflare/worker/subdomain.d.ts.map +0 -1
- package/lib/cloudflare/worker/subdomain.js +0 -10
- package/lib/cloudflare/worker/subdomain.js.map +0 -1
- package/src/cloudflare/bundle/alias-plugin.ts +0 -48
- package/src/cloudflare/bundle/build-failures.ts +0 -55
- package/src/cloudflare/bundle/bundle-worker-dev.ts +0 -129
- package/src/cloudflare/bundle/bundle-worker.ts +0 -150
- package/src/cloudflare/bundle/external.ts +0 -41
- package/src/cloudflare/bundle/wasm-plugin.ts +0 -57
- package/src/cloudflare/worker/get-worker-template.ts +0 -14
- /package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.d.ts → local-dev-cloudflare-shim.d.ts} +0 -0
- /package/lib/cloudflare/{worker → miniflare}/http-server.d.ts +0 -0
- /package/lib/cloudflare/{worker → miniflare}/http-server.js +0 -0
- /package/lib/cloudflare/{worker → miniflare}/miniflare.d.ts +0 -0
- /package/lib/cloudflare/{worker → miniflare}/mixed-mode.d.ts +0 -0
- /package/src/cloudflare/{worker → miniflare}/http-server.ts +0 -0
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ResourceScope } from "../resource.ts";
|
|
4
4
|
import type { Scope } from "../scope.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { deserialize, serialize } from "../serde.ts";
|
|
6
6
|
import type { State, StateStore } from "../state.ts";
|
|
7
7
|
import { ignore } from "../util/ignore.ts";
|
|
8
8
|
|
package/src/scope.ts
CHANGED
|
@@ -1,6 +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";
|
|
4
5
|
import { DOStateStore } from "./cloudflare/do-state-store/index.ts";
|
|
5
6
|
import { destroy, destroyAll } from "./destroy.ts";
|
|
6
7
|
import { FileSystemStateStore } from "./fs/file-system-state-store.ts";
|
|
@@ -38,7 +39,7 @@ export interface ScopeOptions {
|
|
|
38
39
|
stateStore?: StateStoreType;
|
|
39
40
|
quiet?: boolean;
|
|
40
41
|
phase?: Phase;
|
|
41
|
-
dev?:
|
|
42
|
+
dev?: "prefer-local" | "prefer-remote";
|
|
42
43
|
telemetryClient?: ITelemetryClient;
|
|
43
44
|
logger?: LoggerApi;
|
|
44
45
|
}
|
|
@@ -107,7 +108,7 @@ export class Scope {
|
|
|
107
108
|
public readonly stateStore: StateStoreType;
|
|
108
109
|
public readonly quiet: boolean;
|
|
109
110
|
public readonly phase: Phase;
|
|
110
|
-
public readonly dev?:
|
|
111
|
+
public readonly dev?: "prefer-local" | "prefer-remote";
|
|
111
112
|
public readonly logger: LoggerApi;
|
|
112
113
|
public readonly telemetryClient: ITelemetryClient;
|
|
113
114
|
public readonly dataMutex: AsyncMutex;
|
|
@@ -162,21 +163,16 @@ export class Scope {
|
|
|
162
163
|
options.logger,
|
|
163
164
|
);
|
|
164
165
|
|
|
165
|
-
this.dev = options.dev ?? this.parent?.dev
|
|
166
|
+
this.dev = options.dev ?? this.parent?.dev;
|
|
166
167
|
|
|
167
168
|
if (this.dev) {
|
|
168
169
|
this.logger.warnOnce(
|
|
169
|
-
"
|
|
170
|
+
"Development mode is in beta. Please report any issues to https://github.com/sam-goodwin/alchemy/issues.",
|
|
170
171
|
);
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
this.stateStore =
|
|
174
|
-
options.stateStore ??
|
|
175
|
-
this.parent?.stateStore ??
|
|
176
|
-
((scope) =>
|
|
177
|
-
process.env.ALCHEMY_STATE_STORE === "cloudflare"
|
|
178
|
-
? new DOStateStore(scope)
|
|
179
|
-
: new FileSystemStateStore(scope));
|
|
175
|
+
options.stateStore ?? this.parent?.stateStore ?? defaultStateStore;
|
|
180
176
|
this.state = this.stateStore(this);
|
|
181
177
|
if (!options.telemetryClient && !this.parent?.telemetryClient) {
|
|
182
178
|
throw new Error("Telemetry client is required");
|
|
@@ -206,11 +202,23 @@ export class Scope {
|
|
|
206
202
|
}
|
|
207
203
|
|
|
208
204
|
public get chain(): string[] {
|
|
205
|
+
// Since the root scope name is the same as the app name, this ensures
|
|
206
|
+
// the root scope chain is "<app-name>" instead of "<app-name>/<app-name>".
|
|
207
|
+
if (
|
|
208
|
+
!this.parent &&
|
|
209
|
+
this.appName &&
|
|
210
|
+
this.scopeName &&
|
|
211
|
+
this.appName === this.scopeName
|
|
212
|
+
) {
|
|
213
|
+
return [this.appName];
|
|
214
|
+
}
|
|
215
|
+
|
|
209
216
|
const thisScope = this.scopeName ? [this.scopeName] : [];
|
|
210
|
-
const app = this.appName ? [this.appName] : [];
|
|
211
217
|
if (this.parent) {
|
|
212
218
|
return [...this.parent.chain, ...thisScope];
|
|
213
219
|
}
|
|
220
|
+
|
|
221
|
+
const app = this.appName ? [this.appName] : [];
|
|
214
222
|
return [...app, ...thisScope];
|
|
215
223
|
}
|
|
216
224
|
|
|
@@ -489,6 +497,17 @@ export class Scope {
|
|
|
489
497
|
}
|
|
490
498
|
}
|
|
491
499
|
|
|
500
|
+
const defaultStateStore: StateStoreType = (scope: Scope) => {
|
|
501
|
+
switch (process.env.ALCHEMY_STATE_STORE) {
|
|
502
|
+
case "cloudflare-d1":
|
|
503
|
+
return new D1StateStore(scope);
|
|
504
|
+
case "cloudflare":
|
|
505
|
+
return new DOStateStore(scope);
|
|
506
|
+
default:
|
|
507
|
+
return new FileSystemStateStore(scope);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
492
511
|
declare global {
|
|
493
512
|
// for runtime
|
|
494
513
|
// TODO(sam): maybe inject is a better way to achieve this
|
package/src/serde.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ResourceKind,
|
|
6
6
|
type Resource,
|
|
7
7
|
} from "./resource.ts";
|
|
8
|
-
import { isScope, Scope } from "./scope.ts";
|
|
8
|
+
import { isScope, type Scope } from "./scope.ts";
|
|
9
9
|
import { isSecret, Secret } from "./secret.ts";
|
|
10
10
|
|
|
11
11
|
import type { Type } from "arktype";
|
|
@@ -77,7 +77,7 @@ export async function serializeScope(scope: Scope): Promise<SerializedScope> {
|
|
|
77
77
|
async function serializeScope(scope: Scope): Promise<SerializedScope> {
|
|
78
78
|
await Promise.all(
|
|
79
79
|
Array.from(scope.resources.values()).map(async (resource) => {
|
|
80
|
-
if (resource[ResourceKind] === Scope
|
|
80
|
+
if (resource[ResourceKind] === "alchemy::Scope") {
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
map[resource[ResourceFQN]] = await serialize(scope, await resource, {
|
|
@@ -121,7 +121,7 @@ export async function serialize(
|
|
|
121
121
|
if (!scope.password) {
|
|
122
122
|
throw new Error(
|
|
123
123
|
"Cannot serialize secret without password, did you forget to set password when initializing your alchemy app?\n" +
|
|
124
|
-
"See: https://alchemy.run/
|
|
124
|
+
"See: https://alchemy.run/concepts/secret/#encryption-password",
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
127
|
return {
|
|
@@ -220,7 +220,7 @@ export async function deserialize(
|
|
|
220
220
|
if (!scope.password) {
|
|
221
221
|
throw new Error(
|
|
222
222
|
"Cannot deserialize secret without password, did you forget to set password when initializing your alchemy app?\n" +
|
|
223
|
-
"See: https://alchemy.run/
|
|
223
|
+
"See: https://alchemy.run/concepts/secret/#encryption-password",
|
|
224
224
|
);
|
|
225
225
|
}
|
|
226
226
|
if (typeof value["@secret"] === "object") {
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { and, eq, getTableColumns, inArray } from "drizzle-orm";
|
|
2
|
+
import type { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { Scope } from "../scope.ts";
|
|
5
|
+
import type { State, StateStore } from "../state.ts";
|
|
6
|
+
import * as schema from "./schema.ts";
|
|
7
|
+
|
|
8
|
+
type Database = BaseSQLiteDatabase<any, any, typeof schema>;
|
|
9
|
+
|
|
10
|
+
export interface SQLiteStateStoreOptions<T extends Database = Database> {
|
|
11
|
+
create: () => Promise<T>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { scope: _, ...columns } = getTableColumns(schema.resources);
|
|
15
|
+
|
|
16
|
+
export class BaseSQLiteStateStore<T extends Database = Database>
|
|
17
|
+
implements StateStore
|
|
18
|
+
{
|
|
19
|
+
private chain: string[];
|
|
20
|
+
private create: () => Promise<T>;
|
|
21
|
+
private dbPromise?: Promise<T>;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private readonly scope: Scope,
|
|
25
|
+
options: SQLiteStateStoreOptions<T>,
|
|
26
|
+
) {
|
|
27
|
+
this.chain = this.scope.chain;
|
|
28
|
+
this.create = options.create;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private async db() {
|
|
32
|
+
this.dbPromise ??= withExponentialBackoff("init", () => this.create());
|
|
33
|
+
return await this.dbPromise;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async init() {
|
|
37
|
+
await this.db();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async list() {
|
|
41
|
+
const ids = await this.run("list", (db) =>
|
|
42
|
+
db
|
|
43
|
+
.select({ id: schema.resources.id })
|
|
44
|
+
.from(schema.resources)
|
|
45
|
+
.where(eq(schema.resources.scope, this.chain)),
|
|
46
|
+
);
|
|
47
|
+
return ids.map((id) => id.id);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async count() {
|
|
51
|
+
return await this.run("count", (db) =>
|
|
52
|
+
db.$count(
|
|
53
|
+
db
|
|
54
|
+
.select({ id: schema.resources.id })
|
|
55
|
+
.from(schema.resources)
|
|
56
|
+
.where(eq(schema.resources.scope, this.chain)),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async get(id: string) {
|
|
62
|
+
const [state] = await this.run("get", (db) =>
|
|
63
|
+
db
|
|
64
|
+
.select(columns)
|
|
65
|
+
.from(schema.resources)
|
|
66
|
+
.where(
|
|
67
|
+
and(
|
|
68
|
+
eq(schema.resources.id, id),
|
|
69
|
+
eq(schema.resources.scope, this.chain),
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
if (!state) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
return await this.deserialize(state);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getBatch(ids: string[]) {
|
|
80
|
+
const states = await this.run("getBatch", async (db) =>
|
|
81
|
+
db
|
|
82
|
+
.select(columns)
|
|
83
|
+
.from(schema.resources)
|
|
84
|
+
.where(
|
|
85
|
+
and(
|
|
86
|
+
inArray(schema.resources.id, ids),
|
|
87
|
+
eq(schema.resources.scope, this.chain),
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
return await this.deserializeMany(states);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async all() {
|
|
95
|
+
const states = await this.run("all", async (db) =>
|
|
96
|
+
db
|
|
97
|
+
.select(columns)
|
|
98
|
+
.from(schema.resources)
|
|
99
|
+
.where(eq(schema.resources.scope, this.chain)),
|
|
100
|
+
);
|
|
101
|
+
return await this.deserializeMany(states);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async set(_: string, state: State) {
|
|
105
|
+
const serialized = await this.serialize(state);
|
|
106
|
+
await this.run("set", (db) =>
|
|
107
|
+
db
|
|
108
|
+
.insert(schema.resources)
|
|
109
|
+
.values({
|
|
110
|
+
...serialized,
|
|
111
|
+
scope: this.chain,
|
|
112
|
+
})
|
|
113
|
+
.onConflictDoUpdate({
|
|
114
|
+
target: [schema.resources.id, schema.resources.scope],
|
|
115
|
+
set: serialized,
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async delete(id: string) {
|
|
121
|
+
await this.run("delete", async (db) =>
|
|
122
|
+
db
|
|
123
|
+
.delete(schema.resources)
|
|
124
|
+
.where(
|
|
125
|
+
and(
|
|
126
|
+
eq(schema.resources.id, id),
|
|
127
|
+
eq(schema.resources.scope, this.chain),
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async run<U>(
|
|
134
|
+
label: string,
|
|
135
|
+
fn: (db: T) => Promise<U>,
|
|
136
|
+
attempt = 0,
|
|
137
|
+
): Promise<U> {
|
|
138
|
+
const db = await this.db();
|
|
139
|
+
return await withExponentialBackoff(label, () => fn(db), attempt);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async deserialize(
|
|
143
|
+
state: Omit<schema.Resource, "scope">,
|
|
144
|
+
): Promise<State> {
|
|
145
|
+
const { deserialize } = await import("../serde.ts"); // dynamic import to avoid circular dependency
|
|
146
|
+
return await deserialize(this.scope, {
|
|
147
|
+
...state,
|
|
148
|
+
oldProps: state.oldProps ?? undefined,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private async serialize(state: State) {
|
|
153
|
+
const { serialize } = await import("../serde.ts");
|
|
154
|
+
return await serialize(this.scope, state);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async deserializeMany(states: Omit<schema.Resource, "scope">[]) {
|
|
158
|
+
const deserialized = await Promise.all(
|
|
159
|
+
states.map((state) => this.deserialize(state)),
|
|
160
|
+
);
|
|
161
|
+
const map: Record<string, State> = {};
|
|
162
|
+
for (const state of deserialized) {
|
|
163
|
+
map[state.id] = state;
|
|
164
|
+
}
|
|
165
|
+
return map;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const withExponentialBackoff = async <T>(
|
|
170
|
+
label: string,
|
|
171
|
+
fn: () => Promise<T>,
|
|
172
|
+
attempt = 0,
|
|
173
|
+
): Promise<T> => {
|
|
174
|
+
try {
|
|
175
|
+
const result = await fn();
|
|
176
|
+
return result;
|
|
177
|
+
} catch (cause) {
|
|
178
|
+
if (attempt < 10) {
|
|
179
|
+
await delay(attempt);
|
|
180
|
+
return withExponentialBackoff(label, fn, attempt + 1);
|
|
181
|
+
}
|
|
182
|
+
throw new Error(
|
|
183
|
+
`[SQLiteStateStore] Failed to run "${label}" after 10 attempts`,
|
|
184
|
+
{
|
|
185
|
+
cause,
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const delay = (attempt: number, initialDelay = 100, jitterFactor = 0.2) => {
|
|
192
|
+
const baseDelay = initialDelay * 2 ** attempt;
|
|
193
|
+
const jitter = Math.floor(Math.random() * baseDelay * jitterFactor);
|
|
194
|
+
return new Promise((resolve) => setTimeout(resolve, baseDelay + jitter));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const resolveMigrationsPath = () => {
|
|
198
|
+
return path.join(import.meta.dirname, "..", "..", "drizzle");
|
|
199
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./sqlite-state-store.ts";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
integer,
|
|
3
|
+
primaryKey,
|
|
4
|
+
sqliteTable,
|
|
5
|
+
text,
|
|
6
|
+
} from "drizzle-orm/sqlite-core";
|
|
7
|
+
import type {
|
|
8
|
+
Resource as AlchemyResource,
|
|
9
|
+
ResourceProps,
|
|
10
|
+
} from "../resource.ts";
|
|
11
|
+
|
|
12
|
+
export const resources = sqliteTable(
|
|
13
|
+
"resources",
|
|
14
|
+
{
|
|
15
|
+
id: text("id").notNull(),
|
|
16
|
+
scope: text("scope", { mode: "json" }).$type<string[]>().notNull(),
|
|
17
|
+
status: text("status", {
|
|
18
|
+
enum: [
|
|
19
|
+
"creating",
|
|
20
|
+
"created",
|
|
21
|
+
"updating",
|
|
22
|
+
"updated",
|
|
23
|
+
"deleting",
|
|
24
|
+
"deleted",
|
|
25
|
+
],
|
|
26
|
+
}).notNull(),
|
|
27
|
+
kind: text("kind").notNull(),
|
|
28
|
+
fqn: text("fqn").notNull(),
|
|
29
|
+
seq: integer("seq").notNull(),
|
|
30
|
+
data: text("data", { mode: "json" }).notNull().$type<Record<string, any>>(),
|
|
31
|
+
props: text("props", { mode: "json" }).$type<ResourceProps>(),
|
|
32
|
+
oldProps: text("oldProps", { mode: "json" }).$type<ResourceProps>(),
|
|
33
|
+
output: text("output", { mode: "json" }).notNull().$type<AlchemyResource>(),
|
|
34
|
+
},
|
|
35
|
+
(t) => [primaryKey({ columns: [t.scope, t.id] })],
|
|
36
|
+
);
|
|
37
|
+
export type Resource = typeof resources.$inferSelect;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { Config as LibSQLConfig } from "@libsql/client";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { Scope } from "../scope.ts";
|
|
5
|
+
import { memoize } from "../util/memoize.ts";
|
|
6
|
+
import {
|
|
7
|
+
BaseSQLiteStateStore,
|
|
8
|
+
resolveMigrationsPath,
|
|
9
|
+
} from "./base-sqlite-state-store.ts";
|
|
10
|
+
import * as schema from "./schema.ts";
|
|
11
|
+
|
|
12
|
+
interface BunSQLiteStateStoreOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Use `bun:sqlite` to connect to the SQLite database.
|
|
15
|
+
* Requires Bun.
|
|
16
|
+
*/
|
|
17
|
+
engine: "bun";
|
|
18
|
+
/**
|
|
19
|
+
* The filename to use for the SQLite database.
|
|
20
|
+
* @default process.env.ALCHEMY_STATE_FILE if set, otherwise ".alchemy/state.sqlite"
|
|
21
|
+
*/
|
|
22
|
+
filename?: string;
|
|
23
|
+
|
|
24
|
+
// Options are copied from Bun instead of inherited because Bun's type is not exported,
|
|
25
|
+
// and the constructor type isn't an interface so inheritance doesn't work.
|
|
26
|
+
|
|
27
|
+
readonly?: boolean;
|
|
28
|
+
create?: boolean;
|
|
29
|
+
readwrite?: boolean;
|
|
30
|
+
safeIntegers?: boolean;
|
|
31
|
+
strict?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface LibSQLStateStoreOptions extends Omit<LibSQLConfig, "url"> {
|
|
35
|
+
/**
|
|
36
|
+
* Use the `@libsql/client` library to connect to the SQLite database.
|
|
37
|
+
* Supported on Node.js and Bun.
|
|
38
|
+
*/
|
|
39
|
+
engine: "libsql";
|
|
40
|
+
/**
|
|
41
|
+
* The filename to use for the SQLite database.
|
|
42
|
+
* @default process.env.ALCHEMY_STATE_FILE if set, otherwise ".alchemy/state.sqlite"
|
|
43
|
+
* @note If the `url` option is specified, this option is ignored.
|
|
44
|
+
*/
|
|
45
|
+
filename?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The database URL. Overrides the `filename` option.
|
|
48
|
+
*
|
|
49
|
+
* The client supports `libsql:`, `http:`/`https:`, `ws:`/`wss:` and `file:` URL. For more infomation,
|
|
50
|
+
* please refer to the project README:
|
|
51
|
+
*
|
|
52
|
+
* https://github.com/libsql/libsql-client-ts#supported-urls
|
|
53
|
+
*/
|
|
54
|
+
url?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface AutoSQLiteStateStoreOptions {
|
|
58
|
+
/**
|
|
59
|
+
* Automatically choose the best SQLite engine based on your environment.
|
|
60
|
+
* @default "auto" - Uses `bun:sqlite` if available, otherwise uses `@libsql/client`.
|
|
61
|
+
*/
|
|
62
|
+
engine?: "auto";
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The filename to use for the SQLite database.
|
|
66
|
+
* @default ".alchemy/state.sqlite"
|
|
67
|
+
*/
|
|
68
|
+
filename?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type SQLiteStateStoreOptions =
|
|
72
|
+
| BunSQLiteStateStoreOptions
|
|
73
|
+
| LibSQLStateStoreOptions
|
|
74
|
+
| AutoSQLiteStateStoreOptions;
|
|
75
|
+
|
|
76
|
+
export class SQLiteStateStore extends BaseSQLiteStateStore {
|
|
77
|
+
constructor(scope: Scope, options?: SQLiteStateStoreOptions) {
|
|
78
|
+
super(scope, {
|
|
79
|
+
create: async () => createDatabase(options),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const createDatabase = memoize(
|
|
85
|
+
async (options: SQLiteStateStoreOptions | undefined) => {
|
|
86
|
+
switch (options?.engine) {
|
|
87
|
+
case "bun":
|
|
88
|
+
return await createBunSQLiteDatabase(options);
|
|
89
|
+
case "libsql":
|
|
90
|
+
return await createLibSQLDatabase(options);
|
|
91
|
+
default: {
|
|
92
|
+
return await createDefaultDatabase(options?.filename);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
async function createDefaultDatabase(filename: string | undefined) {
|
|
99
|
+
if ("Bun" in globalThis) {
|
|
100
|
+
return createBunSQLiteDatabase({ engine: "bun", filename });
|
|
101
|
+
}
|
|
102
|
+
return createLibSQLDatabase({ engine: "libsql", filename });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function createBunSQLiteDatabase(
|
|
106
|
+
options: BunSQLiteStateStoreOptions | undefined,
|
|
107
|
+
) {
|
|
108
|
+
if (!("Bun" in globalThis)) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"[SQLiteStateStore] The `engine: 'bun'` option is only available in Bun. Please use `engine: 'libsql'` instead.",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const filename =
|
|
115
|
+
options?.filename ??
|
|
116
|
+
process.env.ALCHEMY_STATE_FILE ??
|
|
117
|
+
".alchemy/state.sqlite";
|
|
118
|
+
ensureDirectory(filename);
|
|
119
|
+
const { Database } = await import("bun:sqlite");
|
|
120
|
+
const { drizzle } = await import("drizzle-orm/bun-sqlite").catch(() => {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"[SQLiteStateStore] Missing `drizzle-orm` peer dependency. Please `bun install drizzle-orm`.",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
const { migrate } = await import("drizzle-orm/bun-sqlite/migrator");
|
|
126
|
+
// Bun's constructor throws if we pass in an empty object or if extraneous
|
|
127
|
+
// options are passed in, so here's some ugly destructuring!
|
|
128
|
+
const { engine: _engine, filename: _filename, ...rest } = options ?? {};
|
|
129
|
+
const bunOptions = Object.keys(rest).length > 0 ? rest : undefined;
|
|
130
|
+
const client = new Database(filename, bunOptions);
|
|
131
|
+
client.exec("PRAGMA journal_mode = WAL;");
|
|
132
|
+
const db = drizzle(client, {
|
|
133
|
+
schema,
|
|
134
|
+
});
|
|
135
|
+
migrate(db, { migrationsFolder: resolveMigrationsPath() });
|
|
136
|
+
return db;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function createLibSQLDatabase(
|
|
140
|
+
options: LibSQLStateStoreOptions | undefined,
|
|
141
|
+
) {
|
|
142
|
+
const url =
|
|
143
|
+
options?.url ??
|
|
144
|
+
`file:${options?.filename ?? process.env.ALCHEMY_STATE_FILE ?? ".alchemy/state.sqlite"}`;
|
|
145
|
+
const filename = url.startsWith("file:") ? url.slice(5) : undefined;
|
|
146
|
+
if (filename) {
|
|
147
|
+
ensureDirectory(filename);
|
|
148
|
+
}
|
|
149
|
+
const { createClient } = await import("@libsql/client").catch(() => {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"[SQLiteStateStore] Missing `@libsql/client` peer dependency. Please `npm install @libsql/client`.",
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
const { drizzle } = await import("drizzle-orm/libsql").catch(() => {
|
|
155
|
+
throw new Error(
|
|
156
|
+
"[SQLiteStateStore] Missing `drizzle-orm` peer dependency. Please `npm install drizzle-orm`.",
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
const { migrate } = await import("drizzle-orm/libsql/migrator");
|
|
160
|
+
const client = createClient({ url, ...options });
|
|
161
|
+
await client.execute("PRAGMA journal_mode = WAL;");
|
|
162
|
+
const db = drizzle(client, {
|
|
163
|
+
schema,
|
|
164
|
+
});
|
|
165
|
+
await migrate(db, { migrationsFolder: resolveMigrationsPath() });
|
|
166
|
+
return db;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const ensureDirectory = (filename: string) => {
|
|
170
|
+
const dir = path.dirname(filename);
|
|
171
|
+
if (!fs.existsSync(dir)) {
|
|
172
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
};
|
package/src/test/bun.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { afterAll, beforeAll, it } from "bun:test";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { alchemy } from "../alchemy.ts";
|
|
6
|
-
import { DOStateStore } from "../cloudflare/index.ts";
|
|
7
6
|
import { Scope } from "../scope.ts";
|
|
8
7
|
import type { StateStoreType } from "../state.ts";
|
|
9
8
|
import { NoopTelemetryClient } from "../util/telemetry/index.ts";
|
|
@@ -120,13 +119,6 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
|
120
119
|
defaultOptions = defaultOptions ?? {
|
|
121
120
|
quiet: true,
|
|
122
121
|
};
|
|
123
|
-
if (
|
|
124
|
-
defaultOptions.stateStore === undefined &&
|
|
125
|
-
// process.env.CI &&
|
|
126
|
-
process.env.ALCHEMY_STATE_STORE === "cloudflare"
|
|
127
|
-
) {
|
|
128
|
-
defaultOptions.stateStore = (scope) => new DOStateStore(scope);
|
|
129
|
-
}
|
|
130
122
|
|
|
131
123
|
// Add skipIf functionality
|
|
132
124
|
test.skipIf = it.skipIf.bind(it);
|
package/src/test/vitest.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { afterAll, beforeAll, it } from "vitest";
|
|
3
3
|
import { alchemy } from "../alchemy.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { D1StateStore } from "../cloudflare/d1-state-store.ts";
|
|
5
|
+
import { DOStateStore } from "../cloudflare/do-state-store/index.ts";
|
|
6
|
+
import { FileSystemStateStore } from "../fs/file-system-state-store.ts";
|
|
5
7
|
import { Scope } from "../scope.ts";
|
|
8
|
+
import { SQLiteStateStore } from "../sqlite/sqlite-state-store.ts";
|
|
6
9
|
import type { StateStoreType } from "../state.ts";
|
|
7
10
|
import { NoopTelemetryClient } from "../util/telemetry/client.ts";
|
|
8
11
|
|
|
@@ -123,13 +126,21 @@ export function test(
|
|
|
123
126
|
if (!("quiet" in defaultOptions)) {
|
|
124
127
|
defaultOptions.quiet = true;
|
|
125
128
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
defaultOptions.stateStore ??= (scope) => {
|
|
130
|
+
const storeType = process.env.ALCHEMY_STATE_STORE;
|
|
131
|
+
switch (storeType) {
|
|
132
|
+
case "dofs":
|
|
133
|
+
return new DOStateStore(scope);
|
|
134
|
+
case "fs":
|
|
135
|
+
return new FileSystemStateStore(scope);
|
|
136
|
+
case "d1":
|
|
137
|
+
return new D1StateStore(scope);
|
|
138
|
+
default:
|
|
139
|
+
return new SQLiteStateStore(scope, {
|
|
140
|
+
filename: `.alchemy/${path.relative(process.cwd(), meta.filename)}.sqlite`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
133
144
|
|
|
134
145
|
test.skipIf = (condition: boolean) => {
|
|
135
146
|
if (condition) {
|
package/src/util/memoize.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R>
|
|
2
4
|
? R
|
|
3
5
|
: T;
|
|
4
6
|
|
|
5
7
|
export function memoize<F extends (...args: any[]) => Promise<any>>(
|
|
6
8
|
fn: F,
|
|
7
|
-
keyFn: (...args: Parameters<F>) => string,
|
|
9
|
+
keyFn: (...args: Parameters<F>) => string = defaultKeyFn,
|
|
8
10
|
) {
|
|
9
11
|
const cache = new Map<string, Promise<AsyncReturnType<F>>>();
|
|
10
12
|
return async (...args: Parameters<F>): Promise<AsyncReturnType<F>> => {
|
|
@@ -21,3 +23,13 @@ export function memoize<F extends (...args: any[]) => Promise<any>>(
|
|
|
21
23
|
return await promise;
|
|
22
24
|
};
|
|
23
25
|
}
|
|
26
|
+
|
|
27
|
+
const defaultKeyFn = (...args: any[]) => {
|
|
28
|
+
const hash = createHash("sha256");
|
|
29
|
+
args.forEach((arg) => {
|
|
30
|
+
if (arg) {
|
|
31
|
+
hash.update(JSON.stringify(arg));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return hash.digest("hex");
|
|
35
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This file infers types for the cloudflare:workers environment from your Alchemy Worker.
|
|
2
|
-
// @see https://alchemy.run/
|
|
2
|
+
// @see https://alchemy.run/concepts/bindings/#type-safe-bindings
|
|
3
3
|
|
|
4
4
|
import type { worker } from "../alchemy.run.ts";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This file infers types for the cloudflare:workers environment from your Alchemy Worker.
|
|
2
|
-
// @see https://alchemy.run/
|
|
2
|
+
// @see https://alchemy.run/concepts/bindings/#type-safe-bindings
|
|
3
3
|
|
|
4
4
|
import type { worker } from "../alchemy.run.ts";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This file infers types for the cloudflare:workers environment from your Alchemy Worker.
|
|
2
|
-
// @see https://alchemy.run/
|
|
2
|
+
// @see https://alchemy.run/concepts/bindings/#type-safe-bindings
|
|
3
3
|
|
|
4
4
|
import type { worker } from "../alchemy.run.ts";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This file infers types for the cloudflare:workers environment from your Alchemy Worker.
|
|
2
|
-
// @see https://alchemy.run/
|
|
2
|
+
// @see https://alchemy.run/concepts/bindings/#type-safe-bindings
|
|
3
3
|
|
|
4
4
|
import type { worker } from "../alchemy.run.ts";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This file infers types for the cloudflare:workers environment from your Alchemy Worker.
|
|
2
|
-
// @see https://alchemy.run/
|
|
2
|
+
// @see https://alchemy.run/concepts/bindings/#type-safe-bindings
|
|
3
3
|
|
|
4
4
|
import type { worker } from "../alchemy.run.ts";
|
|
5
5
|
|