alchemy 0.36.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cloudflare/bindings.d.ts +1 -1
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bound.d.ts +1 -1
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +0 -3
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/internal.js +35 -28
- package/lib/cloudflare/do-state-store/internal.js.map +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.js.map +1 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +28 -13
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +8 -5
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +48 -11
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +18 -12
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +74 -68
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/stripe/price.d.ts +24 -0
- package/lib/stripe/price.d.ts.map +1 -1
- package/lib/stripe/price.js +30 -0
- package/lib/stripe/price.js.map +1 -1
- package/package.json +1 -1
- package/src/cloudflare/bindings.ts +1 -1
- package/src/cloudflare/bound.ts +1 -1
- package/src/cloudflare/d1-database.ts +0 -4
- package/src/cloudflare/do-state-store/internal.ts +45 -29
- package/src/cloudflare/durable-object-namespace.ts +2 -3
- package/src/cloudflare/website.ts +40 -16
- package/src/cloudflare/worker-metadata.ts +101 -19
- package/src/cloudflare/worker.ts +104 -148
- package/src/stripe/price.ts +47 -1
|
@@ -48,7 +48,7 @@ export type Binding =
|
|
|
48
48
|
| D1DatabaseResource
|
|
49
49
|
| DispatchNamespaceResource
|
|
50
50
|
| AnalyticsEngineDataset
|
|
51
|
-
| DurableObjectNamespace<
|
|
51
|
+
| DurableObjectNamespace<any>
|
|
52
52
|
| HyperdriveResource
|
|
53
53
|
| Images
|
|
54
54
|
| KVNamespaceResource
|
package/src/cloudflare/bound.ts
CHANGED
|
@@ -36,7 +36,7 @@ type BoundWorker<
|
|
|
36
36
|
export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
|
|
37
37
|
infer O
|
|
38
38
|
>
|
|
39
|
-
? DurableObjectNamespace<O>
|
|
39
|
+
? DurableObjectNamespace<O & Rpc.DurableObjectBranded>
|
|
40
40
|
: T extends { type: "kv_namespace" }
|
|
41
41
|
? KVNamespace
|
|
42
42
|
: T extends WorkerStub<infer RPC>
|
|
@@ -241,13 +241,9 @@ const D1DatabaseResource = Resource(
|
|
|
241
241
|
const databaseName = props.name ?? id;
|
|
242
242
|
|
|
243
243
|
if (this.phase === "delete") {
|
|
244
|
-
logger.log("Deleting D1 database:", databaseName);
|
|
245
244
|
if (props.delete !== false) {
|
|
246
|
-
// Delete D1 database
|
|
247
|
-
logger.log("Deleting D1 database:", databaseName);
|
|
248
245
|
await deleteDatabase(api, this.output?.id);
|
|
249
246
|
}
|
|
250
|
-
|
|
251
247
|
// Return void (a deleted database has no content)
|
|
252
248
|
return this.destroy();
|
|
253
249
|
}
|
|
@@ -3,7 +3,7 @@ import { bundle } from "../../esbuild/index.ts";
|
|
|
3
3
|
import { withExponentialBackoff } from "../../util/retry.ts";
|
|
4
4
|
import { handleApiError } from "../api-error.ts";
|
|
5
5
|
import type { CloudflareApi } from "../api.ts";
|
|
6
|
-
import {
|
|
6
|
+
import type { WorkerMetadata } from "../worker-metadata.ts";
|
|
7
7
|
import type { DOStateStoreAPI } from "./types.ts";
|
|
8
8
|
|
|
9
9
|
interface DOStateStoreClientOptions {
|
|
@@ -116,33 +116,49 @@ export async function upsertStateStoreWorker(
|
|
|
116
116
|
cache.set(key, TAG);
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
119
|
+
const formData = new FormData();
|
|
120
|
+
formData.append("worker.js", await bundleWorkerScript());
|
|
121
|
+
formData.append(
|
|
122
|
+
"metadata",
|
|
123
|
+
new Blob([
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
main_module: "worker.js",
|
|
126
|
+
compatibility_date: "2025-06-01",
|
|
127
|
+
compatibility_flags: ["nodejs_compat"],
|
|
128
|
+
bindings: [
|
|
129
|
+
{
|
|
130
|
+
name: "DOFS_STATE_STORE",
|
|
131
|
+
type: "durable_object_namespace",
|
|
132
|
+
class_name: "DOFSStateStore",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "DOFS_TOKEN",
|
|
136
|
+
type: "secret_text",
|
|
137
|
+
text: token,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
migrations: !found
|
|
141
|
+
? {
|
|
142
|
+
new_sqlite_classes: ["DOFSStateStore"],
|
|
143
|
+
}
|
|
144
|
+
: undefined,
|
|
145
|
+
tags: [TAG],
|
|
146
|
+
observability: {
|
|
147
|
+
enabled: true,
|
|
148
|
+
},
|
|
149
|
+
} satisfies WorkerMetadata),
|
|
150
|
+
]),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Put the worker with migration tag v1
|
|
154
|
+
const response = await api.post(
|
|
155
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}`,
|
|
156
|
+
formData,
|
|
157
|
+
);
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
throw await handleApiError(response, "upload", "worker", workerName);
|
|
160
|
+
}
|
|
161
|
+
|
|
146
162
|
const subdomainRes = await api.post(
|
|
147
163
|
`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
|
|
148
164
|
{ enabled: true, preview_enabled: false },
|
|
@@ -151,7 +167,7 @@ export async function upsertStateStoreWorker(
|
|
|
151
167
|
},
|
|
152
168
|
);
|
|
153
169
|
if (!subdomainRes.ok) {
|
|
154
|
-
await handleApiError(
|
|
170
|
+
throw await handleApiError(
|
|
155
171
|
subdomainRes,
|
|
156
172
|
"creating worker subdomain",
|
|
157
173
|
"worker",
|
|
@@ -41,9 +41,8 @@ export function isDurableObjectNamespace(
|
|
|
41
41
|
* environment: "production"
|
|
42
42
|
* });
|
|
43
43
|
*/
|
|
44
|
-
export class DurableObjectNamespace<
|
|
45
|
-
|
|
46
|
-
> implements DurableObjectNamespaceInput
|
|
44
|
+
export class DurableObjectNamespace<T = any>
|
|
45
|
+
implements DurableObjectNamespaceInput
|
|
47
46
|
{
|
|
48
47
|
public readonly type = "durable_object_namespace" as const;
|
|
49
48
|
// alias for bindingName to be consistent with other bindings
|
|
@@ -115,33 +115,52 @@ export async function Website<B extends Bindings>(
|
|
|
115
115
|
parent: Scope.current,
|
|
116
116
|
},
|
|
117
117
|
async () => {
|
|
118
|
+
// directory from which all relative paths are resolved
|
|
118
119
|
const cwd = path.resolve(props.cwd || process.cwd());
|
|
119
|
-
|
|
120
|
+
|
|
121
|
+
function resolveAbsPath<S extends string | undefined>(f: S): S {
|
|
122
|
+
return (
|
|
123
|
+
f ? (path.isAbsolute(f) ? f : path.resolve(cwd, f)) : undefined
|
|
124
|
+
) as S;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// absolute path to the wrangler.jsonc file
|
|
128
|
+
const wranglerJsonPath = resolveAbsPath(
|
|
120
129
|
typeof wrangler === "boolean"
|
|
121
130
|
? "wrangler.jsonc"
|
|
122
131
|
: typeof wrangler === "string"
|
|
123
132
|
? wrangler
|
|
124
|
-
: (wrangler?.path ?? "wrangler.jsonc")
|
|
125
|
-
|
|
126
|
-
fileName && path.relative(cwd, path.join(cwd, fileName));
|
|
127
|
-
const wranglerMain =
|
|
128
|
-
typeof wrangler === "object"
|
|
129
|
-
? (wrangler.main ?? props.main)
|
|
130
|
-
: props.main;
|
|
133
|
+
: (wrangler?.path ?? "wrangler.jsonc"),
|
|
134
|
+
);
|
|
131
135
|
|
|
132
|
-
|
|
136
|
+
// absolute path to the worker entrypoint
|
|
137
|
+
const mainPath = resolveAbsPath(props.main);
|
|
133
138
|
|
|
134
|
-
|
|
139
|
+
// absolute path to the worker entrypoint (if different in wrangler.jsonc)
|
|
140
|
+
const wranglerMainPath = resolveAbsPath(
|
|
141
|
+
typeof wrangler === "object"
|
|
142
|
+
? (wrangler.main ?? props.main!)
|
|
143
|
+
: props.main,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// absolute path to the assets directory
|
|
147
|
+
const assetsDirPath = resolveAbsPath(
|
|
135
148
|
typeof props.assets === "string"
|
|
136
149
|
? props.assets
|
|
137
|
-
: (props.assets?.dist ?? "dist")
|
|
150
|
+
: (props.assets?.dist ?? "dist"),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const workerName = props.name ?? id;
|
|
138
154
|
|
|
139
155
|
const workerProps = {
|
|
140
156
|
...props,
|
|
141
157
|
compatibilityDate:
|
|
142
158
|
props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE,
|
|
143
159
|
name: workerName,
|
|
144
|
-
entrypoint:
|
|
160
|
+
entrypoint: mainPath
|
|
161
|
+
? // this path should be relative to the process.cwd() because it is used by esbuild
|
|
162
|
+
path.relative(process.cwd(), mainPath)
|
|
163
|
+
: undefined,
|
|
145
164
|
assets: {
|
|
146
165
|
html_handling: "auto-trailing-slash",
|
|
147
166
|
not_found_handling: props.spa ? "single-page-application" : "none",
|
|
@@ -161,14 +180,18 @@ export default {
|
|
|
161
180
|
} as WorkerProps<any> & { name: string };
|
|
162
181
|
|
|
163
182
|
if (wrangler) {
|
|
183
|
+
// paths in wrangler.jsonc must be relative to it
|
|
184
|
+
const relativeToWrangler = <S extends string | undefined>(f: S): S =>
|
|
185
|
+
(f ? path.relative(path.dirname(wranglerJsonPath), f) : f) as S;
|
|
164
186
|
await WranglerJson("wrangler.jsonc", {
|
|
165
|
-
path:
|
|
187
|
+
path: wranglerJsonPath,
|
|
166
188
|
worker: workerProps,
|
|
167
|
-
main:
|
|
189
|
+
main: relativeToWrangler(wranglerMainPath),
|
|
168
190
|
// hard-code the assets directory because we haven't yet included the assets binding
|
|
169
191
|
assets: {
|
|
170
192
|
binding: "ASSETS",
|
|
171
|
-
|
|
193
|
+
// path must be relative to the wrangler.jsonc file
|
|
194
|
+
directory: relativeToWrangler(assetsDirPath),
|
|
172
195
|
},
|
|
173
196
|
});
|
|
174
197
|
}
|
|
@@ -189,7 +212,8 @@ export default {
|
|
|
189
212
|
// we don't include the Assets binding until after build to make sure the asset manifest is correct
|
|
190
213
|
// we generate the wrangler.json using all the bind
|
|
191
214
|
ASSETS: await Assets("assets", {
|
|
192
|
-
|
|
215
|
+
// Assets are discovered from proces.cwd(), not Website.cwd or wrangler.jsonc
|
|
216
|
+
path: path.relative(process.cwd(), assetsDirPath),
|
|
193
217
|
}),
|
|
194
218
|
},
|
|
195
219
|
} as WorkerProps<any> & { name: string })) as Website<B>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import type { Context } from "../context.ts";
|
|
3
2
|
import { logger } from "../util/logger.ts";
|
|
4
|
-
import {
|
|
3
|
+
import type { CloudflareApi } from "./api.ts";
|
|
5
4
|
import {
|
|
6
5
|
Self,
|
|
7
6
|
type Bindings,
|
|
@@ -13,8 +12,11 @@ import {
|
|
|
13
12
|
type DurableObjectNamespace,
|
|
14
13
|
} from "./durable-object-namespace.ts";
|
|
15
14
|
import { createAssetConfig, type AssetUploadResult } from "./worker-assets.ts";
|
|
16
|
-
import type {
|
|
17
|
-
|
|
15
|
+
import type {
|
|
16
|
+
MultiStepMigration,
|
|
17
|
+
SingleStepMigration,
|
|
18
|
+
} from "./worker-migration.ts";
|
|
19
|
+
import type { AssetsConfig, WorkerProps } from "./worker.ts";
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Metadata returned by Cloudflare API for a worker script
|
|
@@ -193,17 +195,21 @@ export interface WorkerMetadata {
|
|
|
193
195
|
}[];
|
|
194
196
|
}
|
|
195
197
|
|
|
196
|
-
export async function prepareWorkerMetadata
|
|
197
|
-
|
|
198
|
-
oldBindings: WorkerBindingSpec[] | undefined,
|
|
199
|
-
oldTags: string[] | undefined,
|
|
198
|
+
export async function prepareWorkerMetadata(
|
|
199
|
+
api: CloudflareApi,
|
|
200
200
|
props: WorkerProps & {
|
|
201
201
|
compatibilityDate: string;
|
|
202
202
|
compatibilityFlags: string[];
|
|
203
203
|
workerName: string;
|
|
204
|
+
migrationTag?: string;
|
|
205
|
+
assetUploadResult?: AssetUploadResult;
|
|
204
206
|
},
|
|
205
|
-
assetUploadResult?: AssetUploadResult,
|
|
206
207
|
): Promise<WorkerMetadata> {
|
|
208
|
+
const oldSettings = await getWorkerSettings(api, props.workerName);
|
|
209
|
+
const oldTags: string[] | undefined =
|
|
210
|
+
oldSettings?.default_environment?.script?.tags;
|
|
211
|
+
const oldBindings = oldSettings?.bindings;
|
|
212
|
+
|
|
207
213
|
// we use Cloudflare Worker tags to store a mapping between Alchemy's stable identifier and the binding name
|
|
208
214
|
// e.g.
|
|
209
215
|
// {
|
|
@@ -212,7 +218,7 @@ export async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
212
218
|
// will be stored as alchemy:do:stable-id:BINDING_NAME
|
|
213
219
|
// TODO(sam): should we base64 encode to ensure no `:` collision risk?
|
|
214
220
|
const bindingNameToStableId = Object.fromEntries(
|
|
215
|
-
oldTags?.flatMap((tag) => {
|
|
221
|
+
oldTags?.flatMap((tag: string) => {
|
|
216
222
|
// alchemy:do:{stableId}:{bindingName}
|
|
217
223
|
if (tag.startsWith("alchemy:do:")) {
|
|
218
224
|
const [, , stableId, bindingName] = tag.split(":");
|
|
@@ -222,6 +228,14 @@ export async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
222
228
|
}) ?? [],
|
|
223
229
|
);
|
|
224
230
|
|
|
231
|
+
const oldMigrationTag = oldTags?.flatMap((tag: string) => {
|
|
232
|
+
if (tag.startsWith("alchemy:migration-tag:")) {
|
|
233
|
+
return [tag.slice("alchemy:migration-tag:".length)];
|
|
234
|
+
}
|
|
235
|
+
return [];
|
|
236
|
+
})[0];
|
|
237
|
+
const newMigrationTag = bumpMigrationTagVersion(oldMigrationTag);
|
|
238
|
+
|
|
225
239
|
const deletedClasses = oldBindings?.flatMap((oldBinding) => {
|
|
226
240
|
if (
|
|
227
241
|
oldBinding.type === "durable_object_namespace" &&
|
|
@@ -284,7 +298,6 @@ export async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
284
298
|
},
|
|
285
299
|
// TODO(sam): base64 encode instead? 0 collision risk vs readability.
|
|
286
300
|
tags: [
|
|
287
|
-
`alchemy:id:${slugify(ctx.fqn)}`,
|
|
288
301
|
// encode a mapping table of Durable Object stable ID -> binding name
|
|
289
302
|
// we use this to reliably compute class migrations based on server-side state
|
|
290
303
|
...Object.entries(props.bindings ?? {}).flatMap(
|
|
@@ -294,19 +307,21 @@ export async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
294
307
|
[`alchemy:do:${binding.id}:${bindingName}`]
|
|
295
308
|
: [],
|
|
296
309
|
),
|
|
310
|
+
// encode the migraiton tag if there is one so we can avoid the failed PutWorker after adoption
|
|
311
|
+
...(newMigrationTag ? [`alchemy:migration-tag:${newMigrationTag}`] : []),
|
|
297
312
|
],
|
|
298
313
|
migrations: {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
],
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
new_sqlite_classes: props.migrations?.new_sqlite_classes ?? [],
|
|
314
|
+
old_tag: oldMigrationTag,
|
|
315
|
+
new_tag: newMigrationTag,
|
|
316
|
+
new_classes: [],
|
|
317
|
+
deleted_classes: [...(deletedClasses ?? [])],
|
|
318
|
+
renamed_classes: [],
|
|
319
|
+
transferred_classes: [],
|
|
320
|
+
new_sqlite_classes: [],
|
|
307
321
|
},
|
|
308
322
|
};
|
|
309
323
|
|
|
324
|
+
const assetUploadResult = props.assetUploadResult;
|
|
310
325
|
// If we have asset upload results, add them to the metadata
|
|
311
326
|
if (assetUploadResult) {
|
|
312
327
|
meta.assets = {
|
|
@@ -577,3 +592,70 @@ export async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
577
592
|
}
|
|
578
593
|
return meta;
|
|
579
594
|
}
|
|
595
|
+
|
|
596
|
+
export function bumpMigrationTagVersion(tag?: string) {
|
|
597
|
+
if (tag) {
|
|
598
|
+
if (!tag.match(/^v\d+$/)) {
|
|
599
|
+
throw new Error(`Invalid tag format: ${tag}. Expected format: v<number>`);
|
|
600
|
+
}
|
|
601
|
+
return `v${Number.parseInt(tag.slice(1)) + 1}`;
|
|
602
|
+
}
|
|
603
|
+
return undefined;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
interface WorkerSettings {
|
|
607
|
+
bindings: WorkerBindingSpec[];
|
|
608
|
+
compatibility_date: string;
|
|
609
|
+
compatibility_flags: string[];
|
|
610
|
+
migrations: SingleStepMigration | MultiStepMigration;
|
|
611
|
+
[key: string]: any;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function getWorkerSettings(
|
|
615
|
+
api: CloudflareApi,
|
|
616
|
+
workerName: string,
|
|
617
|
+
): Promise<WorkerSettings | undefined> {
|
|
618
|
+
// Fetch the bindings for a worker by calling the Cloudflare API endpoint:
|
|
619
|
+
// GET /accounts/:account_id/workers/scripts/:script_name/bindings
|
|
620
|
+
// See: https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/script_and_version_settings/methods/get/
|
|
621
|
+
const response = await api.get(
|
|
622
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}/settings`,
|
|
623
|
+
);
|
|
624
|
+
if (response.status === 404) {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
if (!response.ok) {
|
|
628
|
+
throw new Error(
|
|
629
|
+
`Error getting worker bindings: ${response.status} ${response.statusText}`,
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
// The result is an object with a "result" property containing the bindings array
|
|
633
|
+
const { result, success, errors } = (await response.json()) as {
|
|
634
|
+
result: {
|
|
635
|
+
bindings: WorkerBindingSpec[];
|
|
636
|
+
compatibility_date: string;
|
|
637
|
+
compatibility_flags: string[];
|
|
638
|
+
migrations: SingleStepMigration | MultiStepMigration;
|
|
639
|
+
[key: string]: any;
|
|
640
|
+
};
|
|
641
|
+
success: boolean;
|
|
642
|
+
errors: Array<{
|
|
643
|
+
code: number;
|
|
644
|
+
message: string;
|
|
645
|
+
documentation_url: string;
|
|
646
|
+
[key: string]: any;
|
|
647
|
+
}>;
|
|
648
|
+
messages: Array<{
|
|
649
|
+
code: number;
|
|
650
|
+
message: string;
|
|
651
|
+
documentation_url: string;
|
|
652
|
+
[key: string]: any;
|
|
653
|
+
}>;
|
|
654
|
+
};
|
|
655
|
+
if (!success) {
|
|
656
|
+
throw new Error(
|
|
657
|
+
`Error getting worker bindings: ${response.status} ${response.statusText}\nErrors:\n${errors.map((e) => `- [${e.code}] ${e.message} (${e.documentation_url})`).join("\n")}`,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
return result;
|
|
661
|
+
}
|