alchemy 0.65.1 → 0.67.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.js +139 -79
- package/bin/commands/init.ts +6 -3
- package/bin/services/cdp-manager/server.ts +3 -2
- package/bin/services/execute-alchemy.ts +33 -3
- package/bin/services/find-workspace-root.ts +48 -0
- package/lib/alchemy.d.ts +20 -3
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +18 -5
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +51 -1
- package/lib/apply.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +17 -5
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js +3 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +4 -3
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +1 -1
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bundle/internal-worker-bundle.d.ts +1 -1
- package/lib/cloudflare/bundle/internal-worker-bundle.d.ts.map +1 -1
- package/lib/cloudflare/bundle/internal-worker-bundle.js.map +1 -1
- package/lib/cloudflare/cloudflare-env-proxy.js +3 -3
- package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +2 -1
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/d1-local-migrations.d.ts +1 -0
- package/lib/cloudflare/d1-local-migrations.d.ts.map +1 -1
- package/lib/cloudflare/d1-local-migrations.js +3 -2
- package/lib/cloudflare/d1-local-migrations.js.map +1 -1
- package/lib/cloudflare/hyperdrive-ref.d.ts +23 -0
- package/lib/cloudflare/hyperdrive-ref.d.ts.map +1 -0
- package/lib/cloudflare/hyperdrive-ref.js +52 -0
- package/lib/cloudflare/hyperdrive-ref.js.map +1 -0
- package/lib/cloudflare/index.d.ts +2 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +2 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/kv-namespace.js +1 -1
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts +2 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +11 -6
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/delete.d.ts +3 -2
- package/lib/cloudflare/miniflare/delete.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/delete.js +13 -12
- package/lib/cloudflare/miniflare/delete.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-controller.d.ts +3 -1
- package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-controller.js +29 -13
- package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +24 -13
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +95 -79
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
- package/lib/cloudflare/miniflare/paths.d.ts +4 -4
- package/lib/cloudflare/miniflare/paths.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/paths.js +10 -4
- package/lib/cloudflare/miniflare/paths.js.map +1 -1
- package/lib/cloudflare/miniflare/symlink-miniflare-state.d.ts +2 -0
- package/lib/cloudflare/miniflare/symlink-miniflare-state.d.ts.map +1 -0
- package/lib/cloudflare/miniflare/symlink-miniflare-state.js +19 -0
- package/lib/cloudflare/miniflare/symlink-miniflare-state.js.map +1 -0
- package/lib/cloudflare/miniflare/tunnel.d.ts +18 -0
- package/lib/cloudflare/miniflare/tunnel.d.ts.map +1 -0
- package/lib/cloudflare/miniflare/tunnel.js +79 -0
- package/lib/cloudflare/miniflare/tunnel.js.map +1 -0
- package/lib/cloudflare/next.d.ts.map +1 -1
- package/lib/cloudflare/next.js +3 -0
- package/lib/cloudflare/next.js.map +1 -1
- package/lib/cloudflare/nuxt/plugin.d.ts.map +1 -1
- package/lib/cloudflare/nuxt/plugin.js +3 -3
- package/lib/cloudflare/nuxt/plugin.js.map +1 -1
- package/lib/cloudflare/quick-tunnel.d.ts +1 -1
- package/lib/cloudflare/redwood/plugin.js +2 -2
- package/lib/cloudflare/redwood/plugin.js.map +1 -1
- package/lib/cloudflare/secret-ref.d.ts +38 -0
- package/lib/cloudflare/secret-ref.d.ts.map +1 -0
- package/lib/cloudflare/secret-ref.js +38 -0
- package/lib/cloudflare/secret-ref.js.map +1 -0
- package/lib/cloudflare/vite/plugin.d.ts.map +1 -1
- package/lib/cloudflare/vite/plugin.js +6 -3
- package/lib/cloudflare/vite/plugin.js.map +1 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +5 -16
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +7 -9
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +16 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +16 -2
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +2 -1
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +5 -6
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/llms.cloudflare.txt +1 -1
- package/lib/scope.d.ts +26 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +12 -5
- package/lib/scope.js.map +1 -1
- package/lib/state/file-system-state-store.d.ts.map +1 -1
- package/lib/state/file-system-state-store.js +1 -2
- package/lib/state/file-system-state-store.js.map +1 -1
- package/lib/state/sqlite-state-store.d.ts +11 -0
- package/lib/state/sqlite-state-store.d.ts.map +1 -1
- package/lib/state/sqlite-state-store.js +22 -14
- package/lib/state/sqlite-state-store.js.map +1 -1
- package/lib/stripe/customer.d.ts +0 -8
- package/lib/stripe/customer.d.ts.map +1 -1
- package/lib/stripe/customer.js +0 -6
- package/lib/stripe/customer.js.map +1 -1
- package/lib/stripe/price.d.ts +23 -23
- package/lib/stripe/price.d.ts.map +1 -1
- package/lib/stripe/price.js +6 -22
- package/lib/stripe/price.js.map +1 -1
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +1 -1
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/assert-never.js +1 -1
- package/lib/util/assert-never.js.map +1 -1
- package/lib/util/cli.d.ts.map +1 -1
- package/lib/util/cli.js +12 -13
- package/lib/util/cli.js.map +1 -1
- package/lib/util/idempotent-spawn.d.ts.map +1 -1
- package/lib/util/idempotent-spawn.js +5 -8
- package/lib/util/idempotent-spawn.js.map +1 -1
- package/lib/util/root-dir.d.ts +2 -0
- package/lib/util/root-dir.d.ts.map +1 -0
- package/lib/util/root-dir.js +2 -0
- package/lib/util/root-dir.js.map +1 -0
- package/package.json +4 -4
- package/src/alchemy.ts +43 -8
- package/src/apply.ts +58 -3
- package/src/cloudflare/bindings.ts +21 -4
- package/src/cloudflare/bound.ts +50 -52
- package/src/cloudflare/bucket.ts +1 -1
- package/src/cloudflare/bundle/internal-worker-bundle.ts +2 -1
- package/src/cloudflare/cloudflare-env-proxy.ts +5 -5
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/d1-database.ts +2 -1
- package/src/cloudflare/d1-local-migrations.ts +10 -6
- package/src/cloudflare/hyperdrive-ref.ts +98 -0
- package/src/cloudflare/index.ts +2 -0
- package/src/cloudflare/kv-namespace.ts +1 -1
- package/src/cloudflare/miniflare/build-worker-options.ts +21 -12
- package/src/cloudflare/miniflare/delete.ts +19 -11
- package/src/cloudflare/miniflare/miniflare-controller.ts +33 -14
- package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +122 -102
- package/src/cloudflare/miniflare/paths.ts +11 -7
- package/src/cloudflare/miniflare/symlink-miniflare-state.ts +19 -0
- package/src/cloudflare/miniflare/tunnel.ts +116 -0
- package/src/cloudflare/next.ts +3 -0
- package/src/cloudflare/nuxt/plugin.ts +12 -3
- package/src/cloudflare/redwood/plugin.ts +2 -2
- package/src/cloudflare/secret-ref.ts +56 -0
- package/src/cloudflare/vite/plugin.ts +8 -3
- package/src/cloudflare/website.ts +5 -19
- package/src/cloudflare/worker-metadata.ts +14 -12
- package/src/cloudflare/worker.ts +36 -3
- package/src/cloudflare/wrangler.json.ts +14 -11
- package/src/llms.cloudflare.txt +1 -1
- package/src/scope.ts +38 -6
- package/src/state/file-system-state-store.ts +1 -2
- package/src/state/sqlite-state-store.ts +41 -14
- package/src/stripe/customer.ts +0 -14
- package/src/stripe/price.ts +59 -56
- package/src/test/vitest.ts +4 -1
- package/src/util/assert-never.ts +1 -1
- package/src/util/cli.ts +15 -13
- package/src/util/idempotent-spawn.ts +5 -6
- package/src/util/root-dir.ts +1 -0
- package/templates/nextjs/alchemy.run.ts +2 -2
- package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +1 -1
- package/templates/rwsdk/package.json +3 -3
- package/workers/cloudflare-state-store.js +1 -1
- package/workers/tunnel-proxy.js +110 -0
- package/workers/tunnel-proxy.ts +121 -0
package/src/cloudflare/bucket.ts
CHANGED
|
@@ -388,7 +388,7 @@ const _R2Bucket = Resource(
|
|
|
388
388
|
if (this.phase === "delete") {
|
|
389
389
|
if (props.delete !== false) {
|
|
390
390
|
if (this.output.dev?.id) {
|
|
391
|
-
await deleteMiniflareBinding("r2", this.output.dev.id);
|
|
391
|
+
await deleteMiniflareBinding(this.scope, "r2", this.output.dev.id);
|
|
392
392
|
}
|
|
393
393
|
if (props.empty) {
|
|
394
394
|
await emptyBucket(api, bucketName, props);
|
|
@@ -7,7 +7,8 @@ import type { WorkerBundle } from "../worker-bundle.ts";
|
|
|
7
7
|
export type InternalWorker =
|
|
8
8
|
| "cloudflare-state-store"
|
|
9
9
|
| "dofs-state-store"
|
|
10
|
-
| "remote-binding-proxy"
|
|
10
|
+
| "remote-binding-proxy"
|
|
11
|
+
| "tunnel-proxy";
|
|
11
12
|
|
|
12
13
|
export const getInternalWorkerBundle = memoize(async (name: InternalWorker) => {
|
|
13
14
|
const content = await fs.readFile(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GetPlatformProxyOptions } from "wrangler";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
getDefaultConfigPath,
|
|
4
|
+
getDefaultPersistPath,
|
|
5
5
|
validateConfigPath,
|
|
6
6
|
validatePersistPath,
|
|
7
7
|
} from "./miniflare/paths.ts";
|
|
@@ -26,7 +26,7 @@ export const getPlatformProxyOptions = (
|
|
|
26
26
|
typeof input.persist === "object" &&
|
|
27
27
|
typeof input.persist.path === "string"
|
|
28
28
|
? input.persist.path
|
|
29
|
-
:
|
|
29
|
+
: getDefaultPersistPath(),
|
|
30
30
|
),
|
|
31
31
|
};
|
|
32
32
|
if (!persist) {
|
|
@@ -39,12 +39,12 @@ export const getPlatformProxyOptions = (
|
|
|
39
39
|
}
|
|
40
40
|
return {
|
|
41
41
|
...input,
|
|
42
|
-
configPath: validateConfigPath(input.configPath ??
|
|
42
|
+
configPath: validateConfigPath(input.configPath ?? getDefaultConfigPath()),
|
|
43
43
|
persist,
|
|
44
44
|
experimental: (input as any).experimental ?? {
|
|
45
45
|
remoteBindings: true,
|
|
46
46
|
},
|
|
47
|
-
}
|
|
47
|
+
};
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
const warned = new Set<string>();
|
|
@@ -283,6 +283,7 @@ const _D1Database = Resource(
|
|
|
283
283
|
databaseId: dev.id,
|
|
284
284
|
migrationsTable: props.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE,
|
|
285
285
|
migrations: props.migrationsFiles,
|
|
286
|
+
rootDir: this.scope.rootDir,
|
|
286
287
|
});
|
|
287
288
|
}
|
|
288
289
|
return this({
|
|
@@ -301,7 +302,7 @@ const _D1Database = Resource(
|
|
|
301
302
|
|
|
302
303
|
if (this.phase === "delete") {
|
|
303
304
|
if (this.output.dev?.id) {
|
|
304
|
-
await deleteMiniflareBinding("d1", this.output.dev.id);
|
|
305
|
+
await deleteMiniflareBinding(this.scope, "d1", this.output.dev.id);
|
|
305
306
|
}
|
|
306
307
|
if (props.delete !== false && this.output?.id) {
|
|
307
308
|
await deleteDatabase(api, this.output.id);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as mf from "miniflare";
|
|
2
|
-
import
|
|
2
|
+
import { getDefaultPersistPath } from "./miniflare/paths.ts";
|
|
3
3
|
|
|
4
4
|
export interface D1LocalMigrationOptions {
|
|
5
|
+
rootDir: string;
|
|
5
6
|
databaseId: string;
|
|
6
7
|
migrationsTable: string;
|
|
7
8
|
migrations: { id: string; sql: string }[];
|
|
@@ -13,15 +14,16 @@ export const applyLocalD1Migrations = async (
|
|
|
13
14
|
const miniflare = new mf.Miniflare({
|
|
14
15
|
script: "",
|
|
15
16
|
modules: true,
|
|
16
|
-
defaultPersistRoot:
|
|
17
|
+
defaultPersistRoot: getDefaultPersistPath(options.rootDir),
|
|
17
18
|
d1Persist: true,
|
|
18
19
|
d1Databases: { DB: options.databaseId },
|
|
19
20
|
log: process.env.DEBUG ? new mf.Log(mf.LogLevel.DEBUG) : undefined,
|
|
20
21
|
});
|
|
21
22
|
try {
|
|
22
23
|
await miniflare.ready;
|
|
23
|
-
|
|
24
|
-
const
|
|
24
|
+
// TODO(sam): don't use `any` once prisma is fixed upstream
|
|
25
|
+
const db: any = await miniflare.getD1Database("DB");
|
|
26
|
+
const session: any = db.withSession("first-primary");
|
|
25
27
|
await session
|
|
26
28
|
.prepare(
|
|
27
29
|
`CREATE TABLE IF NOT EXISTS ${options.migrationsTable} (
|
|
@@ -31,11 +33,13 @@ export const applyLocalD1Migrations = async (
|
|
|
31
33
|
)`,
|
|
32
34
|
)
|
|
33
35
|
.run();
|
|
34
|
-
const appliedMigrations
|
|
36
|
+
const appliedMigrations: {
|
|
37
|
+
results: { name: string }[];
|
|
38
|
+
} = await session
|
|
35
39
|
.prepare(
|
|
36
40
|
`SELECT name FROM ${options.migrationsTable} ORDER BY applied_at ASC`,
|
|
37
41
|
)
|
|
38
|
-
.all
|
|
42
|
+
.all();
|
|
39
43
|
const insertRecord = session.prepare(
|
|
40
44
|
`INSERT INTO ${options.migrationsTable} (name) VALUES (?)`,
|
|
41
45
|
);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { alchemy } from "../alchemy.ts";
|
|
2
|
+
import type { Secret } from "../secret.ts";
|
|
3
|
+
import { handleApiError } from "./api-error.ts";
|
|
4
|
+
import {
|
|
5
|
+
createCloudflareApi,
|
|
6
|
+
type CloudflareApi,
|
|
7
|
+
type CloudflareApiOptions,
|
|
8
|
+
} from "./api.ts";
|
|
9
|
+
|
|
10
|
+
export interface HyperdriveRefProps extends CloudflareApiOptions {
|
|
11
|
+
name?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
dev?: {
|
|
14
|
+
origin: string | Secret;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type HyperdriveRef = {
|
|
19
|
+
type: "hyperdrive";
|
|
20
|
+
name: string;
|
|
21
|
+
hyperdriveId: string;
|
|
22
|
+
dev?: {
|
|
23
|
+
origin: Secret;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function HyperdriveRef(
|
|
28
|
+
props: HyperdriveRefProps,
|
|
29
|
+
): Promise<HyperdriveRef> {
|
|
30
|
+
const api = await createCloudflareApi(props);
|
|
31
|
+
|
|
32
|
+
let hyperdriveId = props.id;
|
|
33
|
+
let hyperdriveName = props.name;
|
|
34
|
+
|
|
35
|
+
if (!hyperdriveId && hyperdriveName) {
|
|
36
|
+
// Find hyperdrive by name
|
|
37
|
+
hyperdriveId = await findHyperdriveIdByName(api, hyperdriveName);
|
|
38
|
+
if (!hyperdriveId) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Hyperdrive config with name "${hyperdriveName}" not found`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
} else if (!hyperdriveName && hyperdriveId) {
|
|
44
|
+
throw new Error("HyperdriveRef requires either name or both name and id");
|
|
45
|
+
} else if (!hyperdriveName && !hyperdriveId) {
|
|
46
|
+
throw new Error("HyperdriveRef requires at least a name");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
type: "hyperdrive",
|
|
51
|
+
name: hyperdriveName!,
|
|
52
|
+
hyperdriveId: hyperdriveId!,
|
|
53
|
+
dev: props.dev
|
|
54
|
+
? {
|
|
55
|
+
origin:
|
|
56
|
+
typeof props.dev.origin === "string"
|
|
57
|
+
? alchemy.secret(props.dev.origin)
|
|
58
|
+
: props.dev.origin,
|
|
59
|
+
}
|
|
60
|
+
: undefined,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Find a Hyperdrive configuration by name
|
|
66
|
+
*/
|
|
67
|
+
export async function findHyperdriveIdByName(
|
|
68
|
+
api: CloudflareApi,
|
|
69
|
+
name: string,
|
|
70
|
+
page = 1,
|
|
71
|
+
): Promise<string | undefined> {
|
|
72
|
+
const response = await api.get(
|
|
73
|
+
`/accounts/${api.accountId}/hyperdrive/configs?page=${page}`,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
await handleApiError(response, "list", "hyperdrive", "all");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const data: {
|
|
81
|
+
result: Array<{
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
}>;
|
|
85
|
+
result_info?: { total_pages?: number };
|
|
86
|
+
} = await response.json();
|
|
87
|
+
|
|
88
|
+
const found = data.result.find((config) => config.name === name);
|
|
89
|
+
if (found) {
|
|
90
|
+
return found.id;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (data.result_info?.total_pages && page < data.result_info.total_pages) {
|
|
94
|
+
return await findHyperdriveIdByName(api, name, page + 1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from "./email-address.ts";
|
|
|
30
30
|
export * from "./email-catch-all.ts";
|
|
31
31
|
export * from "./email-routing.ts";
|
|
32
32
|
export * from "./email-rule.ts";
|
|
33
|
+
export * from "./hyperdrive-ref.ts";
|
|
33
34
|
export * from "./hyperdrive.ts";
|
|
34
35
|
export * from "./images.ts";
|
|
35
36
|
export * from "./kv-namespace.ts";
|
|
@@ -49,6 +50,7 @@ export * from "./route.ts";
|
|
|
49
50
|
export * from "./rule.ts";
|
|
50
51
|
export * from "./ruleset.ts";
|
|
51
52
|
export * from "./secret-key.ts";
|
|
53
|
+
export * from "./secret-ref.ts";
|
|
52
54
|
export * from "./secret.ts";
|
|
53
55
|
export * from "./secrets-store.ts";
|
|
54
56
|
export * from "./state.ts";
|
|
@@ -240,7 +240,7 @@ const _KVNamespace = Resource(
|
|
|
240
240
|
|
|
241
241
|
if (this.phase === "delete") {
|
|
242
242
|
if (this.output.dev?.id) {
|
|
243
|
-
await deleteMiniflareBinding("kv", this.output.dev.id);
|
|
243
|
+
await deleteMiniflareBinding(this.scope, "kv", this.output.dev.id);
|
|
244
244
|
}
|
|
245
245
|
if (this.output.namespaceId && props.delete !== false) {
|
|
246
246
|
await deleteKVNamespace(api, this.output.namespaceId);
|
|
@@ -2,12 +2,11 @@ import * as miniflare from "miniflare";
|
|
|
2
2
|
import { assertNever } from "../../util/assert-never.ts";
|
|
3
3
|
import type { HTTPServer } from "../../util/http.ts";
|
|
4
4
|
import type { CloudflareApi } from "../api.ts";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
type WorkerBindingSpec,
|
|
5
|
+
import type {
|
|
6
|
+
Binding,
|
|
7
|
+
Bindings,
|
|
8
|
+
WorkerBindingService,
|
|
9
|
+
WorkerBindingSpec,
|
|
11
10
|
} from "../bindings.ts";
|
|
12
11
|
import { isQueueEventSource, type EventSource } from "../event-source.ts";
|
|
13
12
|
import type { WorkerBundle, WorkerBundleSource } from "../worker-bundle.ts";
|
|
@@ -25,11 +24,11 @@ export interface MiniflareWorkerInput {
|
|
|
25
24
|
assets: AssetsConfig | undefined;
|
|
26
25
|
bundle: WorkerBundleSource;
|
|
27
26
|
port: number | undefined;
|
|
27
|
+
tunnel: boolean | undefined;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
type RemoteOnlyBindingType =
|
|
31
31
|
| "ai"
|
|
32
|
-
| "ai_gateway"
|
|
33
32
|
| "browser"
|
|
34
33
|
| "dispatch_namespace"
|
|
35
34
|
| "mtls_certificate"
|
|
@@ -92,13 +91,18 @@ export const buildWorkerOptions = async (
|
|
|
92
91
|
(options.bindings ??= {})[key] = binding;
|
|
93
92
|
continue;
|
|
94
93
|
}
|
|
95
|
-
if (binding === Self) {
|
|
94
|
+
if (binding.type === "cloudflare::Worker::Self") {
|
|
96
95
|
(options.serviceBindings ??= {})[key] = miniflare.kCurrentWorker;
|
|
97
96
|
continue;
|
|
98
97
|
}
|
|
99
98
|
switch (binding.type) {
|
|
100
|
-
case "ai":
|
|
101
|
-
|
|
99
|
+
case "ai": {
|
|
100
|
+
const existing = remoteBindings.find((b) => b.type === "ai");
|
|
101
|
+
if (existing) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Workers cannot have multiple AI bindings. Binding "${key}" conflicts with "${existing.name}".`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
102
106
|
remoteBindings.push({
|
|
103
107
|
type: "ai",
|
|
104
108
|
name: key,
|
|
@@ -180,7 +184,9 @@ export const buildWorkerOptions = async (
|
|
|
180
184
|
break;
|
|
181
185
|
}
|
|
182
186
|
case "hyperdrive": {
|
|
183
|
-
(
|
|
187
|
+
if (binding.dev) {
|
|
188
|
+
(options.hyperdrives ??= {})[key] = binding.dev.origin.unencrypted;
|
|
189
|
+
}
|
|
184
190
|
break;
|
|
185
191
|
}
|
|
186
192
|
case "images": {
|
|
@@ -459,7 +465,10 @@ const normalizeBundle = (bundle: WorkerBundle) => {
|
|
|
459
465
|
};
|
|
460
466
|
|
|
461
467
|
const isRemoteBinding = (binding: Binding) => {
|
|
462
|
-
if (
|
|
468
|
+
if (
|
|
469
|
+
typeof binding === "string" ||
|
|
470
|
+
binding.type === "cloudflare::Worker::Self"
|
|
471
|
+
) {
|
|
463
472
|
return false;
|
|
464
473
|
}
|
|
465
474
|
return (
|
|
@@ -2,11 +2,11 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import type { RmDirOptions } from "node:fs";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "pathe";
|
|
5
|
+
import type { Scope } from "../../scope.ts";
|
|
5
6
|
import type { DurableObjectNamespace } from "../durable-object-namespace.ts";
|
|
6
7
|
import type { Workflow } from "../workflow.ts";
|
|
7
|
-
import {
|
|
8
|
+
import { getDefaultPersistPath } from "./paths.ts";
|
|
8
9
|
|
|
9
|
-
const PERSIST_ROOT = path.resolve(DEFAULT_PERSIST_PATH);
|
|
10
10
|
const BINDING_TYPE_KEY = {
|
|
11
11
|
d1: "miniflare-D1DatabaseObject",
|
|
12
12
|
kv: "miniflare-KVNamespaceObject",
|
|
@@ -18,8 +18,14 @@ type BindingType = keyof typeof BINDING_TYPE_KEY;
|
|
|
18
18
|
/**
|
|
19
19
|
* Delete a binding from the miniflare directory.
|
|
20
20
|
*/
|
|
21
|
-
export async function deleteMiniflareBinding(
|
|
22
|
-
|
|
21
|
+
export async function deleteMiniflareBinding(
|
|
22
|
+
scope: Scope,
|
|
23
|
+
type: BindingType,
|
|
24
|
+
id: string,
|
|
25
|
+
) {
|
|
26
|
+
const persistRoot = path.resolve(getDefaultPersistPath(scope.rootDir));
|
|
27
|
+
|
|
28
|
+
const bindingPath = path.join(persistRoot, type, BINDING_TYPE_KEY[type]);
|
|
23
29
|
|
|
24
30
|
// delete durable object/sqlite files
|
|
25
31
|
const namespaceId = durableObjectNamespaceIdFromName(
|
|
@@ -33,29 +39,31 @@ export async function deleteMiniflareBinding(type: BindingType, id: string) {
|
|
|
33
39
|
await Promise.all(files.map((file) => fs.rm(path.join(bindingPath, file))));
|
|
34
40
|
|
|
35
41
|
// delete data directory, if present
|
|
36
|
-
await removeDirectory(path.join(
|
|
42
|
+
await removeDirectory(path.join(persistRoot, type, id), {
|
|
37
43
|
recursive: true,
|
|
38
44
|
});
|
|
39
45
|
|
|
40
46
|
// delete the miniflare directory if it is empty
|
|
41
|
-
await cleanMiniflareDirectory();
|
|
47
|
+
await cleanMiniflareDirectory(scope.dotAlchemy);
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
/**
|
|
45
51
|
* Delete local durable object and workflow data for a given Worker script.
|
|
46
52
|
*/
|
|
47
53
|
export async function deleteMiniflareWorkerData(
|
|
54
|
+
scope: Scope,
|
|
48
55
|
scriptName: string,
|
|
49
56
|
input: {
|
|
50
57
|
durableObjects: DurableObjectNamespace[];
|
|
51
58
|
workflows: Workflow[];
|
|
52
59
|
},
|
|
53
60
|
) {
|
|
61
|
+
const persistRoot = path.resolve(getDefaultPersistPath(scope.rootDir));
|
|
54
62
|
await Promise.all(
|
|
55
63
|
input.durableObjects.map((durableObject) =>
|
|
56
64
|
removeDirectory(
|
|
57
65
|
path.join(
|
|
58
|
-
|
|
66
|
+
persistRoot,
|
|
59
67
|
"do",
|
|
60
68
|
`${scriptName}-${durableObject.className}`,
|
|
61
69
|
),
|
|
@@ -69,7 +77,7 @@ export async function deleteMiniflareWorkerData(
|
|
|
69
77
|
input.workflows.map((workflow) =>
|
|
70
78
|
removeDirectory(
|
|
71
79
|
path.join(
|
|
72
|
-
|
|
80
|
+
persistRoot,
|
|
73
81
|
"workflows",
|
|
74
82
|
`miniflare-workflows-${workflow.workflowName}`,
|
|
75
83
|
),
|
|
@@ -79,14 +87,14 @@ export async function deleteMiniflareWorkerData(
|
|
|
79
87
|
),
|
|
80
88
|
),
|
|
81
89
|
);
|
|
82
|
-
await cleanMiniflareDirectory();
|
|
90
|
+
await cleanMiniflareDirectory(persistRoot);
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
/**
|
|
86
94
|
* Delete the miniflare directory iff it is empty.
|
|
87
95
|
*/
|
|
88
|
-
async function cleanMiniflareDirectory() {
|
|
89
|
-
const directory = path.resolve("
|
|
96
|
+
async function cleanMiniflareDirectory(dotAlchemy: string) {
|
|
97
|
+
const directory = path.resolve(dotAlchemy, "miniflare");
|
|
90
98
|
const files = await fs
|
|
91
99
|
.readdir(directory, {
|
|
92
100
|
recursive: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as miniflare from "miniflare";
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { Scope } from "../../scope.ts";
|
|
4
5
|
import { findOpenPort } from "../../util/find-open-port.ts";
|
|
5
6
|
import type { HTTPServer } from "../../util/http.ts";
|
|
6
7
|
import { logger } from "../../util/logger.ts";
|
|
@@ -9,8 +10,12 @@ import {
|
|
|
9
10
|
buildWorkerOptions,
|
|
10
11
|
type MiniflareWorkerInput,
|
|
11
12
|
} from "./build-worker-options.ts";
|
|
12
|
-
import {
|
|
13
|
-
|
|
13
|
+
import {
|
|
14
|
+
createMiniflareWorkerProxy,
|
|
15
|
+
type MiniflareWorkerProxy,
|
|
16
|
+
} from "./miniflare-worker-proxy.ts";
|
|
17
|
+
import { getDefaultPersistPath } from "./paths.ts";
|
|
18
|
+
import { createTunnel, type Tunnel } from "./tunnel.ts";
|
|
14
19
|
|
|
15
20
|
declare global {
|
|
16
21
|
var ALCHEMY_MINIFLARE_CONTROLLER: MiniflareController | undefined;
|
|
@@ -20,13 +25,14 @@ export class MiniflareController {
|
|
|
20
25
|
abort = new AbortController();
|
|
21
26
|
miniflare: miniflare.Miniflare | undefined;
|
|
22
27
|
options = new Map<string, miniflare.WorkerOptions>();
|
|
28
|
+
tunnel: Tunnel | undefined;
|
|
23
29
|
localProxies = new Map<string, MiniflareWorkerProxy>();
|
|
24
30
|
remoteProxies = new Map<string, HTTPServer>();
|
|
25
31
|
mutex = new AsyncMutex();
|
|
26
32
|
|
|
27
33
|
static get singleton() {
|
|
28
|
-
globalThis.ALCHEMY_MINIFLARE_CONTROLLER ??=
|
|
29
|
-
|
|
34
|
+
return (globalThis.ALCHEMY_MINIFLARE_CONTROLLER ??=
|
|
35
|
+
new MiniflareController());
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
async add(input: MiniflareWorkerInput) {
|
|
@@ -39,21 +45,32 @@ export class MiniflareController {
|
|
|
39
45
|
assert(first.value, "First value is undefined");
|
|
40
46
|
this.options.set(input.name, first.value);
|
|
41
47
|
const miniflare = await this.update();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
let url: URL;
|
|
49
|
+
if (input.tunnel) {
|
|
50
|
+
this.tunnel ??= await createTunnel(miniflare);
|
|
51
|
+
url = await this.tunnel.configureWorker({
|
|
52
|
+
api: input.api,
|
|
53
|
+
name: input.name,
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
const proxy = await createMiniflareWorkerProxy({
|
|
57
|
+
port: input.port ?? (await findOpenPort()),
|
|
58
|
+
getWorkerName: () => input.name,
|
|
59
|
+
miniflare,
|
|
60
|
+
mode: "local",
|
|
61
|
+
});
|
|
62
|
+
this.localProxies.set(input.name, proxy);
|
|
63
|
+
url = proxy.url;
|
|
64
|
+
}
|
|
48
65
|
void this.watch(input.id, watcher);
|
|
49
66
|
logger.task(input.id, {
|
|
50
|
-
message: `Ready at ${
|
|
67
|
+
message: `Ready at ${url}`,
|
|
51
68
|
status: "success",
|
|
52
69
|
resource: input.id,
|
|
53
70
|
prefix: "dev",
|
|
54
71
|
prefixColor: "cyanBright",
|
|
55
72
|
});
|
|
56
|
-
return
|
|
73
|
+
return url.toString();
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
private async watch(
|
|
@@ -77,12 +94,13 @@ export class MiniflareController {
|
|
|
77
94
|
return await this.mutex.lock(async () => {
|
|
78
95
|
const options: miniflare.MiniflareOptions = {
|
|
79
96
|
workers: [],
|
|
80
|
-
defaultPersistRoot: path.resolve(
|
|
97
|
+
defaultPersistRoot: path.resolve(
|
|
98
|
+
getDefaultPersistPath(Scope.current.rootDir),
|
|
99
|
+
),
|
|
81
100
|
unsafeDevRegistryPath: miniflare.getDefaultDevRegistryPath(),
|
|
82
101
|
log: process.env.DEBUG
|
|
83
102
|
? new miniflare.Log(miniflare.LogLevel.DEBUG)
|
|
84
103
|
: undefined,
|
|
85
|
-
|
|
86
104
|
// This is required to allow websites and other separate processes
|
|
87
105
|
// to detect Alchemy-managed Durable Objects via the Wrangler dev registry.
|
|
88
106
|
unsafeDevRegistryDurableObjectProxy: true,
|
|
@@ -143,6 +161,7 @@ export class MiniflareController {
|
|
|
143
161
|
this.abort.abort();
|
|
144
162
|
await Promise.all([
|
|
145
163
|
this.miniflare?.dispose(),
|
|
164
|
+
this.tunnel?.close(),
|
|
146
165
|
...this.localProxies.values().map((proxy) => proxy.close()),
|
|
147
166
|
...this.remoteProxies.values().map((proxy) => proxy.close()),
|
|
148
167
|
]);
|