alchemy 0.48.3 → 0.49.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/bin/alchemy.mjs +15 -15
- package/bin/commands/create.ts +0 -15
- package/bin/commands/deploy.ts +2 -0
- package/bin/commands/dev.ts +10 -1
- package/bin/services/execute-alchemy.ts +9 -0
- package/lib/alchemy.d.ts +13 -1
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +3 -1
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.js +22 -18
- package/lib/apply.js.map +1 -1
- package/lib/aws/s3-state-store.d.ts +0 -1
- package/lib/aws/s3-state-store.d.ts.map +1 -1
- package/lib/aws/s3-state-store.js +1 -3
- package/lib/aws/s3-state-store.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +21 -2
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +8 -1
- package/lib/cloudflare/bound.d.ts.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/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/miniflare/miniflare-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-options.js +4 -0
- package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -1
- package/lib/cloudflare/rate-limit.d.ts +63 -0
- package/lib/cloudflare/rate-limit.d.ts.map +1 -0
- package/lib/cloudflare/rate-limit.js +41 -0
- package/lib/cloudflare/rate-limit.js.map +1 -0
- package/lib/cloudflare/route.d.ts.map +1 -1
- package/lib/cloudflare/route.js +3 -0
- package/lib/cloudflare/route.js.map +1 -1
- package/lib/cloudflare/tanstack-start.d.ts.map +1 -1
- package/lib/cloudflare/tanstack-start.js +10 -1
- package/lib/cloudflare/tanstack-start.js.map +1 -1
- package/lib/cloudflare/website.d.ts +4 -0
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +14 -1
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +8 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.js +1 -1
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +11 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +15 -1
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/context.d.ts +2 -2
- package/lib/context.d.ts.map +1 -1
- package/lib/destroy.d.ts +3 -1
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +23 -4
- package/lib/destroy.js.map +1 -1
- package/lib/planetscale/password.js +1 -1
- package/lib/planetscale/password.js.map +1 -1
- package/lib/resource.d.ts +9 -0
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js +2 -0
- package/lib/resource.js.map +1 -1
- package/lib/scope.d.ts +15 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +9 -3
- package/lib/scope.js.map +1 -1
- package/lib/test/bun.d.ts +1 -24
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js.map +1 -1
- package/lib/test/options.d.ts +37 -0
- package/lib/test/options.d.ts.map +1 -0
- package/lib/test/options.js +1 -0
- package/lib/test/options.js.map +1 -0
- package/lib/test/vitest.d.ts +1 -24
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/ignore-matcher.d.ts.map +1 -1
- package/lib/util/ignore-matcher.js +1 -3
- package/lib/util/ignore-matcher.js.map +1 -1
- package/lib/util/safe-fetch.d.ts.map +1 -1
- package/lib/util/safe-fetch.js +9 -5
- package/lib/util/safe-fetch.js.map +1 -1
- package/package.json +2 -2
- package/src/alchemy.ts +16 -2
- package/src/apply.ts +23 -21
- package/src/aws/s3-state-store.ts +2 -2
- package/src/cloudflare/bindings.ts +22 -0
- package/src/cloudflare/bound.ts +29 -22
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/miniflare/miniflare-worker-options.ts +4 -0
- package/src/cloudflare/rate-limit.ts +70 -0
- package/src/cloudflare/route.ts +4 -0
- package/src/cloudflare/tanstack-start.ts +10 -1
- package/src/cloudflare/website.ts +20 -1
- package/src/cloudflare/worker-metadata.ts +7 -0
- package/src/cloudflare/worker.ts +1 -1
- package/src/cloudflare/wrangler.json.ts +30 -1
- package/src/context.ts +2 -2
- package/src/destroy.ts +27 -5
- package/src/planetscale/password.ts +1 -1
- package/src/resource.ts +11 -0
- package/src/scope.ts +21 -4
- package/src/test/bun.ts +1 -28
- package/src/test/options.ts +42 -0
- package/src/test/vitest.ts +1 -28
- package/src/util/ignore-matcher.ts +0 -1
- package/src/util/safe-fetch.ts +12 -5
- package/templates/tanstack-start/alchemy.run.ts +1 -3
- package/templates/tanstack-start/package.json +3 -3
- package/templates/tanstack-start/vite.config.ts +26 -25
package/src/apply.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { alchemy } from "./alchemy.ts";
|
|
2
2
|
import { context } from "./context.ts";
|
|
3
|
-
import { destroy } from "./destroy.ts";
|
|
3
|
+
import { destroy, DestroyStrategy } from "./destroy.ts";
|
|
4
4
|
import {
|
|
5
5
|
PROVIDERS,
|
|
6
6
|
ResourceFQN,
|
|
@@ -92,6 +92,7 @@ async function _apply<Out extends Resource>(
|
|
|
92
92
|
[ResourceKind]: resource[ResourceKind],
|
|
93
93
|
[ResourceScope]: scope,
|
|
94
94
|
[ResourceSeq]: resource[ResourceSeq],
|
|
95
|
+
[DestroyStrategy]: provider.options?.destroyStrategy ?? "sequential",
|
|
95
96
|
},
|
|
96
97
|
// deps: [...deps],
|
|
97
98
|
props,
|
|
@@ -113,9 +114,14 @@ async function _apply<Out extends Resource>(
|
|
|
113
114
|
});
|
|
114
115
|
if (
|
|
115
116
|
JSON.stringify(oldProps) === JSON.stringify(newProps) &&
|
|
116
|
-
alwaysUpdate !== true
|
|
117
|
+
alwaysUpdate !== true &&
|
|
118
|
+
!scope.force
|
|
117
119
|
) {
|
|
118
|
-
|
|
120
|
+
const innerScope = new Scope({
|
|
121
|
+
parent: scope,
|
|
122
|
+
scopeName: resource[ResourceID],
|
|
123
|
+
});
|
|
124
|
+
innerScope.skip();
|
|
119
125
|
if (!quiet) {
|
|
120
126
|
logger.task(resource[ResourceFQN], {
|
|
121
127
|
prefix: "skipped",
|
|
@@ -125,12 +131,7 @@ async function _apply<Out extends Resource>(
|
|
|
125
131
|
status: "success",
|
|
126
132
|
});
|
|
127
133
|
}
|
|
128
|
-
options?.resolveInnerScope?.(
|
|
129
|
-
new Scope({
|
|
130
|
-
parent: scope,
|
|
131
|
-
scopeName: resource[ResourceID],
|
|
132
|
-
}),
|
|
133
|
-
);
|
|
134
|
+
options?.resolveInnerScope?.(innerScope);
|
|
134
135
|
scope.telemetryClient.record({
|
|
135
136
|
event: "resource.skip",
|
|
136
137
|
resource: resource[ResourceKind],
|
|
@@ -174,7 +175,7 @@ async function _apply<Out extends Resource>(
|
|
|
174
175
|
props: state.oldProps,
|
|
175
176
|
state,
|
|
176
177
|
isReplacement: false,
|
|
177
|
-
replace:
|
|
178
|
+
replace: (force?: boolean) => {
|
|
178
179
|
if (phase === "create") {
|
|
179
180
|
throw new Error(
|
|
180
181
|
`Resource ${resource[ResourceKind]} ${resource[ResourceFQN]} cannot be replaced in create phase.`,
|
|
@@ -186,17 +187,6 @@ async function _apply<Out extends Resource>(
|
|
|
186
187
|
);
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
if (force) {
|
|
190
|
-
await destroy(resource, {
|
|
191
|
-
quiet: scope.quiet,
|
|
192
|
-
strategy: "sequential",
|
|
193
|
-
replace: {
|
|
194
|
-
props: state.oldProps,
|
|
195
|
-
output: resource,
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
190
|
isReplaced = true;
|
|
201
191
|
throw new ReplacedSignal(force);
|
|
202
192
|
},
|
|
@@ -209,6 +199,7 @@ async function _apply<Out extends Resource>(
|
|
|
209
199
|
{
|
|
210
200
|
isResource: true,
|
|
211
201
|
parent: scope,
|
|
202
|
+
destroyStrategy: provider.options?.destroyStrategy ?? "sequential",
|
|
212
203
|
},
|
|
213
204
|
async (scope) => {
|
|
214
205
|
options?.resolveInnerScope?.(scope);
|
|
@@ -223,6 +214,17 @@ async function _apply<Out extends Resource>(
|
|
|
223
214
|
);
|
|
224
215
|
}
|
|
225
216
|
|
|
217
|
+
if (error.force) {
|
|
218
|
+
await destroy(resource, {
|
|
219
|
+
quiet: scope.quiet,
|
|
220
|
+
strategy: resource[DestroyStrategy] ?? "sequential",
|
|
221
|
+
replace: {
|
|
222
|
+
props: state.oldProps,
|
|
223
|
+
output: resource,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
226
228
|
output = await alchemy.run(
|
|
227
229
|
resource[ResourceID],
|
|
228
230
|
{ isResource: true, parent: scope },
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "@aws-sdk/client-s3";
|
|
10
10
|
import { ResourceScope } from "../resource.ts";
|
|
11
11
|
import type { Scope } from "../scope.ts";
|
|
12
|
-
import {
|
|
12
|
+
import { deserialize, serialize } from "../serde.ts";
|
|
13
13
|
import type { State, StateStore } from "../state.ts";
|
|
14
14
|
import { ignore } from "../util/ignore.ts";
|
|
15
15
|
import { retry } from "./retry.ts";
|
|
@@ -55,7 +55,7 @@ export class S3StateStore implements StateStore {
|
|
|
55
55
|
*/
|
|
56
56
|
constructor(
|
|
57
57
|
public readonly scope: Scope,
|
|
58
|
-
|
|
58
|
+
options: S3StateStoreOptions = {},
|
|
59
59
|
) {
|
|
60
60
|
// Use the scope's chain to build the prefix, similar to how FileSystemStateStore builds its directory
|
|
61
61
|
const scopePath = scope.chain.join("/");
|
|
@@ -20,6 +20,7 @@ import type { Images } from "./images.ts";
|
|
|
20
20
|
import type { KVNamespaceResource } from "./kv-namespace.ts";
|
|
21
21
|
import type { PipelineResource } from "./pipeline.ts";
|
|
22
22
|
import type { QueueResource } from "./queue.ts";
|
|
23
|
+
import type { RateLimit } from "./rate-limit.ts";
|
|
23
24
|
import type { SecretKey } from "./secret-key.ts";
|
|
24
25
|
import type { Secret as CloudflareSecret } from "./secret.ts";
|
|
25
26
|
import type { VectorizeIndexResource } from "./vectorize-index.ts";
|
|
@@ -56,6 +57,7 @@ export type Binding =
|
|
|
56
57
|
| KVNamespaceResource
|
|
57
58
|
| PipelineResource
|
|
58
59
|
| QueueResource
|
|
60
|
+
| RateLimit
|
|
59
61
|
| R2BucketResource
|
|
60
62
|
| {
|
|
61
63
|
type: "kv_namespace";
|
|
@@ -108,6 +110,7 @@ export type WorkerBindingSpec =
|
|
|
108
110
|
| WorkerBindingPipeline
|
|
109
111
|
| WorkerBindingPlainText
|
|
110
112
|
| WorkerBindingQueue
|
|
113
|
+
| WorkerBindingRateLimit
|
|
111
114
|
| WorkerBindingR2Bucket
|
|
112
115
|
| WorkerBindingSecretKey
|
|
113
116
|
| WorkerBindingSecretText
|
|
@@ -284,6 +287,25 @@ export interface WorkerBindingQueue {
|
|
|
284
287
|
queue_name: string;
|
|
285
288
|
}
|
|
286
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Rate Limit binding type
|
|
292
|
+
*/
|
|
293
|
+
export interface WorkerBindingRateLimit {
|
|
294
|
+
/** The name of the binding */
|
|
295
|
+
name: string;
|
|
296
|
+
/** Type identifier for Rate Limit binding */
|
|
297
|
+
type: "ratelimit";
|
|
298
|
+
/** Namespace ID for the rate limit */
|
|
299
|
+
namespace_id: string;
|
|
300
|
+
/** Simple rate limiting configuration */
|
|
301
|
+
simple: {
|
|
302
|
+
/** Maximum number of requests */
|
|
303
|
+
limit: number;
|
|
304
|
+
/** Time period in seconds */
|
|
305
|
+
period: 60 | 10;
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
287
309
|
/**
|
|
288
310
|
* R2 Bucket binding type
|
|
289
311
|
*/
|
package/src/cloudflare/bound.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { HyperdriveResource as _Hyperdrive } from "./hyperdrive.ts";
|
|
|
15
15
|
import type { Images as _Images } from "./images.ts";
|
|
16
16
|
import type { PipelineResource as _Pipeline } from "./pipeline.ts";
|
|
17
17
|
import type { QueueResource as _Queue } from "./queue.ts";
|
|
18
|
+
import type { RateLimit } from "./rate-limit.ts";
|
|
18
19
|
import type { SecretKey } from "./secret-key.ts";
|
|
19
20
|
import type { Secret as CloudflareSecret } from "./secret.ts";
|
|
20
21
|
import type { VectorizeIndexResource as _VectorizeIndex } from "./vectorize-index.ts";
|
|
@@ -74,25 +75,31 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
|
|
|
74
75
|
? AnalyticsEngineDataset
|
|
75
76
|
: T extends _Pipeline<infer R>
|
|
76
77
|
? Pipeline<R>
|
|
77
|
-
: T extends
|
|
78
|
-
?
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
78
|
+
: T extends RateLimit
|
|
79
|
+
? {
|
|
80
|
+
limit(options: {
|
|
81
|
+
key: string;
|
|
82
|
+
}): Promise<{ success: boolean }>;
|
|
83
|
+
}
|
|
84
|
+
: T extends string
|
|
85
|
+
? string
|
|
86
|
+
: T extends BrowserRendering
|
|
87
|
+
? Fetcher
|
|
88
|
+
: T extends _Ai<infer M>
|
|
89
|
+
? Ai<M>
|
|
90
|
+
: T extends _Images
|
|
91
|
+
? ImagesBinding
|
|
92
|
+
: T extends _VersionMetadata
|
|
93
|
+
? WorkerVersionMetadata
|
|
94
|
+
: T extends Self
|
|
95
|
+
? Service
|
|
96
|
+
: T extends Json<infer T>
|
|
97
|
+
? T
|
|
98
|
+
: T extends _Container<
|
|
99
|
+
infer Obj
|
|
100
|
+
>
|
|
101
|
+
? DurableObjectNamespace<
|
|
102
|
+
Obj &
|
|
103
|
+
Rpc.DurableObjectBranded
|
|
104
|
+
>
|
|
105
|
+
: Service;
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ export * from "./permission-groups.ts";
|
|
|
36
36
|
export * from "./pipeline.ts";
|
|
37
37
|
export * from "./queue-consumer.ts";
|
|
38
38
|
export * from "./queue.ts";
|
|
39
|
+
export * from "./rate-limit.ts";
|
|
39
40
|
export * from "./react-router.ts";
|
|
40
41
|
export * from "./redirect-rule.ts";
|
|
41
42
|
export * from "./redwood.ts";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base interface for rate limiting configuration.
|
|
3
|
+
*/
|
|
4
|
+
export interface RateLimitProps {
|
|
5
|
+
/**
|
|
6
|
+
* A positive integer that uniquely defines this rate limiting configuration (e.g., namespace_id = 999).
|
|
7
|
+
*/
|
|
8
|
+
namespace_id: number;
|
|
9
|
+
/**
|
|
10
|
+
* Simple rate limiting configuration.
|
|
11
|
+
*/
|
|
12
|
+
simple: {
|
|
13
|
+
/**
|
|
14
|
+
* The limit (number of requests or API calls) to be applied. This is incremented when you call the limit() function in your Worker.
|
|
15
|
+
*/
|
|
16
|
+
limit: number;
|
|
17
|
+
/**
|
|
18
|
+
* The period, in seconds, to measure increments to the limit over. Must be either 10 or 60.
|
|
19
|
+
*/
|
|
20
|
+
period: 60 | 10;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* RateLimit binding for Cloudflare Workers.
|
|
26
|
+
*/
|
|
27
|
+
export interface RateLimit extends RateLimitProps {
|
|
28
|
+
type: "ratelimit";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Rate limiting binding for Cloudflare Workers.
|
|
33
|
+
*
|
|
34
|
+
* The RateLimit binding provides access to Cloudflare's rate limiting functionality,
|
|
35
|
+
* allowing you to implement rate limiting directly in your Workers without needing
|
|
36
|
+
* to rely on external services.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { Worker, RateLimit } from "alchemy/cloudflare";
|
|
41
|
+
*
|
|
42
|
+
* const rateLimit = RateLimit({
|
|
43
|
+
* namespace_id: 1001,
|
|
44
|
+
* simple: {
|
|
45
|
+
* limit: 1500,
|
|
46
|
+
* period: 60
|
|
47
|
+
* }
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* await Worker("my-worker", {
|
|
51
|
+
* name: "my-worker",
|
|
52
|
+
* entrypoint: "./src/worker.ts",
|
|
53
|
+
* bindings: {
|
|
54
|
+
* MY_RATE_LIMIT: rateLimit
|
|
55
|
+
* }
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/
|
|
60
|
+
*
|
|
61
|
+
* Creates a rate limiting binding for Cloudflare Workers.
|
|
62
|
+
* @returns A RateLimit binding object.
|
|
63
|
+
*/
|
|
64
|
+
export function RateLimit(options: RateLimitProps): RateLimit {
|
|
65
|
+
return {
|
|
66
|
+
type: "ratelimit",
|
|
67
|
+
namespace_id: options.namespace_id,
|
|
68
|
+
simple: options.simple,
|
|
69
|
+
};
|
|
70
|
+
}
|
package/src/cloudflare/route.ts
CHANGED
|
@@ -282,6 +282,10 @@ async function updateRoute(
|
|
|
282
282
|
},
|
|
283
283
|
);
|
|
284
284
|
|
|
285
|
+
if (updateResponse.status === 404) {
|
|
286
|
+
return await createRoute(api, zoneId, pattern, script);
|
|
287
|
+
}
|
|
288
|
+
|
|
285
289
|
if (!updateResponse.ok) {
|
|
286
290
|
return await handleApiError(updateResponse, "updating", "Route", pattern);
|
|
287
291
|
}
|
|
@@ -17,7 +17,16 @@ export async function TanStackStart<B extends Bindings>(
|
|
|
17
17
|
): Promise<TanStackStart<B>> {
|
|
18
18
|
return Website(id, {
|
|
19
19
|
...props,
|
|
20
|
-
command: props?.command ?? "
|
|
20
|
+
command: props?.command ?? "vite build",
|
|
21
|
+
commandEnv: {
|
|
22
|
+
// TODO(sam): should probably be set for all Websites, or at least all Vite Websites
|
|
23
|
+
// needs to be set or else jsxDev is included in the bundle and breaks in production
|
|
24
|
+
NODE_ENV: process.env.NODE_ENV ?? "production",
|
|
25
|
+
},
|
|
26
|
+
noBundle: true,
|
|
27
|
+
dev: props?.dev ?? {
|
|
28
|
+
command: "vite dev",
|
|
29
|
+
},
|
|
21
30
|
wrangler: props?.wrangler ?? true,
|
|
22
31
|
main: props?.main ?? ".output/server/index.mjs",
|
|
23
32
|
compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])],
|
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { alchemy } from "../alchemy.ts";
|
|
3
3
|
import { Exec } from "../os/exec.ts";
|
|
4
4
|
import { Scope } from "../scope.ts";
|
|
5
|
+
import { isSecret } from "../secret.ts";
|
|
5
6
|
import { detectPackageManager } from "../util/detect-package-manager.ts";
|
|
6
7
|
import { Assets } from "./assets.ts";
|
|
7
8
|
import type { Bindings } from "./bindings.ts";
|
|
@@ -18,6 +19,11 @@ export interface WebsiteProps<B extends Bindings>
|
|
|
18
19
|
*/
|
|
19
20
|
command?: string;
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Additional environment variables to set when running the command
|
|
24
|
+
*/
|
|
25
|
+
commandEnv?: Record<string, string>;
|
|
26
|
+
|
|
21
27
|
/**
|
|
22
28
|
* Whether to memoize the command (only re-run if the command changes)
|
|
23
29
|
*
|
|
@@ -224,7 +230,20 @@ export default {
|
|
|
224
230
|
await Exec("build", {
|
|
225
231
|
cwd,
|
|
226
232
|
command: props.command,
|
|
227
|
-
env:
|
|
233
|
+
env: {
|
|
234
|
+
...(props.env ?? {}),
|
|
235
|
+
...(props.commandEnv ?? {}),
|
|
236
|
+
...Object.fromEntries(
|
|
237
|
+
Object.entries(props.bindings ?? {}).flatMap(([key, value]) => {
|
|
238
|
+
if (isSecret(value)) {
|
|
239
|
+
return [[key, value.unencrypted]];
|
|
240
|
+
} else if (typeof value === "string") {
|
|
241
|
+
return [[key, value]];
|
|
242
|
+
}
|
|
243
|
+
return [];
|
|
244
|
+
}),
|
|
245
|
+
),
|
|
246
|
+
},
|
|
228
247
|
memoize: props.memoize,
|
|
229
248
|
});
|
|
230
249
|
}
|
|
@@ -565,6 +565,13 @@ export async function prepareWorkerMetadata(
|
|
|
565
565
|
configureClassMigration(bindingName, binding);
|
|
566
566
|
}
|
|
567
567
|
(meta.containers ??= []).push({ class_name: binding.className });
|
|
568
|
+
} else if (binding.type === "ratelimit") {
|
|
569
|
+
meta.bindings.push({
|
|
570
|
+
type: "ratelimit",
|
|
571
|
+
name: bindingName,
|
|
572
|
+
namespace_id: binding.namespace_id.toString(),
|
|
573
|
+
simple: binding.simple,
|
|
574
|
+
});
|
|
568
575
|
} else {
|
|
569
576
|
assertNever(
|
|
570
577
|
binding,
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -1166,7 +1166,7 @@ export const _Worker = Resource(
|
|
|
1166
1166
|
|
|
1167
1167
|
if (oldName !== newName) {
|
|
1168
1168
|
if (dispatchNamespace) {
|
|
1169
|
-
|
|
1169
|
+
this.replace(true);
|
|
1170
1170
|
} else {
|
|
1171
1171
|
const renameResponse = await api.patch(
|
|
1172
1172
|
`/accounts/${api.accountId}/workers/services/${oldName}`,
|
|
@@ -4,13 +4,21 @@ import type { Context } from "../context.ts";
|
|
|
4
4
|
import { formatJson } from "../fs/static-json-file.ts";
|
|
5
5
|
import { Resource } from "../resource.ts";
|
|
6
6
|
import { assertNever } from "../util/assert-never.ts";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
Self,
|
|
9
|
+
type Bindings,
|
|
10
|
+
type WorkerBindingRateLimit,
|
|
11
|
+
} from "./bindings.ts";
|
|
8
12
|
import type { DurableObjectNamespace } from "./durable-object-namespace.ts";
|
|
9
13
|
import type { EventSource } from "./event-source.ts";
|
|
10
14
|
import { isQueueEventSource } from "./event-source.ts";
|
|
11
15
|
import { isQueue } from "./queue.ts";
|
|
12
16
|
import type { Worker, WorkerProps } from "./worker.ts";
|
|
13
17
|
|
|
18
|
+
type WranglerJsonRateLimit = Omit<WorkerBindingRateLimit, "type"> & {
|
|
19
|
+
type: "rate_limit";
|
|
20
|
+
};
|
|
21
|
+
|
|
14
22
|
/**
|
|
15
23
|
* Properties for wrangler.json configuration file
|
|
16
24
|
*/
|
|
@@ -447,6 +455,13 @@ export interface WranglerJsonSpec {
|
|
|
447
455
|
namespace: string;
|
|
448
456
|
experimental_remote?: boolean;
|
|
449
457
|
}[];
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Unsafe bindings section for experimental features
|
|
461
|
+
*/
|
|
462
|
+
unsafe?: {
|
|
463
|
+
bindings: WranglerJsonRateLimit[];
|
|
464
|
+
};
|
|
450
465
|
}
|
|
451
466
|
|
|
452
467
|
/**
|
|
@@ -534,6 +549,7 @@ function processBindings(
|
|
|
534
549
|
namespace: string;
|
|
535
550
|
experimental_remote?: boolean;
|
|
536
551
|
}[] = [];
|
|
552
|
+
const unsafeBindings: WranglerJsonRateLimit[] = [];
|
|
537
553
|
const containers: {
|
|
538
554
|
class_name: string;
|
|
539
555
|
}[] = [];
|
|
@@ -721,6 +737,13 @@ function processBindings(
|
|
|
721
737
|
namespace: binding.namespaceName,
|
|
722
738
|
experimental_remote: true,
|
|
723
739
|
});
|
|
740
|
+
} else if (binding.type === "ratelimit") {
|
|
741
|
+
unsafeBindings.push({
|
|
742
|
+
name: bindingName,
|
|
743
|
+
type: "rate_limit",
|
|
744
|
+
namespace_id: binding.namespace_id.toString(),
|
|
745
|
+
simple: binding.simple,
|
|
746
|
+
});
|
|
724
747
|
} else if (binding.type === "secret_key") {
|
|
725
748
|
// no-op
|
|
726
749
|
} else if (binding.type === "container") {
|
|
@@ -802,4 +825,10 @@ function processBindings(
|
|
|
802
825
|
if (dispatchNamespaces.length > 0) {
|
|
803
826
|
spec.dispatch_namespaces = dispatchNamespaces;
|
|
804
827
|
}
|
|
828
|
+
|
|
829
|
+
if (unsafeBindings.length > 0) {
|
|
830
|
+
spec.unsafe = {
|
|
831
|
+
bindings: unsafeBindings,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
805
834
|
}
|
package/src/context.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface BaseContext<Out extends Resource> {
|
|
|
57
57
|
* Indicate that this resource is being replaced.
|
|
58
58
|
* This will cause the resource to be deleted at the end of the stack's CREATE phase.
|
|
59
59
|
*/
|
|
60
|
-
replace(force?: boolean):
|
|
60
|
+
replace(force?: boolean): never;
|
|
61
61
|
/**
|
|
62
62
|
* Terminate the resource lifecycle handler and destroy the resource.
|
|
63
63
|
*
|
|
@@ -102,7 +102,7 @@ export function context<
|
|
|
102
102
|
seq: number;
|
|
103
103
|
props: Props;
|
|
104
104
|
state: State<Kind, Props, Out>;
|
|
105
|
-
replace: (force?: boolean) =>
|
|
105
|
+
replace: (force?: boolean) => never;
|
|
106
106
|
isReplacement?: boolean;
|
|
107
107
|
}): Context<Out> {
|
|
108
108
|
type InternalSymbols =
|
package/src/destroy.ts
CHANGED
|
@@ -16,9 +16,13 @@ import { logger } from "./util/logger.ts";
|
|
|
16
16
|
|
|
17
17
|
export class DestroyedSignal extends Error {}
|
|
18
18
|
|
|
19
|
+
export type DestroyStrategy = "sequential" | "parallel";
|
|
20
|
+
|
|
21
|
+
export const DestroyStrategy = Symbol.for("alchemy::DestroyStrategy");
|
|
22
|
+
|
|
19
23
|
export interface DestroyOptions {
|
|
20
24
|
quiet?: boolean;
|
|
21
|
-
strategy?:
|
|
25
|
+
strategy?: DestroyStrategy;
|
|
22
26
|
replace?: {
|
|
23
27
|
props?: ResourceProps | undefined;
|
|
24
28
|
output?: Resource<string>;
|
|
@@ -40,7 +44,7 @@ export async function destroy<Type extends string>(
|
|
|
40
44
|
if (isScopeArgs(args)) {
|
|
41
45
|
const [scope] = args;
|
|
42
46
|
const options = {
|
|
43
|
-
strategy: "sequential",
|
|
47
|
+
strategy: scope.destroyStrategy ?? "sequential",
|
|
44
48
|
...(args[1] ?? {}),
|
|
45
49
|
} satisfies DestroyOptions;
|
|
46
50
|
|
|
@@ -118,7 +122,20 @@ export async function destroy<Type extends string>(
|
|
|
118
122
|
fqn: instance[ResourceFQN],
|
|
119
123
|
seq: instance[ResourceSeq],
|
|
120
124
|
props: options?.replace?.props ?? state.props,
|
|
121
|
-
state
|
|
125
|
+
state: options?.replace?.output
|
|
126
|
+
? {
|
|
127
|
+
output: options.replace.output,
|
|
128
|
+
status: "deleting",
|
|
129
|
+
oldProps: options.replace.props,
|
|
130
|
+
data: {},
|
|
131
|
+
kind: instance[ResourceKind],
|
|
132
|
+
id: instance[ResourceID],
|
|
133
|
+
fqn: instance[ResourceFQN],
|
|
134
|
+
seq: instance[ResourceSeq],
|
|
135
|
+
props: options.replace.props,
|
|
136
|
+
}
|
|
137
|
+
: state,
|
|
138
|
+
// TODO(sam|michael): should this always be false or !!options?.replace
|
|
122
139
|
isReplacement: false,
|
|
123
140
|
replace: () => {
|
|
124
141
|
throw new Error("Cannot replace a resource that is being deleted");
|
|
@@ -134,6 +151,7 @@ export async function destroy<Type extends string>(
|
|
|
134
151
|
// TODO(sam): this is an awful hack to differentiate between naked scopes and resources
|
|
135
152
|
isResource: instance[ResourceKind] !== "alchemy::Scope",
|
|
136
153
|
parent: scope,
|
|
154
|
+
destroyStrategy: instance[DestroyStrategy] ?? "sequential",
|
|
137
155
|
},
|
|
138
156
|
async (scope) => {
|
|
139
157
|
nestedScope = options?.replace?.props == null ? scope : undefined;
|
|
@@ -151,13 +169,17 @@ export async function destroy<Type extends string>(
|
|
|
151
169
|
}
|
|
152
170
|
}
|
|
153
171
|
|
|
172
|
+
const destroyOptions = {
|
|
173
|
+
...options,
|
|
174
|
+
strategy: instance[DestroyStrategy] ?? "sequential",
|
|
175
|
+
};
|
|
154
176
|
if (nestedScope) {
|
|
155
|
-
await destroy(nestedScope,
|
|
177
|
+
await destroy(nestedScope, destroyOptions);
|
|
156
178
|
}
|
|
157
179
|
|
|
158
180
|
if (options?.replace == null) {
|
|
159
181
|
if (nestedScope) {
|
|
160
|
-
await destroy(nestedScope,
|
|
182
|
+
await destroy(nestedScope, destroyOptions);
|
|
161
183
|
}
|
|
162
184
|
await scope.deleteResource(instance[ResourceID]);
|
|
163
185
|
} else {
|
|
@@ -306,7 +306,7 @@ export const Password = Resource(
|
|
|
306
306
|
this.output.cidrs.length === props.cidrs.length &&
|
|
307
307
|
this.output.cidrs.every((cidr, i) => cidr === props.cidrs![i])))
|
|
308
308
|
) {
|
|
309
|
-
return
|
|
309
|
+
return this.replace();
|
|
310
310
|
}
|
|
311
311
|
const updateResponse = await api.patch(
|
|
312
312
|
`/organizations/${props.organizationId}/databases/${databaseName}/branches/${branchName}/passwords/${this.output.id}`,
|
package/src/resource.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apply } from "./apply.ts";
|
|
2
2
|
import type { Context } from "./context.ts";
|
|
3
|
+
import { DestroyStrategy } from "./destroy.ts";
|
|
3
4
|
import { Scope as _Scope, type Scope } from "./scope.ts";
|
|
4
5
|
|
|
5
6
|
declare global {
|
|
@@ -59,6 +60,13 @@ export interface ProviderOptions {
|
|
|
59
60
|
* If true, the resource will be updated even if the inputs have not changed.
|
|
60
61
|
*/
|
|
61
62
|
alwaysUpdate: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The strategy to use when destroying the resource.
|
|
66
|
+
*
|
|
67
|
+
* @default "sequential"
|
|
68
|
+
*/
|
|
69
|
+
destroyStrategy?: DestroyStrategy;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
export type ResourceProps = {
|
|
@@ -81,6 +89,7 @@ export interface PendingResource<Out = unknown> extends Promise<Out> {
|
|
|
81
89
|
[ResourceFQN]: ResourceFQN;
|
|
82
90
|
[ResourceScope]: Scope;
|
|
83
91
|
[ResourceSeq]: number;
|
|
92
|
+
[DestroyStrategy]: DestroyStrategy;
|
|
84
93
|
[InnerResourceScope]: Promise<Scope>;
|
|
85
94
|
}
|
|
86
95
|
|
|
@@ -90,6 +99,7 @@ export interface Resource<Kind extends ResourceKind = ResourceKind> {
|
|
|
90
99
|
[ResourceFQN]: ResourceFQN;
|
|
91
100
|
[ResourceScope]: Scope;
|
|
92
101
|
[ResourceSeq]: number;
|
|
102
|
+
[DestroyStrategy]: DestroyStrategy;
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
// helper for semantic syntax highlighting (color as a type/class instead of function/value)
|
|
@@ -167,6 +177,7 @@ export function Resource<
|
|
|
167
177
|
[ResourceFQN]: scope.fqn(resourceID),
|
|
168
178
|
[ResourceSeq]: seq,
|
|
169
179
|
[ResourceScope]: scope,
|
|
180
|
+
[DestroyStrategy]: options?.destroyStrategy ?? "sequential",
|
|
170
181
|
[InnerResourceScope]: new Promise<Scope>((resolve) => {
|
|
171
182
|
resolveInnerScope = resolve;
|
|
172
183
|
}),
|