alchemy 0.49.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/alchemy.mjs +2 -2
- package/lib/alchemy.d.ts +7 -1
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +2 -1
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.js +10 -7
- 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/api-response.d.ts +2 -2
- package/lib/cloudflare/api-response.d.ts.map +1 -1
- package/lib/cloudflare/api-response.js +2 -1
- package/lib/cloudflare/api-response.js.map +1 -1
- package/lib/cloudflare/bucket.d.ts +70 -105
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +127 -244
- package/lib/cloudflare/bucket.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/nuxt.js +1 -1
- package/lib/cloudflare/nuxt.js.map +1 -1
- package/lib/cloudflare/orange.d.ts.map +1 -1
- package/lib/cloudflare/orange.js +4 -1
- package/lib/cloudflare/orange.js.map +1 -1
- package/lib/cloudflare/redwood.d.ts.map +1 -1
- package/lib/cloudflare/redwood.js +5 -1
- package/lib/cloudflare/redwood.js.map +1 -1
- 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/vite.d.ts.map +1 -1
- package/lib/cloudflare/vite.js +2 -10
- package/lib/cloudflare/vite.js.map +1 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +1 -0
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +1 -0
- package/lib/cloudflare/worker.js.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/os/exec.d.ts +1 -1
- package/lib/os/exec.d.ts.map +1 -1
- package/lib/os/exec.js +1 -1
- package/lib/os/exec.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 +8 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +6 -2
- package/lib/scope.js.map +1 -1
- package/lib/test/options.d.ts +7 -0
- package/lib/test/options.d.ts.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 +9 -2
- package/src/apply.ts +10 -9
- package/src/aws/s3-state-store.ts +2 -2
- package/src/cloudflare/api-response.ts +7 -3
- package/src/cloudflare/bucket.ts +276 -468
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/nuxt.ts +1 -1
- package/src/cloudflare/orange.ts +4 -1
- package/src/cloudflare/redwood.ts +6 -1
- package/src/cloudflare/route.ts +4 -0
- package/src/cloudflare/vite.ts +2 -10
- package/src/cloudflare/website.ts +1 -0
- package/src/cloudflare/worker.ts +2 -1
- package/src/destroy.ts +27 -5
- package/src/os/exec.ts +2 -2
- package/src/resource.ts +11 -0
- package/src/scope.ts +12 -3
- package/src/test/options.ts +8 -0
- package/src/util/safe-fetch.ts +12 -5
package/src/cloudflare/nuxt.ts
CHANGED
|
@@ -55,7 +55,7 @@ export async function Nuxt<B extends Bindings>(
|
|
|
55
55
|
...props,
|
|
56
56
|
noBundle: props?.noBundle ?? true,
|
|
57
57
|
// Default build command, can be overridden by props.command
|
|
58
|
-
command: props?.command ?? "
|
|
58
|
+
command: props?.command ?? "nuxt build",
|
|
59
59
|
// Default entry point for cloudflare-module preset
|
|
60
60
|
main: props?.main ?? "./.output/server/index.mjs",
|
|
61
61
|
// Default static assets directory for cloudflare-module preset
|
package/src/cloudflare/orange.ts
CHANGED
|
@@ -40,7 +40,10 @@ export async function Orange<B extends Bindings>(
|
|
|
40
40
|
): Promise<Orange<B>> {
|
|
41
41
|
return Website(id, {
|
|
42
42
|
...props,
|
|
43
|
-
command: props?.command ?? "
|
|
43
|
+
command: props?.command ?? "vite build",
|
|
44
|
+
dev: props?.dev ?? {
|
|
45
|
+
command: "vite dev",
|
|
46
|
+
},
|
|
44
47
|
// The entrypoint and the Wrangler main must differ for the Cloudflare vite plugin
|
|
45
48
|
// to bundle the application correctly, and alchemy must run our bundler on the Vite
|
|
46
49
|
// output since we don't resolve vite imports.
|
|
@@ -43,7 +43,12 @@ export async function Redwood<B extends Bindings>(
|
|
|
43
43
|
): Promise<Redwood<B>> {
|
|
44
44
|
return Website(id, {
|
|
45
45
|
...props,
|
|
46
|
-
command:
|
|
46
|
+
command:
|
|
47
|
+
props?.command ??
|
|
48
|
+
"rm -rf ./node_modules/.vite && RWSDK_DEPLOY=1 vite build",
|
|
49
|
+
dev: props?.dev ?? {
|
|
50
|
+
command: "vite dev",
|
|
51
|
+
},
|
|
47
52
|
noBundle: props?.noBundle ?? true,
|
|
48
53
|
wrangler:
|
|
49
54
|
props?.wrangler === false
|
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
|
}
|
package/src/cloudflare/vite.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { detectPackageManager } from "../util/detect-package-manager.ts";
|
|
3
2
|
import type { Assets } from "./assets.ts";
|
|
4
3
|
import type { Bindings } from "./bindings.ts";
|
|
5
4
|
import { Website, type WebsiteProps } from "./website.ts";
|
|
@@ -18,14 +17,6 @@ export async function Vite<B extends Bindings>(
|
|
|
18
17
|
props: ViteProps<B>,
|
|
19
18
|
): Promise<Vite<B>> {
|
|
20
19
|
const defaultAssets = path.join("dist", "client");
|
|
21
|
-
const packageManager = await detectPackageManager();
|
|
22
|
-
const devCommand = {
|
|
23
|
-
npm: "npx vite dev",
|
|
24
|
-
bun: "bun vite dev",
|
|
25
|
-
pnpm: "pnpm vite dev",
|
|
26
|
-
yarn: "yarn vite dev",
|
|
27
|
-
deno: "deno vite dev",
|
|
28
|
-
}[packageManager];
|
|
29
20
|
return Website(id, {
|
|
30
21
|
...props,
|
|
31
22
|
spa: true,
|
|
@@ -36,8 +27,9 @@ export async function Vite<B extends Bindings>(
|
|
|
36
27
|
dist: props.assets.dist ?? defaultAssets,
|
|
37
28
|
}
|
|
38
29
|
: (props.assets ?? defaultAssets),
|
|
30
|
+
command: props.command ?? "vite build",
|
|
39
31
|
dev: props.dev ?? {
|
|
40
|
-
command:
|
|
32
|
+
command: "vite dev",
|
|
41
33
|
},
|
|
42
34
|
});
|
|
43
35
|
}
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -1088,6 +1088,7 @@ export const _Worker = Resource(
|
|
|
1088
1088
|
command: props.dev.command,
|
|
1089
1089
|
cwd: props.dev.cwd || props.cwd || process.cwd(),
|
|
1090
1090
|
env: {
|
|
1091
|
+
...(process.env ?? {}),
|
|
1091
1092
|
...props.env,
|
|
1092
1093
|
...Object.fromEntries(
|
|
1093
1094
|
Object.entries(props.bindings ?? {}).flatMap(([key, value]) =>
|
|
@@ -1834,7 +1835,7 @@ async function createDevCommand(props: {
|
|
|
1834
1835
|
id: string;
|
|
1835
1836
|
command: string;
|
|
1836
1837
|
cwd: string;
|
|
1837
|
-
env: Record<string, string>;
|
|
1838
|
+
env: Record<string, string | undefined>;
|
|
1838
1839
|
}): Promise<{ url: string }> {
|
|
1839
1840
|
const persistFile = path.join(process.cwd(), ".alchemy", `${props.id}.pid`);
|
|
1840
1841
|
if (await exists(persistFile)) {
|
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 {
|
package/src/os/exec.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface ExecProps {
|
|
|
48
48
|
/**
|
|
49
49
|
* Environment variables to set
|
|
50
50
|
*/
|
|
51
|
-
env?: Record<string, string | Secret<string
|
|
51
|
+
env?: Record<string, string | Secret<string> | undefined>;
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Whether to inherit stdio from parent process
|
|
@@ -218,7 +218,7 @@ export const Exec = Resource(
|
|
|
218
218
|
for (const [key, value] of Object.entries(props.env)) {
|
|
219
219
|
if (typeof value === "string") {
|
|
220
220
|
processedEnv[key] = value;
|
|
221
|
-
} else {
|
|
221
|
+
} else if (value) {
|
|
222
222
|
// Handle Secret objects
|
|
223
223
|
processedEnv[key] = value.unencrypted;
|
|
224
224
|
}
|
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
|
}),
|
package/src/scope.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import util from "node:util";
|
|
3
3
|
import type { Phase } from "./alchemy.ts";
|
|
4
|
-
import { destroy, destroyAll } from "./destroy.ts";
|
|
4
|
+
import { destroy, destroyAll, DestroyStrategy } from "./destroy.ts";
|
|
5
5
|
import {
|
|
6
6
|
ResourceFQN,
|
|
7
7
|
ResourceID,
|
|
@@ -56,6 +56,12 @@ export interface ScopeOptions {
|
|
|
56
56
|
* @default false
|
|
57
57
|
*/
|
|
58
58
|
force?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The strategy to use when destroying resources.
|
|
61
|
+
*
|
|
62
|
+
* @default "sequential"
|
|
63
|
+
*/
|
|
64
|
+
destroyStrategy?: DestroyStrategy;
|
|
59
65
|
telemetryClient?: ITelemetryClient;
|
|
60
66
|
logger?: LoggerApi;
|
|
61
67
|
}
|
|
@@ -127,6 +133,7 @@ export class Scope {
|
|
|
127
133
|
public readonly local: boolean;
|
|
128
134
|
public readonly watch: boolean;
|
|
129
135
|
public readonly force: boolean;
|
|
136
|
+
public readonly destroyStrategy: DestroyStrategy;
|
|
130
137
|
public readonly logger: LoggerApi;
|
|
131
138
|
public readonly telemetryClient: ITelemetryClient;
|
|
132
139
|
public readonly dataMutex: AsyncMutex;
|
|
@@ -185,7 +192,8 @@ export class Scope {
|
|
|
185
192
|
this.local = options.local ?? this.parent?.local ?? false;
|
|
186
193
|
this.watch = options.watch ?? this.parent?.watch ?? false;
|
|
187
194
|
this.force = options.force ?? this.parent?.force ?? false;
|
|
188
|
-
|
|
195
|
+
this.destroyStrategy =
|
|
196
|
+
options.destroyStrategy ?? this.parent?.destroyStrategy ?? "sequential";
|
|
189
197
|
if (this.local) {
|
|
190
198
|
this.logger.warnOnce(
|
|
191
199
|
"Development mode is in beta. Please report any issues to https://github.com/sam-goodwin/alchemy/issues.",
|
|
@@ -322,6 +330,7 @@ export class Scope {
|
|
|
322
330
|
[ResourceKind]: "alchemy::Scope",
|
|
323
331
|
[ResourceScope]: this,
|
|
324
332
|
[ResourceSeq]: this.seq(),
|
|
333
|
+
[DestroyStrategy]: this.destroyStrategy,
|
|
325
334
|
},
|
|
326
335
|
props: {},
|
|
327
336
|
}
|
|
@@ -433,7 +442,7 @@ export class Scope {
|
|
|
433
442
|
);
|
|
434
443
|
await destroyAll(orphans, {
|
|
435
444
|
quiet: this.quiet,
|
|
436
|
-
strategy:
|
|
445
|
+
strategy: this.destroyStrategy,
|
|
437
446
|
force: shouldForce,
|
|
438
447
|
});
|
|
439
448
|
this.rootTelemetryClient?.record({
|
package/src/test/options.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DestroyStrategy } from "../destroy.ts";
|
|
1
2
|
import type { StateStoreType } from "../state.ts";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -31,4 +32,11 @@ export interface TestOptions {
|
|
|
31
32
|
* Prefix to use for the scope to isolate tests and environments.
|
|
32
33
|
*/
|
|
33
34
|
prefix?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The strategy to use when destroying resources.
|
|
38
|
+
*
|
|
39
|
+
* @default "sequential"
|
|
40
|
+
*/
|
|
41
|
+
destroyStrategy?: DestroyStrategy;
|
|
34
42
|
}
|
package/src/util/safe-fetch.ts
CHANGED
|
@@ -15,13 +15,10 @@ export async function safeFetch(
|
|
|
15
15
|
dispatcher: await dispatcher(),
|
|
16
16
|
} as RequestInit);
|
|
17
17
|
} catch (err: any) {
|
|
18
|
+
console.log("err", err);
|
|
18
19
|
latestErr = err;
|
|
19
20
|
const shouldRetry =
|
|
20
|
-
err
|
|
21
|
-
err?.code === "ECONNRESET" ||
|
|
22
|
-
err?.code === "UND_ERR_CONNECT_TIMEOUT" ||
|
|
23
|
-
err?.code === "EPIPE" ||
|
|
24
|
-
err?.name === "FetchError";
|
|
21
|
+
isTransientNetworkError(err) || isTransientNetworkError(err.cause);
|
|
25
22
|
|
|
26
23
|
if (!shouldRetry || attempt === retries) {
|
|
27
24
|
throw err;
|
|
@@ -34,6 +31,16 @@ export async function safeFetch(
|
|
|
34
31
|
throw latestErr;
|
|
35
32
|
}
|
|
36
33
|
|
|
34
|
+
function isTransientNetworkError(err: any) {
|
|
35
|
+
return (
|
|
36
|
+
err?.code === "UND_ERR_SOCKET" ||
|
|
37
|
+
err?.code === "ECONNRESET" ||
|
|
38
|
+
err?.code === "UND_ERR_CONNECT_TIMEOUT" ||
|
|
39
|
+
err?.code === "EPIPE" ||
|
|
40
|
+
err?.name === "FetchError"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
let agent:
|
|
38
45
|
| {
|
|
39
46
|
value: Agent | undefined;
|