alchemy 0.24.0 → 0.25.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/lib/aws/control/client.js +1 -1
- package/lib/aws/control/client.js.map +1 -1
- package/lib/aws/control/resource.d.ts.map +1 -1
- package/lib/aws/control/resource.js +2 -1
- package/lib/aws/control/resource.js.map +1 -1
- package/lib/aws/function.d.ts.map +1 -1
- package/lib/aws/function.js +1 -4
- package/lib/aws/function.js.map +1 -1
- package/lib/cloudflare/ai.d.ts +21 -0
- package/lib/cloudflare/ai.d.ts.map +1 -1
- package/lib/cloudflare/ai.js +21 -0
- package/lib/cloudflare/ai.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +3 -3
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +3 -3
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/index.d.ts +2 -0
- package/lib/cloudflare/do-state-store/index.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/index.js +2 -0
- package/lib/cloudflare/do-state-store/index.js.map +1 -0
- package/lib/cloudflare/do-state-store/internal.d.ts +19 -0
- package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/internal.js +163 -0
- package/lib/cloudflare/do-state-store/internal.js.map +1 -0
- package/lib/cloudflare/do-state-store/store.d.ts +59 -0
- package/lib/cloudflare/do-state-store/store.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/store.js +120 -0
- package/lib/cloudflare/do-state-store/store.js.map +1 -0
- package/lib/cloudflare/do-state-store/types.d.ts +82 -0
- package/lib/cloudflare/do-state-store/types.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/types.js +1 -0
- package/lib/cloudflare/do-state-store/types.js.map +1 -0
- package/lib/cloudflare/do-state-store/worker.d.ts +27 -0
- package/lib/cloudflare/do-state-store/worker.d.ts.map +1 -0
- package/lib/cloudflare/do-state-store/worker.js +170 -0
- package/lib/cloudflare/do-state-store/worker.js.map +1 -0
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/pipeline.js +1 -1
- package/lib/cloudflare/pipeline.js.map +1 -1
- package/lib/cloudflare/queue-consumer.d.ts +14 -1
- package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
- package/lib/cloudflare/queue-consumer.js +17 -0
- package/lib/cloudflare/queue-consumer.js.map +1 -1
- package/lib/cloudflare/website.d.ts +30 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +8 -5
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-assets.d.ts.map +1 -1
- package/lib/cloudflare/worker-assets.js +13 -3
- package/lib/cloudflare/worker-assets.js.map +1 -1
- package/lib/cloudflare/worker-metadata.js +1 -1
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +40 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +40 -38
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.js +1 -1
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/cloudflare/zone.d.ts +30 -2
- package/lib/cloudflare/zone.d.ts.map +1 -1
- package/lib/cloudflare/zone.js +53 -0
- package/lib/cloudflare/zone.js.map +1 -1
- package/lib/fs/file-system-state-store.d.ts +3 -1
- package/lib/fs/file-system-state-store.d.ts.map +1 -1
- package/lib/fs/file-system-state-store.js +2 -2
- package/lib/fs/file-system-state-store.js.map +1 -1
- package/lib/stripe/card.d.ts +171 -0
- package/lib/stripe/card.d.ts.map +1 -0
- package/lib/stripe/card.js +148 -0
- package/lib/stripe/card.js.map +1 -0
- package/lib/stripe/client.d.ts +9 -0
- package/lib/stripe/client.d.ts.map +1 -0
- package/lib/stripe/client.js +94 -0
- package/lib/stripe/client.js.map +1 -0
- package/lib/stripe/coupon.d.ts +135 -0
- package/lib/stripe/coupon.d.ts.map +1 -0
- package/lib/stripe/coupon.js +161 -0
- package/lib/stripe/coupon.js.map +1 -0
- package/lib/stripe/customer.d.ts +269 -0
- package/lib/stripe/customer.d.ts.map +1 -0
- package/lib/stripe/customer.js +289 -0
- package/lib/stripe/customer.js.map +1 -0
- package/lib/stripe/entitlements-feature.d.ts +84 -0
- package/lib/stripe/entitlements-feature.d.ts.map +1 -0
- package/lib/stripe/entitlements-feature.js +102 -0
- package/lib/stripe/entitlements-feature.js.map +1 -0
- package/lib/stripe/file.d.ts +143 -0
- package/lib/stripe/file.d.ts.map +1 -0
- package/lib/stripe/file.js +97 -0
- package/lib/stripe/file.js.map +1 -0
- package/lib/stripe/index.d.ts +11 -0
- package/lib/stripe/index.d.ts.map +1 -1
- package/lib/stripe/index.js +11 -0
- package/lib/stripe/index.js.map +1 -1
- package/lib/stripe/meter.d.ts +10 -1
- package/lib/stripe/meter.d.ts.map +1 -1
- package/lib/stripe/meter.js +13 -7
- package/lib/stripe/meter.js.map +1 -1
- package/lib/stripe/portal-configuration.d.ts +286 -0
- package/lib/stripe/portal-configuration.d.ts.map +1 -0
- package/lib/stripe/portal-configuration.js +320 -0
- package/lib/stripe/portal-configuration.js.map +1 -0
- package/lib/stripe/price.d.ts +10 -1
- package/lib/stripe/price.d.ts.map +1 -1
- package/lib/stripe/price.js +34 -13
- package/lib/stripe/price.js.map +1 -1
- package/lib/stripe/product-feature.d.ts +76 -0
- package/lib/stripe/product-feature.d.ts.map +1 -0
- package/lib/stripe/product-feature.js +74 -0
- package/lib/stripe/product-feature.js.map +1 -0
- package/lib/stripe/product.d.ts +10 -1
- package/lib/stripe/product.d.ts.map +1 -1
- package/lib/stripe/product.js +33 -14
- package/lib/stripe/product.js.map +1 -1
- package/lib/stripe/promotion-code.d.ts +138 -0
- package/lib/stripe/promotion-code.d.ts.map +1 -0
- package/lib/stripe/promotion-code.js +150 -0
- package/lib/stripe/promotion-code.js.map +1 -0
- package/lib/stripe/shipping-rate.d.ts +188 -0
- package/lib/stripe/shipping-rate.d.ts.map +1 -0
- package/lib/stripe/shipping-rate.js +198 -0
- package/lib/stripe/shipping-rate.js.map +1 -0
- package/lib/stripe/tax-rate.d.ts +130 -0
- package/lib/stripe/tax-rate.d.ts.map +1 -0
- package/lib/stripe/tax-rate.js +126 -0
- package/lib/stripe/tax-rate.js.map +1 -0
- package/lib/stripe/webhook.d.ts +10 -1
- package/lib/stripe/webhook.d.ts.map +1 -1
- package/lib/stripe/webhook.js +59 -14
- package/lib/stripe/webhook.js.map +1 -1
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +2 -6
- package/lib/test/bun.js.map +1 -1
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +3 -7
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/retry.d.ts +1 -1
- package/lib/util/retry.d.ts.map +1 -1
- package/lib/util/retry.js.map +1 -1
- package/package.json +3 -2
- package/src/aws/control/client.ts +1 -1
- package/src/aws/control/resource.ts +2 -1
- package/src/aws/function.ts +3 -6
- package/src/cloudflare/ai.ts +21 -0
- package/src/cloudflare/bindings.ts +3 -2
- package/src/cloudflare/bound.ts +3 -3
- package/src/cloudflare/do-state-store/index.ts +1 -0
- package/src/cloudflare/do-state-store/internal.ts +215 -0
- package/src/cloudflare/do-state-store/store.ts +188 -0
- package/src/cloudflare/do-state-store/types.ts +74 -0
- package/src/cloudflare/do-state-store/worker.ts +199 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/pipeline.ts +1 -1
- package/src/cloudflare/queue-consumer.ts +59 -1
- package/src/cloudflare/website.ts +38 -6
- package/src/cloudflare/worker-assets.ts +15 -3
- package/src/cloudflare/worker-metadata.ts +1 -1
- package/src/cloudflare/worker.ts +66 -46
- package/src/cloudflare/wrangler.json.ts +1 -1
- package/src/cloudflare/zone.ts +75 -2
- package/src/fs/file-system-state-store.ts +7 -2
- package/src/stripe/card.ts +320 -0
- package/src/stripe/client.ts +145 -0
- package/src/stripe/coupon.ts +280 -0
- package/src/stripe/customer.ts +528 -0
- package/src/stripe/entitlements-feature.ts +169 -0
- package/src/stripe/file.ts +219 -0
- package/src/stripe/index.ts +11 -0
- package/src/stripe/meter.ts +23 -7
- package/src/stripe/portal-configuration.ts +609 -0
- package/src/stripe/price.ts +47 -14
- package/src/stripe/product-feature.ts +150 -0
- package/src/stripe/product.ts +48 -15
- package/src/stripe/promotion-code.ts +268 -0
- package/src/stripe/shipping-rate.ts +362 -0
- package/src/stripe/tax-rate.ts +218 -0
- package/src/stripe/webhook.ts +82 -15
- package/src/test/bun.ts +2 -7
- package/src/test/vitest.ts +3 -8
- package/src/util/retry.ts +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { State } from "../../state.ts";
|
|
2
|
+
|
|
3
|
+
export namespace DOStateStoreAPI {
|
|
4
|
+
export type API = {
|
|
5
|
+
validate: {
|
|
6
|
+
method: "validate";
|
|
7
|
+
params: null;
|
|
8
|
+
result: null;
|
|
9
|
+
};
|
|
10
|
+
get: {
|
|
11
|
+
method: "get";
|
|
12
|
+
params: { key: string };
|
|
13
|
+
result: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
getBatch: {
|
|
16
|
+
method: "getBatch";
|
|
17
|
+
params: { keys: string[] };
|
|
18
|
+
result: Record<string, string>;
|
|
19
|
+
};
|
|
20
|
+
all: {
|
|
21
|
+
method: "all";
|
|
22
|
+
params: { prefix: string };
|
|
23
|
+
result: Record<string, string>;
|
|
24
|
+
};
|
|
25
|
+
set: {
|
|
26
|
+
method: "set";
|
|
27
|
+
params: { key: string; value: State };
|
|
28
|
+
result: null;
|
|
29
|
+
};
|
|
30
|
+
delete: {
|
|
31
|
+
method: "delete";
|
|
32
|
+
params: { key: string };
|
|
33
|
+
result: null;
|
|
34
|
+
};
|
|
35
|
+
list: {
|
|
36
|
+
method: "list";
|
|
37
|
+
params: { prefix: string };
|
|
38
|
+
result: string[];
|
|
39
|
+
};
|
|
40
|
+
count: {
|
|
41
|
+
method: "count";
|
|
42
|
+
params: { prefix: string };
|
|
43
|
+
result: number;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export interface Context {
|
|
48
|
+
app: string;
|
|
49
|
+
stage: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type Request = {
|
|
53
|
+
[K in keyof API]: {
|
|
54
|
+
method: K;
|
|
55
|
+
params: API[K]["params"];
|
|
56
|
+
};
|
|
57
|
+
}[keyof API];
|
|
58
|
+
|
|
59
|
+
export interface SuccessResponse<T extends keyof API = keyof API> {
|
|
60
|
+
success: true;
|
|
61
|
+
status: number;
|
|
62
|
+
result: API[T]["result"];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ErrorResponse {
|
|
66
|
+
success: false;
|
|
67
|
+
status: number;
|
|
68
|
+
error: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type Response<T extends keyof API = keyof API> =
|
|
72
|
+
| SuccessResponse<T>
|
|
73
|
+
| ErrorResponse;
|
|
74
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
|
|
2
|
+
import { Fs } from "dofs";
|
|
3
|
+
import { timingSafeEqual } from "node:crypto";
|
|
4
|
+
import type { State } from "../../state.ts";
|
|
5
|
+
import type { DOStateStoreAPI } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
interface Env {
|
|
8
|
+
DOFS_STATE_STORE: DurableObjectNamespace<DOFSStateStore>;
|
|
9
|
+
DOFS_TOKEN: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default class extends WorkerEntrypoint<Env> {
|
|
13
|
+
override async fetch(request: Request) {
|
|
14
|
+
try {
|
|
15
|
+
const result = await this.handle(request);
|
|
16
|
+
const body: DOStateStoreAPI.Response = {
|
|
17
|
+
success: true,
|
|
18
|
+
status: 200,
|
|
19
|
+
result: result ?? undefined,
|
|
20
|
+
};
|
|
21
|
+
return Response.json(body, { status: body.status });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return APIError.fromUnknown(error).toResponse();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private async handle(request: Request) {
|
|
28
|
+
if (!this.authorize(request)) {
|
|
29
|
+
throw new APIError("Unauthorized", 401);
|
|
30
|
+
}
|
|
31
|
+
const stub = this.getStub(request);
|
|
32
|
+
const body = await this.parseBody(request);
|
|
33
|
+
switch (body.method) {
|
|
34
|
+
case "validate":
|
|
35
|
+
return null;
|
|
36
|
+
case "all":
|
|
37
|
+
return await stub.all(body.params.prefix);
|
|
38
|
+
case "count":
|
|
39
|
+
return await stub.count(body.params.prefix);
|
|
40
|
+
case "delete":
|
|
41
|
+
return await stub.delete(body.params.key);
|
|
42
|
+
case "get":
|
|
43
|
+
return await stub.get(body.params.key);
|
|
44
|
+
case "getBatch":
|
|
45
|
+
return await stub.getBatch(body.params.keys);
|
|
46
|
+
case "list":
|
|
47
|
+
return await stub.list(body.params.prefix);
|
|
48
|
+
case "set":
|
|
49
|
+
return await stub.set(body.params.key, body.params.value);
|
|
50
|
+
default: {
|
|
51
|
+
const _: never = body;
|
|
52
|
+
throw new APIError("Method not found", 404);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private authorize(request: Request): boolean {
|
|
58
|
+
const token = request.headers.get("Authorization")?.split(" ")[1];
|
|
59
|
+
if (!token) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return timingSafeEqual(
|
|
63
|
+
new TextEncoder().encode(token),
|
|
64
|
+
new TextEncoder().encode(this.env.DOFS_TOKEN),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private getStub(request: Request): DurableObjectStub<DOFSStateStore> {
|
|
69
|
+
const url = new URL(request.url);
|
|
70
|
+
const app = url.searchParams.get("app");
|
|
71
|
+
const stage = url.searchParams.get("stage");
|
|
72
|
+
if (!app || !stage) {
|
|
73
|
+
throw new APIError("Missing app or stage", 400);
|
|
74
|
+
}
|
|
75
|
+
return this.env.DOFS_STATE_STORE.get(
|
|
76
|
+
this.env.DOFS_STATE_STORE.idFromName(`${app}/${stage}`),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async parseBody(request: Request): Promise<DOStateStoreAPI.Request> {
|
|
81
|
+
try {
|
|
82
|
+
return await request.json<DOStateStoreAPI.Request>();
|
|
83
|
+
} catch {
|
|
84
|
+
throw new APIError("Invalid JSON", 400);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class APIError extends Error {
|
|
90
|
+
constructor(
|
|
91
|
+
message: string,
|
|
92
|
+
public readonly status: number,
|
|
93
|
+
) {
|
|
94
|
+
super(message);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
toResponse(): Response {
|
|
98
|
+
return Response.json(
|
|
99
|
+
{
|
|
100
|
+
success: false,
|
|
101
|
+
status: this.status,
|
|
102
|
+
error: this.message,
|
|
103
|
+
},
|
|
104
|
+
{ status: this.status },
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static fromUnknown(error: unknown): APIError {
|
|
109
|
+
if (error instanceof APIError) {
|
|
110
|
+
return error;
|
|
111
|
+
}
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
return new APIError(error.message, 500);
|
|
114
|
+
}
|
|
115
|
+
return new APIError("An unknown error occurred.", 500);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class DOFSStateStore extends DurableObject<Env> {
|
|
120
|
+
fs = new Fs(this.ctx, this.env, {
|
|
121
|
+
chunkSize: 256 * 1024,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
async all(prefix: string): Promise<Record<string, string>> {
|
|
125
|
+
const keys = this.list(prefix);
|
|
126
|
+
return await this.getBatch(keys);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
count(prefix: string): number {
|
|
130
|
+
return this.list(prefix).length;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
delete(key: string): void {
|
|
134
|
+
try {
|
|
135
|
+
this.fs.unlink(key);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (isErrorCode(error, "ENOENT")) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async get(key: string): Promise<string | undefined> {
|
|
145
|
+
try {
|
|
146
|
+
const file = this.fs.readFile(key);
|
|
147
|
+
return new Response(file).text();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (isErrorCode(error, "ENOENT")) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async getBatch(keys: string[]): Promise<Record<string, string>> {
|
|
157
|
+
const result: Record<string, string> = {};
|
|
158
|
+
await Promise.all(
|
|
159
|
+
keys.map(async (key) => {
|
|
160
|
+
const value = await this.get(key);
|
|
161
|
+
if (value) {
|
|
162
|
+
result[key] = value;
|
|
163
|
+
}
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
list(prefix: string): string[] {
|
|
170
|
+
try {
|
|
171
|
+
return this.fs
|
|
172
|
+
.listDir(prefix, { recursive: true })
|
|
173
|
+
.filter((item) => item !== "." && item !== "..");
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (isErrorCode(error, "ENOENT")) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async set(key: string, value: State): Promise<void> {
|
|
183
|
+
this.ensureDir(key);
|
|
184
|
+
await this.fs.writeFile(key, JSON.stringify(value));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private ensureDir(path: string): void {
|
|
188
|
+
const dir = path.split("/").slice(0, -1).join("/");
|
|
189
|
+
try {
|
|
190
|
+
this.fs.mkdir(dir, { recursive: true });
|
|
191
|
+
} catch {
|
|
192
|
+
// directory already exists, ignore
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const isErrorCode = (error: unknown, code: string) => {
|
|
198
|
+
return error instanceof Error && "message" in error && error.message === code;
|
|
199
|
+
};
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from "./d1-database.ts";
|
|
|
18
18
|
export * from "./d1-export.ts";
|
|
19
19
|
export * from "./d1-import.ts";
|
|
20
20
|
export * from "./dns-records.ts";
|
|
21
|
+
export * from "./do-state-store/index.ts";
|
|
21
22
|
export * from "./durable-object-namespace.ts";
|
|
22
23
|
export * from "./hyperdrive.ts";
|
|
23
24
|
export * from "./kv-namespace.ts";
|
|
@@ -364,7 +364,7 @@ const PipelineResource = Resource("cloudflare::Pipeline", async function <
|
|
|
364
364
|
// Update operation
|
|
365
365
|
if (this.output?.id) {
|
|
366
366
|
// Check if name is being changed, which is not allowed
|
|
367
|
-
if (
|
|
367
|
+
if (pipelineName !== this.output.name) {
|
|
368
368
|
throw new Error(
|
|
369
369
|
"Cannot update Pipeline name after creation. Pipeline name is immutable.",
|
|
370
370
|
);
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type CloudflareApi,
|
|
7
7
|
type CloudflareApiOptions,
|
|
8
8
|
} from "./api.ts";
|
|
9
|
-
import type {
|
|
9
|
+
import type { Queue, QueueResource } from "./queue.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Settings for configuring a Queue Consumer
|
|
@@ -450,3 +450,61 @@ export async function listQueueConsumers(
|
|
|
450
450
|
: undefined,
|
|
451
451
|
}));
|
|
452
452
|
}
|
|
453
|
+
|
|
454
|
+
export async function listQueueConsumersForWorker(
|
|
455
|
+
api: CloudflareApi,
|
|
456
|
+
workerName: string,
|
|
457
|
+
) {
|
|
458
|
+
const response = await api.get(
|
|
459
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}/queue-consumers?perPage=100`,
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
if (response.status === 404) {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (!response.ok) {
|
|
467
|
+
return await handleApiError(
|
|
468
|
+
response,
|
|
469
|
+
"list",
|
|
470
|
+
"QueueConsumer",
|
|
471
|
+
`for worker ${workerName}`,
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const data = (await response.json()) as {
|
|
476
|
+
result: Array<{
|
|
477
|
+
script: string;
|
|
478
|
+
settings?: {
|
|
479
|
+
batch_size?: number;
|
|
480
|
+
max_retries?: number;
|
|
481
|
+
max_wait_time_ms?: number;
|
|
482
|
+
retry_delay?: number;
|
|
483
|
+
dead_letter_queue?: string;
|
|
484
|
+
};
|
|
485
|
+
type: string;
|
|
486
|
+
queue_name: string;
|
|
487
|
+
queue_id: string;
|
|
488
|
+
consumer_id: string;
|
|
489
|
+
created_on: string;
|
|
490
|
+
}>;
|
|
491
|
+
success: boolean;
|
|
492
|
+
errors: any[] | null;
|
|
493
|
+
messages: any[] | null;
|
|
494
|
+
result_info: {
|
|
495
|
+
page: number;
|
|
496
|
+
per_page: number;
|
|
497
|
+
count: number;
|
|
498
|
+
total_count: number;
|
|
499
|
+
total_pages: number;
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
return data.result.map((consumer) => ({
|
|
504
|
+
queueName: consumer.queue_name,
|
|
505
|
+
queueId: consumer.queue_id,
|
|
506
|
+
consumerId: consumer.consumer_id,
|
|
507
|
+
createdOn: consumer.created_on,
|
|
508
|
+
settings: consumer.settings,
|
|
509
|
+
}));
|
|
510
|
+
}
|
|
@@ -15,8 +15,37 @@ export interface WebsiteProps<B extends Bindings>
|
|
|
15
15
|
extends Omit<WorkerProps<B>, "name" | "assets" | "entrypoint"> {
|
|
16
16
|
/**
|
|
17
17
|
* The command to run to build the site
|
|
18
|
+
*
|
|
19
|
+
* If one is not provided, the build is assumed to have already happened.
|
|
20
|
+
*/
|
|
21
|
+
command?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether to memoize the command (only re-run if the command changes)
|
|
25
|
+
*
|
|
26
|
+
* When set to `true`, the command will only be re-executed if the command string changes.
|
|
27
|
+
*
|
|
28
|
+
* When set to an object with `patterns`, the command will be re-executed if either:
|
|
29
|
+
* 1. The command string changes, or
|
|
30
|
+
* 2. The contents of any files matching the glob patterns change
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ **Important Note**: When using memoization with build commands, the build outputs
|
|
33
|
+
* will not be produced if the command is memoized. This is because the command is not
|
|
34
|
+
* actually executed when memoized. Consider disabling memoization in CI environments:
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // Disable memoization in CI to ensure build outputs are always produced
|
|
38
|
+
* await Website("my-website", {
|
|
39
|
+
* command: "vite build",
|
|
40
|
+
* memoize: process.env.CI ? false : {
|
|
41
|
+
* patterns: ["./src/**"]
|
|
42
|
+
* }
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* @default false
|
|
18
46
|
*/
|
|
19
|
-
|
|
47
|
+
memoize?: boolean | { patterns: string[] };
|
|
48
|
+
|
|
20
49
|
/**
|
|
21
50
|
* The name of the worker
|
|
22
51
|
*
|
|
@@ -128,11 +157,14 @@ export default {
|
|
|
128
157
|
});
|
|
129
158
|
}
|
|
130
159
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
160
|
+
if (props.command) {
|
|
161
|
+
await Exec("build", {
|
|
162
|
+
cwd,
|
|
163
|
+
command: props.command,
|
|
164
|
+
env: props.env,
|
|
165
|
+
memoize: props.memoize,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
136
168
|
|
|
137
169
|
return (await Worker("worker", {
|
|
138
170
|
...workerProps,
|
|
@@ -109,8 +109,8 @@ export async function uploadAssets(
|
|
|
109
109
|
for (const bucket of buckets) {
|
|
110
110
|
const formData = new FormData();
|
|
111
111
|
|
|
112
|
-
//
|
|
113
|
-
|
|
112
|
+
// Prepare file reading operations for parallel execution
|
|
113
|
+
const fileReadPromises = bucket.map(async (fileHash) => {
|
|
114
114
|
// Find the file with this hash
|
|
115
115
|
const file = assets.files.find((f) => {
|
|
116
116
|
const filePath = f.path.startsWith("/") ? f.path : `/${f.path}`;
|
|
@@ -127,10 +127,22 @@ export async function uploadAssets(
|
|
|
127
127
|
// Convert to base64 as required by the API when using base64=true
|
|
128
128
|
const base64Content = fileContent.toString("base64");
|
|
129
129
|
|
|
130
|
+
return {
|
|
131
|
+
fileHash,
|
|
132
|
+
base64Content,
|
|
133
|
+
filePath: file.filePath,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Read all files in parallel
|
|
138
|
+
const fileResults = await Promise.all(fileReadPromises);
|
|
139
|
+
|
|
140
|
+
// Add each file to the form
|
|
141
|
+
for (const { fileHash, base64Content, filePath } of fileResults) {
|
|
130
142
|
// Add the file to the form with the hash as the key and set the correct content type
|
|
131
143
|
const blob = new Blob([base64Content], {
|
|
132
144
|
// if you set application/octet-stream, you get weird `-1` errors with a message to contact support
|
|
133
|
-
type: getContentType(
|
|
145
|
+
type: getContentType(filePath) ?? "application/null",
|
|
134
146
|
});
|
|
135
147
|
formData.append(fileHash, blob, fileHash);
|
|
136
148
|
}
|
|
@@ -361,7 +361,7 @@ export async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
361
361
|
meta.bindings.push({
|
|
362
362
|
type: "service",
|
|
363
363
|
name: bindingName,
|
|
364
|
-
service: binding.name,
|
|
364
|
+
service: "service" in binding ? binding.service : binding.name,
|
|
365
365
|
});
|
|
366
366
|
} else if (binding.type === "durable_object_namespace") {
|
|
367
367
|
meta.bindings.push({
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type Binding,
|
|
24
24
|
type Bindings,
|
|
25
25
|
Json,
|
|
26
|
+
type WorkerBindingService,
|
|
26
27
|
type WorkerBindingSpec,
|
|
27
28
|
} from "./bindings.ts";
|
|
28
29
|
import type { Bound } from "./bound.ts";
|
|
@@ -46,7 +47,7 @@ import { isPipeline } from "./pipeline.ts";
|
|
|
46
47
|
import {
|
|
47
48
|
QueueConsumer,
|
|
48
49
|
deleteQueueConsumer,
|
|
49
|
-
|
|
50
|
+
listQueueConsumersForWorker,
|
|
50
51
|
} from "./queue-consumer.ts";
|
|
51
52
|
import { type QueueResource, isQueue } from "./queue.ts";
|
|
52
53
|
import { isVectorizeIndex } from "./vectorize-index.ts";
|
|
@@ -384,6 +385,56 @@ export type Worker<
|
|
|
384
385
|
compatibilityFlags: string[];
|
|
385
386
|
};
|
|
386
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Represents a reference to a Cloudflare Worker service.
|
|
390
|
+
*
|
|
391
|
+
* @template RPC - The type of the worker's RPC entrypoint, defaults to Rpc.WorkerEntrypointBranded.
|
|
392
|
+
*
|
|
393
|
+
* This interface extends all properties of WorkerBindingService except for "name".
|
|
394
|
+
* It also includes an optional __rpc__ property for type branding.
|
|
395
|
+
*/
|
|
396
|
+
export type WorkerRef<
|
|
397
|
+
RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
|
|
398
|
+
> = Omit<WorkerBindingService, "name"> & {
|
|
399
|
+
/**
|
|
400
|
+
* Optional type branding for the worker's RPC entrypoint.
|
|
401
|
+
*/
|
|
402
|
+
__rpc__?: RPC;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Creates a reference to a Cloudflare Worker service.
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* // Create a reference to a Cloudflare Worker service:
|
|
410
|
+
* const ref = WorkerRef({
|
|
411
|
+
* service: "my-worker",
|
|
412
|
+
* environment: "production",
|
|
413
|
+
* namespace: "main"
|
|
414
|
+
* });
|
|
415
|
+
*
|
|
416
|
+
* // Optionally, you can specify only the service:
|
|
417
|
+
* const ref2 = WorkerRef({ service: "my-worker" });
|
|
418
|
+
*
|
|
419
|
+
* // You can also specify the RPC type for stronger typing:
|
|
420
|
+
* interface MyWorkerRPC extends Rpc.WorkerEntrypointBranded {
|
|
421
|
+
* myMethod(arg: string): Promise<number>;
|
|
422
|
+
* }
|
|
423
|
+
* const typedRef = WorkerRef<MyWorkerRPC>({ service: "my-worker" });
|
|
424
|
+
*/
|
|
425
|
+
export function WorkerRef<
|
|
426
|
+
RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
|
|
427
|
+
>(options?: {
|
|
428
|
+
service: string;
|
|
429
|
+
environment?: string;
|
|
430
|
+
namespace?: string;
|
|
431
|
+
}): WorkerRef<RPC> {
|
|
432
|
+
return {
|
|
433
|
+
...options,
|
|
434
|
+
type: "service",
|
|
435
|
+
} as WorkerRef<RPC>;
|
|
436
|
+
}
|
|
437
|
+
|
|
387
438
|
/**
|
|
388
439
|
* A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
|
|
389
440
|
*
|
|
@@ -869,7 +920,7 @@ export const _Worker = Resource(
|
|
|
869
920
|
|
|
870
921
|
if (this.phase === "delete") {
|
|
871
922
|
// Delete any queue consumers attached to this worker first
|
|
872
|
-
await deleteQueueConsumers(
|
|
923
|
+
await deleteQueueConsumers(api, workerName);
|
|
873
924
|
|
|
874
925
|
// @ts-ignore
|
|
875
926
|
await uploadWorkerScript({
|
|
@@ -879,7 +930,7 @@ export const _Worker = Resource(
|
|
|
879
930
|
script:
|
|
880
931
|
props.format === "cjs"
|
|
881
932
|
? "addEventListener('fetch', event => { event.respondWith(new Response('hello world')); });"
|
|
882
|
-
: "export default { fetch(request) { return new Response('hello world'); } }",
|
|
933
|
+
: "export default { fetch(request) { return new Response('hello world'); }, queue: () => {} }",
|
|
883
934
|
bindings: {} as B,
|
|
884
935
|
// we are writing a stub worker (to remove binding/event source dependencies)
|
|
885
936
|
// queue consumers will no longer exist by this point
|
|
@@ -1074,7 +1125,14 @@ export async function putWorker(
|
|
|
1074
1125
|
|
|
1075
1126
|
return formData;
|
|
1076
1127
|
},
|
|
1077
|
-
(err) =>
|
|
1128
|
+
(err) =>
|
|
1129
|
+
err.status === 404 ||
|
|
1130
|
+
err.status === 500 ||
|
|
1131
|
+
err.status === 503 ||
|
|
1132
|
+
// this is a tranient error that cloudflare throws randomly
|
|
1133
|
+
(err instanceof CloudflareApiError &&
|
|
1134
|
+
err.status === 400 &&
|
|
1135
|
+
err.message.match(/binding.*failed to generate/)),
|
|
1078
1136
|
10,
|
|
1079
1137
|
100,
|
|
1080
1138
|
);
|
|
@@ -1248,53 +1306,15 @@ async function getWorkerBindings(api: CloudflareApi, workerName: string) {
|
|
|
1248
1306
|
* @param api CloudflareApi instance
|
|
1249
1307
|
* @param workerName Name of the worker script
|
|
1250
1308
|
*/
|
|
1251
|
-
async function deleteQueueConsumers
|
|
1252
|
-
ctx: Context<Worker<B>>,
|
|
1309
|
+
async function deleteQueueConsumers(
|
|
1253
1310
|
api: CloudflareApi,
|
|
1254
1311
|
workerName: string,
|
|
1255
1312
|
): Promise<void> {
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
// Extract queue IDs from event sources
|
|
1259
|
-
const queueIds = eventSources.flatMap((eventSource) => {
|
|
1260
|
-
if (isQueue(eventSource)) {
|
|
1261
|
-
return [eventSource.id];
|
|
1262
|
-
}
|
|
1263
|
-
if (isQueueEventSource(eventSource)) {
|
|
1264
|
-
return [eventSource.queue.id];
|
|
1265
|
-
}
|
|
1266
|
-
return [];
|
|
1267
|
-
});
|
|
1313
|
+
const consumers = await listQueueConsumersForWorker(api, workerName);
|
|
1268
1314
|
|
|
1269
|
-
// Process each queue associated with this worker
|
|
1270
1315
|
await Promise.all(
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
// List all consumers for this queue
|
|
1274
|
-
const consumers = await listQueueConsumers(api, queueId);
|
|
1275
|
-
|
|
1276
|
-
// Filter consumers by worker name
|
|
1277
|
-
const workerConsumers = consumers.filter(
|
|
1278
|
-
(consumer) => consumer.scriptName === workerName,
|
|
1279
|
-
);
|
|
1280
|
-
|
|
1281
|
-
// Delete all consumers for this worker in parallel
|
|
1282
|
-
await Promise.all(
|
|
1283
|
-
workerConsumers.map(async (consumer) => {
|
|
1284
|
-
console.log(
|
|
1285
|
-
`Deleting queue consumer ${consumer.id} for worker ${workerName}`,
|
|
1286
|
-
);
|
|
1287
|
-
// Use the deleteQueueConsumer function from queue-consumer.ts
|
|
1288
|
-
await deleteQueueConsumer(api, consumer.queueId, consumer.id);
|
|
1289
|
-
}),
|
|
1290
|
-
);
|
|
1291
|
-
} catch (err) {
|
|
1292
|
-
if (err instanceof CloudflareApiError && err.status === 404) {
|
|
1293
|
-
// this is OK
|
|
1294
|
-
} else {
|
|
1295
|
-
throw err;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1316
|
+
consumers.map(async (consumer) => {
|
|
1317
|
+
await deleteQueueConsumer(api, consumer.queueId, consumer.consumerId);
|
|
1298
1318
|
}),
|
|
1299
1319
|
);
|
|
1300
1320
|
}
|
|
@@ -447,7 +447,7 @@ function processBindings(
|
|
|
447
447
|
// Service binding
|
|
448
448
|
services.push({
|
|
449
449
|
binding: bindingName,
|
|
450
|
-
service: binding.name,
|
|
450
|
+
service: "name" in binding ? binding.name : binding.service,
|
|
451
451
|
});
|
|
452
452
|
} else if (binding.type === "kv_namespace") {
|
|
453
453
|
// KV Namespace binding
|