alchemy 2.0.0-beta.64 → 2.0.0-beta.65
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/EC2/InternetGateway.d.ts.map +1 -1
- package/lib/AWS/EC2/InternetGateway.js +8 -3
- package/lib/AWS/EC2/InternetGateway.js.map +1 -1
- package/lib/AWS/EC2/LingeringEnis.d.ts +26 -0
- package/lib/AWS/EC2/LingeringEnis.d.ts.map +1 -0
- package/lib/AWS/EC2/LingeringEnis.js +128 -0
- package/lib/AWS/EC2/LingeringEnis.js.map +1 -0
- package/lib/AWS/EC2/SecurityGroup.d.ts.map +1 -1
- package/lib/AWS/EC2/SecurityGroup.js +14 -14
- package/lib/AWS/EC2/SecurityGroup.js.map +1 -1
- package/lib/AWS/EC2/Subnet.d.ts.map +1 -1
- package/lib/AWS/EC2/Subnet.js +13 -21
- package/lib/AWS/EC2/Subnet.js.map +1 -1
- package/lib/AWS/ECR/Image.d.ts +1 -0
- package/lib/AWS/ECR/Image.d.ts.map +1 -1
- package/lib/AWS/EKS/Addon.d.ts.map +1 -1
- package/lib/AWS/EKS/Addon.js +15 -3
- package/lib/AWS/EKS/Addon.js.map +1 -1
- package/lib/AWS/EKS/Deployment.d.ts +10 -1
- package/lib/AWS/EKS/Deployment.d.ts.map +1 -1
- package/lib/AWS/EKS/Deployment.js +10 -4
- package/lib/AWS/EKS/Deployment.js.map +1 -1
- package/lib/AWS/EKS/HyperPod.d.ts +57 -0
- package/lib/AWS/EKS/HyperPod.d.ts.map +1 -0
- package/lib/AWS/EKS/HyperPod.js +46 -0
- package/lib/AWS/EKS/HyperPod.js.map +1 -0
- package/lib/AWS/EKS/Job.d.ts +10 -1
- package/lib/AWS/EKS/Job.d.ts.map +1 -1
- package/lib/AWS/EKS/Job.js +16 -6
- package/lib/AWS/EKS/Job.js.map +1 -1
- package/lib/AWS/EKS/internal/client.d.ts +4 -3
- package/lib/AWS/EKS/internal/client.d.ts.map +1 -1
- package/lib/AWS/EKS/internal/client.js +33 -2
- package/lib/AWS/EKS/internal/client.js.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +1 -1
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/AWS/Providers.d.ts.map +1 -1
- package/lib/AWS/Providers.js +4 -1
- package/lib/AWS/Providers.js.map +1 -1
- package/lib/AWS/SageMaker/Cluster.d.ts +327 -0
- package/lib/AWS/SageMaker/Cluster.d.ts.map +1 -0
- package/lib/AWS/SageMaker/Cluster.js +469 -0
- package/lib/AWS/SageMaker/Cluster.js.map +1 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.d.ts +96 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.d.ts.map +1 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.js +270 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.js.map +1 -0
- package/lib/AWS/SageMaker/ComputeQuota.d.ts +106 -0
- package/lib/AWS/SageMaker/ComputeQuota.d.ts.map +1 -0
- package/lib/AWS/SageMaker/ComputeQuota.js +268 -0
- package/lib/AWS/SageMaker/ComputeQuota.js.map +1 -0
- package/lib/AWS/SageMaker/index.d.ts +3 -0
- package/lib/AWS/SageMaker/index.d.ts.map +1 -1
- package/lib/AWS/SageMaker/index.js +3 -0
- package/lib/AWS/SageMaker/index.js.map +1 -1
- package/lib/Bundle/PurePlugin.d.ts +29 -7
- package/lib/Bundle/PurePlugin.d.ts.map +1 -1
- package/lib/Bundle/PurePlugin.js +56 -33
- package/lib/Bundle/PurePlugin.js.map +1 -1
- package/lib/Cli/commands/nuke.d.ts.map +1 -1
- package/lib/Cli/commands/nuke.js +231 -37
- package/lib/Cli/commands/nuke.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +1 -1
- package/lib/Cloudflare/Workers/Assets.d.ts +26 -8
- package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Assets.js +46 -1
- package/lib/Cloudflare/Workers/Assets.js.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +2 -2
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +39 -6
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +7 -5
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +0 -1
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts +2 -2
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts +20 -1
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js +11 -0
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.js +10 -4
- package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts +2 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts.map +1 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.js +5 -5
- package/lib/Cloudflare/Workflows/WorkflowBridge.js.map +1 -1
- package/lib/Cloudflare/Workflows/index.d.ts +1 -1
- package/lib/Cloudflare/Workflows/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workflows/index.js +1 -1
- package/lib/Cloudflare/Workflows/index.js.map +1 -1
- package/lib/Docker/Container.d.ts +26 -0
- package/lib/Docker/Container.d.ts.map +1 -1
- package/lib/Docker/Container.js +20 -1
- package/lib/Docker/Container.js.map +1 -1
- package/lib/Docker/Docker.d.ts +2 -0
- package/lib/Docker/Docker.d.ts.map +1 -1
- package/lib/Docker/Docker.js.map +1 -1
- package/lib/Output.d.ts +4 -1
- package/lib/Output.d.ts.map +1 -1
- package/lib/Output.js.map +1 -1
- package/lib/Provider.d.ts +20 -0
- package/lib/Provider.d.ts.map +1 -1
- package/lib/Provider.js.map +1 -1
- package/lib/Random.d.ts +1 -1
- package/lib/Random.d.ts.map +1 -1
- package/lib/Resource.d.ts +14 -1
- package/lib/Resource.d.ts.map +1 -1
- package/lib/Resource.js.map +1 -1
- package/lib/SQL/D1.d.ts +1 -1
- package/lib/SQL/D1.js +1 -1
- package/lib/SQL/Postgres.d.ts +1 -1
- package/lib/SQL/Postgres.js +1 -1
- package/lib/State/HttpStateApi.d.ts +11 -11
- package/lib/Test/Alchemy.d.ts +1 -1
- package/lib/Test/Alchemy.js +1 -1
- package/lib/Test/Vitest.d.ts +58 -0
- package/lib/Test/Vitest.d.ts.map +1 -0
- package/lib/Test/Vitest.js +114 -0
- package/lib/Test/Vitest.js.map +1 -0
- package/lib/Util/proxy-chain.d.ts +5 -0
- package/lib/Util/proxy-chain.d.ts.map +1 -1
- package/lib/Util/proxy-chain.js +52 -4
- package/lib/Util/proxy-chain.js.map +1 -1
- package/package.json +23 -15
- package/src/AWS/EC2/InternetGateway.ts +8 -3
- package/src/AWS/EC2/LingeringEnis.ts +169 -0
- package/src/AWS/EC2/SecurityGroup.ts +24 -28
- package/src/AWS/EC2/Subnet.ts +23 -34
- package/src/AWS/EKS/Addon.ts +17 -4
- package/src/AWS/EKS/Deployment.ts +26 -5
- package/src/AWS/EKS/HyperPod.ts +97 -0
- package/src/AWS/EKS/Job.ts +32 -7
- package/src/AWS/EKS/internal/client.ts +40 -3
- package/src/AWS/Lambda/Function.ts +5 -3
- package/src/AWS/Providers.ts +6 -0
- package/src/AWS/SageMaker/Cluster.ts +775 -0
- package/src/AWS/SageMaker/ClusterSchedulerConfig.ts +430 -0
- package/src/AWS/SageMaker/ComputeQuota.ts +426 -0
- package/src/AWS/SageMaker/index.ts +3 -0
- package/src/Bundle/PurePlugin.ts +92 -39
- package/src/Cli/commands/nuke.ts +295 -56
- package/src/Cloudflare/Workers/Assets.ts +53 -1
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +46 -7
- package/src/Cloudflare/Workers/Worker.ts +7 -5
- package/src/Cloudflare/Workers/WorkerBundle.ts +31 -1
- package/src/Cloudflare/Workers/WorkerProvider.ts +18 -4
- package/src/Cloudflare/Workflows/WorkflowBridge.ts +5 -5
- package/src/Cloudflare/Workflows/index.ts +1 -1
- package/src/Docker/Container.ts +30 -1
- package/src/Docker/Docker.ts +2 -0
- package/src/Output.ts +19 -11
- package/src/Provider.ts +20 -0
- package/src/Resource.ts +18 -1
- package/src/SQL/D1.ts +1 -1
- package/src/SQL/Postgres.ts +1 -1
- package/src/Test/Alchemy.ts +1 -1
- package/src/Test/Vitest.ts +244 -0
- package/src/Util/proxy-chain.ts +73 -4
- package/lib/SQL/index.d.ts +0 -4
- package/lib/SQL/index.d.ts.map +0 -1
- package/lib/SQL/index.js +0 -4
- package/lib/SQL/index.js.map +0 -1
- package/src/SQL/index.ts +0 -3
|
@@ -20,6 +20,25 @@ import {
|
|
|
20
20
|
type DurableObjectExport,
|
|
21
21
|
} from "./DurableObject.ts";
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Bundler options for a Worker: the generic {@link Bundle.BundleExtraOptions}
|
|
25
|
+
* plus rolldown output overrides merged over Alchemy's defaults.
|
|
26
|
+
*/
|
|
27
|
+
export interface WorkerBuildOptions extends Bundle.BundleExtraOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Rolldown output options merged over Alchemy's defaults. Use this to
|
|
30
|
+
* control chunking (`codeSplitting`), minification, etc.
|
|
31
|
+
*/
|
|
32
|
+
output?: rolldown.OutputOptions;
|
|
33
|
+
/**
|
|
34
|
+
* Forwarded to rolldown's `preserveEntrySignatures` input option. Some
|
|
35
|
+
* `output.codeSplitting` configurations require relaxing it (e.g.
|
|
36
|
+
* `includeDependenciesRecursively: false` needs `"allow-extension"`).
|
|
37
|
+
* Workers must keep their entry exports, so never pass `false`.
|
|
38
|
+
*/
|
|
39
|
+
preserveEntrySignatures?: rolldown.InputOptions["preserveEntrySignatures"];
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
export interface WorkerBundleOptions {
|
|
24
43
|
id: string;
|
|
25
44
|
main: string;
|
|
@@ -36,7 +55,7 @@ export interface WorkerBundleOptions {
|
|
|
36
55
|
exports: Record<string, DurableObjectExport | WorkflowExport>;
|
|
37
56
|
};
|
|
38
57
|
stack: { name: string; stage: string };
|
|
39
|
-
extraOptions:
|
|
58
|
+
extraOptions: WorkerBuildOptions | undefined;
|
|
40
59
|
}
|
|
41
60
|
|
|
42
61
|
export const WorkerBundle = Effect.gen(function* () {
|
|
@@ -48,6 +67,7 @@ export const WorkerBundle = Effect.gen(function* () {
|
|
|
48
67
|
const realMain = yield* sanitizeMain(options.main);
|
|
49
68
|
const inputOptions: rolldown.InputOptions = {
|
|
50
69
|
input: realMain,
|
|
70
|
+
preserveEntrySignatures: options.extraOptions?.preserveEntrySignatures,
|
|
51
71
|
// Forever-devtool native modules that vite/chokidar reference behind
|
|
52
72
|
// runtime guards. Rolldown resolves before tree-shaking, so the dead
|
|
53
73
|
// `require('../pkg')` (lightningcss < 1.32) and `require('fsevents')`
|
|
@@ -89,7 +109,17 @@ export const WorkerBundle = Effect.gen(function* () {
|
|
|
89
109
|
sourcemap: "hidden",
|
|
90
110
|
minify: true,
|
|
91
111
|
keepNames: true,
|
|
112
|
+
// Rolldown's default chunking can split top-level initializer modules
|
|
113
|
+
// (e.g. Drizzle `pgTable` schemas) away from the classes they read,
|
|
114
|
+
// and workerd then evaluates a reader before its imported binding is
|
|
115
|
+
// initialized — the script fails Cloudflare startup validation with
|
|
116
|
+
// `ScriptStartupError: Cannot access '<minified>' before
|
|
117
|
+
// initialization` (#749). `strictExecutionOrder` wraps cross-chunk
|
|
118
|
+
// modules so evaluation follows ESM semantics regardless of how the
|
|
119
|
+
// graph was chunked. See DrizzleSchemaChunks.test.ts.
|
|
120
|
+
strictExecutionOrder: true,
|
|
92
121
|
dir: `.alchemy/bundles/${options.id}`,
|
|
122
|
+
...options.extraOptions?.output,
|
|
93
123
|
};
|
|
94
124
|
return { inputOptions, outputOptions, extraOptions: options.extraOptions };
|
|
95
125
|
});
|
|
@@ -23,7 +23,12 @@ import { sha256Object } from "../../Util/sha256.ts";
|
|
|
23
23
|
import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
|
|
24
24
|
import { CloudflareLogs } from "../Logs.ts";
|
|
25
25
|
import { listAllZones, resolveZoneId } from "../Zone/lookup.ts";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
mergeAssetsConfigFiles,
|
|
28
|
+
readAssets,
|
|
29
|
+
readAssetsConfigFiles,
|
|
30
|
+
uploadAssets,
|
|
31
|
+
} from "./Assets.ts";
|
|
27
32
|
import { getCompatibility } from "./Compatibility.ts";
|
|
28
33
|
import { isDurableObjectExport } from "./DurableObject.ts";
|
|
29
34
|
import { LocalWorkerProvider } from "./LocalWorkerProvider.ts";
|
|
@@ -1360,8 +1365,8 @@ export const LiveWorkerProvider = () =>
|
|
|
1360
1365
|
output: Worker["Attributes"] | undefined,
|
|
1361
1366
|
) => {
|
|
1362
1367
|
if (!Predicate.hasProperty(assets, "hash")) return undefined;
|
|
1363
|
-
const { directory
|
|
1364
|
-
return { config, hash, skip: hash === output?.hash?.assets };
|
|
1368
|
+
const { directory, hash, ...config } = assets;
|
|
1369
|
+
return { directory, config, hash, skip: hash === output?.hash?.assets };
|
|
1365
1370
|
};
|
|
1366
1371
|
|
|
1367
1372
|
const putWorker = Effect.fn(function* (
|
|
@@ -1445,7 +1450,16 @@ export const LiveWorkerProvider = () =>
|
|
|
1445
1450
|
`Cloudflare Worker update: assets unchanged for ${name}, keeping existing`,
|
|
1446
1451
|
);
|
|
1447
1452
|
keepAssets = true;
|
|
1448
|
-
|
|
1453
|
+
// `keepAssets` only preserves the uploaded files — the PUT
|
|
1454
|
+
// replaces the asset config wholesale. The skip path never
|
|
1455
|
+
// walked the directory, so read just `_headers`/`_redirects`
|
|
1456
|
+
// here or a no-op deploy would wipe their rules.
|
|
1457
|
+
metadataAssets = {
|
|
1458
|
+
config: mergeAssetsConfigFiles(
|
|
1459
|
+
prebuiltAssets.config,
|
|
1460
|
+
yield* readAssetsConfigFiles(prebuiltAssets.directory),
|
|
1461
|
+
),
|
|
1462
|
+
};
|
|
1449
1463
|
metadataBindings.push({
|
|
1450
1464
|
type: "assets",
|
|
1451
1465
|
name: "ASSETS",
|
|
@@ -116,7 +116,7 @@ const wrapWorkflowEvent = (event: any): WorkflowEventService["Service"] => ({
|
|
|
116
116
|
schedule: event.schedule,
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
const wrapWorkflowStep = (step: any): WorkflowStep["Service"] => ({
|
|
119
|
+
export const wrapWorkflowStep = (step: any): WorkflowStep["Service"] => ({
|
|
120
120
|
do: <T>(options: WorkflowTaskOptions<T, any, any>): Effect.Effect<T> => {
|
|
121
121
|
const { name } = options;
|
|
122
122
|
// The surrounding body context is already provided in `task`; the bridge
|
|
@@ -151,7 +151,7 @@ const wrapWorkflowStep = (step: any): WorkflowStep["Service"] => ({
|
|
|
151
151
|
rollbackConfig: options.rollbackConfig,
|
|
152
152
|
}
|
|
153
153
|
: undefined;
|
|
154
|
-
return Effect.
|
|
154
|
+
return Effect.promise(() => {
|
|
155
155
|
if (config && rollback) return step.do(name, config, callback, rollback);
|
|
156
156
|
if (config) return step.do(name, config, callback);
|
|
157
157
|
if (rollback) return step.do(name, callback, rollback);
|
|
@@ -159,14 +159,14 @@ const wrapWorkflowStep = (step: any): WorkflowStep["Service"] => ({
|
|
|
159
159
|
});
|
|
160
160
|
},
|
|
161
161
|
sleep: (name: string, duration: string | number): Effect.Effect<void> =>
|
|
162
|
-
Effect.
|
|
162
|
+
Effect.promise(() => step.sleep(name, duration)),
|
|
163
163
|
sleepUntil: (name: string, timestamp: Date | number): Effect.Effect<void> =>
|
|
164
|
-
Effect.
|
|
164
|
+
Effect.promise(() => step.sleepUntil(name, timestamp)),
|
|
165
165
|
waitForEvent: <T>(
|
|
166
166
|
name: string,
|
|
167
167
|
options: any,
|
|
168
168
|
): Effect.Effect<WorkflowStepEvent<T>> =>
|
|
169
|
-
Effect.
|
|
169
|
+
Effect.promise(
|
|
170
170
|
() => step.waitForEvent(name, options) as Promise<WorkflowStepEvent<T>>,
|
|
171
171
|
),
|
|
172
172
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from "./Workflow.ts";
|
|
2
|
-
export
|
|
2
|
+
export { makeWorkflowBridge } from "./WorkflowBridge.ts";
|
package/src/Docker/Container.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { isResolved } from "../Diff.ts";
|
|
|
8
8
|
import * as Provider from "../Provider.ts";
|
|
9
9
|
import { Resource } from "../Resource.ts";
|
|
10
10
|
import { createInternalTags, hasAlchemyTags } from "../Tags.ts";
|
|
11
|
+
import { toSeconds } from "../Util/Duration.ts";
|
|
11
12
|
import { Docker, dockerPhysicalName } from "./Docker.ts";
|
|
12
13
|
import type { Providers } from "./Providers.ts";
|
|
13
14
|
|
|
@@ -30,6 +31,16 @@ export interface ContainerProps {
|
|
|
30
31
|
volumes?: Container.VolumeMapping[];
|
|
31
32
|
/** Restart policy. */
|
|
32
33
|
restart?: "no" | "always" | "on-failure" | "unless-stopped";
|
|
34
|
+
/**
|
|
35
|
+
* Container labels. Alchemy's internal ownership labels are added
|
|
36
|
+
* automatically.
|
|
37
|
+
*/
|
|
38
|
+
labels?: Record<string, string>;
|
|
39
|
+
/**
|
|
40
|
+
* Grace period before Docker forcefully kills the container after stopping
|
|
41
|
+
* it.
|
|
42
|
+
*/
|
|
43
|
+
stopTimeout?: Duration.Input;
|
|
33
44
|
/** Networks to connect after create. */
|
|
34
45
|
networks?: Container.NetworkMapping[];
|
|
35
46
|
/** Remove the container when it exits. @default false */
|
|
@@ -161,6 +172,22 @@ export interface Container extends Resource<
|
|
|
161
172
|
* });
|
|
162
173
|
* const runtime = yield* Docker.inspectContainer(postgresName);
|
|
163
174
|
* ```
|
|
175
|
+
*
|
|
176
|
+
* @section Traefik
|
|
177
|
+
* @example Route a container through Traefik
|
|
178
|
+
* ```typescript
|
|
179
|
+
* const api = yield* Docker.Container("api", {
|
|
180
|
+
* image: "ghcr.io/acme/api:latest",
|
|
181
|
+
* networks: [{ name: "traefik" }],
|
|
182
|
+
* labels: {
|
|
183
|
+
* "traefik.enable": "true",
|
|
184
|
+
* "traefik.http.routers.api.rule": "Host(`api.example.com`)",
|
|
185
|
+
* "traefik.http.services.api.loadbalancer.server.port": "3000",
|
|
186
|
+
* },
|
|
187
|
+
* stopTimeout: "30 seconds",
|
|
188
|
+
* start: true,
|
|
189
|
+
* });
|
|
190
|
+
* ```
|
|
164
191
|
*/
|
|
165
192
|
export const Container = Resource<Container>("Docker.Container");
|
|
166
193
|
|
|
@@ -308,7 +335,7 @@ export const ContainerProvider = () =>
|
|
|
308
335
|
const internalTags = yield* createInternalTags(id);
|
|
309
336
|
const { stdout: containerId } = yield* docker.container.create({
|
|
310
337
|
...args,
|
|
311
|
-
label: internalTags,
|
|
338
|
+
label: { ...args.label, ...internalTags },
|
|
312
339
|
});
|
|
313
340
|
yield* Effect.forEach(
|
|
314
341
|
news.networks ?? [],
|
|
@@ -355,6 +382,8 @@ const makeCreateArgs = (id: string, news: ContainerProps, instanceId: string) =>
|
|
|
355
382
|
`${port.external}:${port.internal}/${port.protocol ?? "tcp"}`,
|
|
356
383
|
),
|
|
357
384
|
restart: news.restart ?? "no",
|
|
385
|
+
label: news.labels,
|
|
386
|
+
"stop-timeout": toSeconds(news.stopTimeout)?.toString(),
|
|
358
387
|
rm: news.removeOnExit ?? false,
|
|
359
388
|
...(news.healthcheck
|
|
360
389
|
? {
|
package/src/Docker/Docker.ts
CHANGED
|
@@ -49,6 +49,7 @@ export class Docker extends Context.Service<
|
|
|
49
49
|
"health-retries": number | undefined;
|
|
50
50
|
"health-start-period": string | undefined;
|
|
51
51
|
"health-start-interval": string | undefined;
|
|
52
|
+
"stop-timeout": string | undefined;
|
|
52
53
|
p: Array<string> | undefined;
|
|
53
54
|
command: Array<string> | undefined;
|
|
54
55
|
label?: Record<string, string>;
|
|
@@ -193,6 +194,7 @@ export declare namespace Docker {
|
|
|
193
194
|
Cmd: string[] | null;
|
|
194
195
|
Env: string[] | null;
|
|
195
196
|
Labels: Record<string, string> | null;
|
|
197
|
+
StopTimeout?: number;
|
|
196
198
|
Healthcheck?: {
|
|
197
199
|
Test: string[] | null;
|
|
198
200
|
Interval?: number;
|
package/src/Output.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { RuntimeContext, sanitizeKey } from "./RuntimeContext.ts";
|
|
|
12
12
|
import { Stack } from "./Stack.ts";
|
|
13
13
|
import { Stage } from "./Stage.ts";
|
|
14
14
|
import * as State from "./State/State.ts";
|
|
15
|
-
import { isPrimitive } from "./Util/data.ts";
|
|
15
|
+
import { isPrimitive, type Primitive } from "./Util/data.ts";
|
|
16
16
|
|
|
17
17
|
const inspect = Symbol.for("nodejs.util.inspect.custom");
|
|
18
18
|
|
|
@@ -73,16 +73,24 @@ export interface Output<A = any, Req = any> extends Pipeable {
|
|
|
73
73
|
|
|
74
74
|
export interface Accessor<A> extends Effect.Effect<A> {}
|
|
75
75
|
|
|
76
|
-
export type ToOutput<A, Req = never> =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
export type ToOutput<A, Req = never> =
|
|
77
|
+
// Branded primitives (`string & Brand<"...">`) are assignable to `object`
|
|
78
|
+
// via the brand intersection, so they must short-circuit to a plain Output
|
|
79
|
+
// before the object check — otherwise they explode into an ObjectExpr
|
|
80
|
+
// mapped over every String/Number method. Date is opaque for the same
|
|
81
|
+
// reason (mirrors AttrOutput in Resource.ts).
|
|
82
|
+
[A] extends [Primitive | Date]
|
|
83
|
+
? Output<A, Req>
|
|
84
|
+
: [Extract<A, object>] extends [never]
|
|
85
|
+
? Output<A, Req>
|
|
86
|
+
: [Extract<A, any[]>] extends [never]
|
|
87
|
+
? ObjectExpr<
|
|
88
|
+
{
|
|
89
|
+
[attr in keyof A]: A[attr];
|
|
90
|
+
},
|
|
91
|
+
Req
|
|
92
|
+
>
|
|
93
|
+
: ArrayExpr<Extract<A, any[]>, Req>;
|
|
86
94
|
|
|
87
95
|
export const ExprSymbol = Symbol.for("alchemy/Expr");
|
|
88
96
|
|
package/src/Provider.ts
CHANGED
|
@@ -134,6 +134,26 @@ export interface ProviderService<
|
|
|
134
134
|
* {@link singleton}, these are ordinary multi-instance resources.
|
|
135
135
|
*/
|
|
136
136
|
skip?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Provider IDs (picomatch globs, e.g. `"AWS.IAM.Role"` or `"AWS.EC2.*"`)
|
|
139
|
+
* whose resources this type's **cloud-side teardown** consumes — they
|
|
140
|
+
* must still exist while this type is deleting. `alchemy unsafe nuke`
|
|
141
|
+
* topologically orders deletion so every resource of this type is fully
|
|
142
|
+
* gone before any matching type starts deleting; if this type's deletes
|
|
143
|
+
* fail, the matching types are held back rather than deleted out from
|
|
144
|
+
* under an in-flight teardown.
|
|
145
|
+
*
|
|
146
|
+
* Example: SageMaker HyperPod deletes node ENIs by assuming the
|
|
147
|
+
* instance group's execution role — deleting the role (or the VPC)
|
|
148
|
+
* mid-teardown wedges the cluster in `Deleting` permanently, so
|
|
149
|
+
* `AWS.SageMaker.Cluster` declares `dependsOn: ["AWS.IAM.Role", ...]`.
|
|
150
|
+
*
|
|
151
|
+
* The constraint only takes effect when resources of this type are
|
|
152
|
+
* actually present in the nuke target set; declaration cycles collapse
|
|
153
|
+
* into a single concurrent wave (with a logged warning) instead of
|
|
154
|
+
* failing.
|
|
155
|
+
*/
|
|
156
|
+
dependsOn?: readonly string[];
|
|
137
157
|
};
|
|
138
158
|
/**
|
|
139
159
|
* Enumerates every existing resource of this type in the ambient scope
|
package/src/Resource.ts
CHANGED
|
@@ -190,9 +190,26 @@ export type Resource<
|
|
|
190
190
|
...args: any[]
|
|
191
191
|
): (binding: Input<Binding>) => Effect.Effect<void>;
|
|
192
192
|
} & {
|
|
193
|
-
[attr in keyof Attributes]-?:
|
|
193
|
+
[attr in keyof Attributes]-?: AttrOutput<Attributes[attr]>;
|
|
194
194
|
};
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Accessor type for one attribute. Pure object attributes upgrade to
|
|
198
|
+
* {@link Output.ObjectExpr} so nested access is typed —
|
|
199
|
+
* `hyperpod.instanceGroups.workers` — while primitives, unions with
|
|
200
|
+
* `undefined`, branded string unions (`"a" | (string & {})`), and arrays
|
|
201
|
+
* stay plain {@link Output.Output} (running them through `ToOutput` would
|
|
202
|
+
* classify the `string & {}` branch as an object and explode into
|
|
203
|
+
* String-method mapped types).
|
|
204
|
+
*/
|
|
205
|
+
type AttrOutput<A> = [A] extends [
|
|
206
|
+
string | number | boolean | bigint | null | undefined | Date | any[],
|
|
207
|
+
]
|
|
208
|
+
? Output.Output<A, never>
|
|
209
|
+
: [A] extends [Record<string, any>]
|
|
210
|
+
? Output.ObjectExpr<A, never>
|
|
211
|
+
: Output.Output<A, never>;
|
|
212
|
+
|
|
196
213
|
export interface ResourceOptions {
|
|
197
214
|
/**
|
|
198
215
|
* Default removal policy for this resource type when the caller has not
|
package/src/SQL/D1.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type D1Config = Omit<D1Client.D1ClientConfig, "db">;
|
|
|
31
31
|
* resolved once at Worker init and used from any handler:
|
|
32
32
|
*
|
|
33
33
|
* ```typescript
|
|
34
|
-
* import * as SQL from "alchemy/SQL";
|
|
34
|
+
* import * as SQL from "alchemy/SQL/D1";
|
|
35
35
|
*
|
|
36
36
|
* const d1 = yield* Cloudflare.D1.QueryDatabase(Db);
|
|
37
37
|
* const sql = yield* SQL.D1(d1);
|
package/src/SQL/Postgres.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type PostgresConfig<E, R> = Omit<PgClient.PgPoolConfig, "url"> & {
|
|
|
28
28
|
* handler:
|
|
29
29
|
*
|
|
30
30
|
* ```typescript
|
|
31
|
-
* import * as SQL from "alchemy/SQL";
|
|
31
|
+
* import * as SQL from "alchemy/SQL/Postgres";
|
|
32
32
|
*
|
|
33
33
|
* const hd = yield* Cloudflare.Hyperdrive.Connect(Hyperdrive);
|
|
34
34
|
* const sql = yield* SQL.Postgres({ url: hd.connectionString });
|
package/src/Test/Alchemy.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Test adapter for the `alchemy-test` runner (see `packages/alchemy-test`).
|
|
5
5
|
*
|
|
6
|
-
* Same shape as {@link "./Bun.ts"}, but registers
|
|
6
|
+
* Same shape as {@link "./Vitest.ts"} / {@link "./Bun.ts"}, but registers
|
|
7
7
|
* tests as raw Effects with the alchemy-test harness so the single-process
|
|
8
8
|
* runner can inject a buffering Logger/Console per test and manage
|
|
9
9
|
* concurrency + timeouts itself.
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/** @effect-diagnostics anyUnknownInErrorContext:off */
|
|
2
|
+
import { it } from "@effect/vitest";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Exit from "effect/Exit";
|
|
5
|
+
import * as Scope from "effect/Scope";
|
|
6
|
+
import {
|
|
7
|
+
afterAll as vitestAfterAll,
|
|
8
|
+
afterEach as vitestAfterEach,
|
|
9
|
+
beforeAll as vitestBeforeAll,
|
|
10
|
+
beforeEach as vitestBeforeEach,
|
|
11
|
+
} from "vitest";
|
|
12
|
+
|
|
13
|
+
import type { AlchemyContext } from "../AlchemyContext.ts";
|
|
14
|
+
import type { CompiledStack } from "../Stack.ts";
|
|
15
|
+
import type { Stage } from "../Stage.ts";
|
|
16
|
+
import * as Core from "./Core.ts";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
executeWhenReady,
|
|
20
|
+
getWhenReady,
|
|
21
|
+
guardContentType,
|
|
22
|
+
guardedFetchLayer,
|
|
23
|
+
rpcClientLayer,
|
|
24
|
+
WorkerNotReady,
|
|
25
|
+
type EdgeGuardOptions,
|
|
26
|
+
type WhenReadyOptions,
|
|
27
|
+
} from "./Http.ts";
|
|
28
|
+
|
|
29
|
+
export type MakeOptions<ROut = any> = Core.MakeOptions<ROut>;
|
|
30
|
+
export type ScratchStack = Core.ScratchStack;
|
|
31
|
+
export type TestEffect<A, R = never> = Core.TestEffect<A, R>;
|
|
32
|
+
|
|
33
|
+
type TestOptions = number | { timeout?: number };
|
|
34
|
+
|
|
35
|
+
const timeoutOf = (opts: TestOptions | undefined): number | undefined =>
|
|
36
|
+
typeof opts === "number" ? opts : opts?.timeout;
|
|
37
|
+
|
|
38
|
+
interface TestFn {
|
|
39
|
+
(name: string, eff: TestEffect<void>, options?: TestOptions): void;
|
|
40
|
+
skip: (name: string, eff: TestEffect<void>, options?: TestOptions) => void;
|
|
41
|
+
skipIf: (
|
|
42
|
+
condition: boolean,
|
|
43
|
+
) => (name: string, eff: TestEffect<void>, options?: TestOptions) => void;
|
|
44
|
+
only: (name: string, eff: TestEffect<void>, options?: TestOptions) => void;
|
|
45
|
+
todo: (name: string, eff: TestEffect<void>, options?: TestOptions) => void;
|
|
46
|
+
provider: ProviderFn;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface ProviderFn {
|
|
50
|
+
(
|
|
51
|
+
name: string,
|
|
52
|
+
fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
|
|
53
|
+
options?: TestOptions,
|
|
54
|
+
): void;
|
|
55
|
+
skip: (
|
|
56
|
+
name: string,
|
|
57
|
+
fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
|
|
58
|
+
options?: TestOptions,
|
|
59
|
+
) => void;
|
|
60
|
+
skipIf: (
|
|
61
|
+
condition: boolean,
|
|
62
|
+
) => (
|
|
63
|
+
name: string,
|
|
64
|
+
fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
|
|
65
|
+
options?: TestOptions,
|
|
66
|
+
) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface BeforeAllFn {
|
|
70
|
+
<A>(eff: TestEffect<A>, options?: TestOptions): Effect.Effect<A>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface BeforeEachFn {
|
|
74
|
+
(eff: TestEffect<void>, options?: TestOptions): void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface AfterAllFn {
|
|
78
|
+
(eff: TestEffect<any>, options?: TestOptions): void;
|
|
79
|
+
skipIf: (
|
|
80
|
+
predicate: boolean,
|
|
81
|
+
) => (eff: TestEffect<any>, options?: TestOptions) => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface AfterEachFn {
|
|
85
|
+
(eff: TestEffect<void>, options?: TestOptions): void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface TestApi {
|
|
89
|
+
test: TestFn;
|
|
90
|
+
beforeAll: BeforeAllFn;
|
|
91
|
+
beforeEach: BeforeEachFn;
|
|
92
|
+
afterAll: AfterAllFn;
|
|
93
|
+
afterEach: AfterEachFn;
|
|
94
|
+
deploy: <A>(
|
|
95
|
+
stack: TestEffect<CompiledStack<A>, Stage | AlchemyContext>,
|
|
96
|
+
options?: { stage?: string },
|
|
97
|
+
) => ReturnType<typeof Core.deploy<A>>;
|
|
98
|
+
destroy: (
|
|
99
|
+
stack: TestEffect<CompiledStack, Stage | AlchemyContext>,
|
|
100
|
+
options?: { stage?: string },
|
|
101
|
+
) => ReturnType<typeof Core.destroy>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const DEFAULT_TIMEOUT = 120_000;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build the per-file test API. See {@link "./Bun.ts"} for the same shape
|
|
108
|
+
* over `bun:test`. Vitest variant uses `@effect/vitest`'s `it.live` so
|
|
109
|
+
* Effect-aware tests stay first-class.
|
|
110
|
+
*/
|
|
111
|
+
export const make = <ROut = any>(options: MakeOptions<ROut>): TestApi => {
|
|
112
|
+
// See `Test/Bun.ts` for the rationale: the dev sidecar must outlive a
|
|
113
|
+
// single `runPromise` boundary, so all hooks share one scope that's only
|
|
114
|
+
// closed after `destroy(...)` runs.
|
|
115
|
+
const sharedScope = Scope.makeUnsafe("sequential");
|
|
116
|
+
const wrap = <A>(eff: TestEffect<A>) =>
|
|
117
|
+
Core.toEffect(eff, options, sharedScope);
|
|
118
|
+
const runEff = <A>(eff: TestEffect<A>) => Core.run(eff, options, sharedScope);
|
|
119
|
+
|
|
120
|
+
const test = ((name, eff, opts) => {
|
|
121
|
+
it.live(name, () => wrap(eff), timeoutOf(opts));
|
|
122
|
+
}) as TestFn;
|
|
123
|
+
|
|
124
|
+
test.skip = (name, _eff, opts) => {
|
|
125
|
+
it.skip(name, () => {}, timeoutOf(opts));
|
|
126
|
+
};
|
|
127
|
+
test.skipIf = (condition) => (name, eff, opts) => {
|
|
128
|
+
if (condition) {
|
|
129
|
+
it.skip(name, () => {}, timeoutOf(opts));
|
|
130
|
+
} else {
|
|
131
|
+
it.live(name, () => wrap(eff), timeoutOf(opts));
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
test.only = (name, eff, opts) => {
|
|
135
|
+
it.only(name, () => wrap(eff) as Effect.Effect<any>, timeoutOf(opts));
|
|
136
|
+
};
|
|
137
|
+
test.todo = (name, _eff, _opts) => {
|
|
138
|
+
it.todo(name);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const wrapProvider = (
|
|
142
|
+
name: string,
|
|
143
|
+
fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
|
|
144
|
+
) => {
|
|
145
|
+
const scratch = Core.scratchStack(options, name);
|
|
146
|
+
// Guarantee teardown. `test.provider` has no built-in cleanup, so a body
|
|
147
|
+
// that fails (assertion, API error like a 409/Unauthorized) or is
|
|
148
|
+
// interrupted (vitest timeout) BEFORE its trailing `stack.destroy()` would
|
|
149
|
+
// otherwise leak every cloud resource it deployed: the scratch's in-memory
|
|
150
|
+
// state is discarded with the process, so no later run can reclaim the
|
|
151
|
+
// orphan (only an account-wide `nuke` can). `scratch.destroy()` is
|
|
152
|
+
// idempotent (empty-plan apply against the shared scratch state) — a no-op
|
|
153
|
+
// when the body already destroyed, and it reclaims the orphans otherwise.
|
|
154
|
+
// `Effect.ensuring` runs the finalizer on success, failure, AND interruption.
|
|
155
|
+
const body = Core.withProviders(fn(scratch), options, scratch.name).pipe(
|
|
156
|
+
Effect.ensuring(scratch.destroy().pipe(Effect.ignore)),
|
|
157
|
+
);
|
|
158
|
+
return Core.toEffect(
|
|
159
|
+
body,
|
|
160
|
+
{ ...options, state: scratch.state },
|
|
161
|
+
sharedScope,
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const provider = ((name, fn, opts) => {
|
|
166
|
+
it.live(name, () => wrapProvider(name, fn), timeoutOf(opts));
|
|
167
|
+
}) as ProviderFn;
|
|
168
|
+
provider.skip = (name, _fn, opts) => {
|
|
169
|
+
it.skip(name, () => {}, timeoutOf(opts));
|
|
170
|
+
};
|
|
171
|
+
provider.skipIf = (condition) => (name, fn, opts) => {
|
|
172
|
+
if (condition) {
|
|
173
|
+
it.skip(name, () => {}, timeoutOf(opts));
|
|
174
|
+
} else {
|
|
175
|
+
it.live(name, () => wrapProvider(name, fn), timeoutOf(opts));
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
test.provider = provider;
|
|
179
|
+
|
|
180
|
+
const beforeAll: BeforeAllFn = <A>(
|
|
181
|
+
eff: TestEffect<A>,
|
|
182
|
+
hookOptions?: TestOptions,
|
|
183
|
+
) => {
|
|
184
|
+
let result: A;
|
|
185
|
+
vitestBeforeAll(
|
|
186
|
+
() => runEff(eff).then((v) => (result = v)),
|
|
187
|
+
timeoutOf(hookOptions) ?? DEFAULT_TIMEOUT,
|
|
188
|
+
);
|
|
189
|
+
return Effect.sync(() => result);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const beforeEach: BeforeEachFn = (eff, hookOptions) => {
|
|
193
|
+
vitestBeforeEach(() => runEff(eff), timeoutOf(hookOptions));
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const afterAll = ((eff, hookOptions) => {
|
|
197
|
+
vitestAfterAll(
|
|
198
|
+
() => runEff(eff),
|
|
199
|
+
timeoutOf(hookOptions) ?? DEFAULT_TIMEOUT,
|
|
200
|
+
);
|
|
201
|
+
}) as AfterAllFn;
|
|
202
|
+
afterAll.skipIf = (predicate) => (eff, hookOptions) => {
|
|
203
|
+
if (predicate) return;
|
|
204
|
+
vitestAfterAll(
|
|
205
|
+
() => runEff(eff),
|
|
206
|
+
timeoutOf(hookOptions) ?? DEFAULT_TIMEOUT,
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const afterEach: AfterEachFn = (eff, hookOptions) => {
|
|
211
|
+
vitestAfterEach(() => runEff(eff), timeoutOf(hookOptions));
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// `destroy(Stack)` needs the dev sidecar alive so it can call `sidecar.stop`.
|
|
215
|
+
// `Scope.close` on an already-closed scope is a no-op, so the destroy
|
|
216
|
+
// wrapper AND the fallback cleanup hook below can both call it safely.
|
|
217
|
+
const closeScope = Effect.suspend(() =>
|
|
218
|
+
Scope.close(sharedScope, Exit.void),
|
|
219
|
+
).pipe(Effect.ignore);
|
|
220
|
+
|
|
221
|
+
// Fallback cleanup: if the user never calls `destroy(Stack)` (e.g.
|
|
222
|
+
// `NO_DESTROY=1`), nothing else closes the shared scope and the sidecar
|
|
223
|
+
// child process leaks past the test process. Register an `afterAll` that
|
|
224
|
+
// closes it. We defer registration to a microtask so it runs AFTER any
|
|
225
|
+
// user-registered `afterAll` (including `destroy(Stack)`); vitest runs
|
|
226
|
+
// afterAll hooks in registration order.
|
|
227
|
+
queueMicrotask(() => {
|
|
228
|
+
vitestAfterAll(() => Effect.runPromise(closeScope), DEFAULT_TIMEOUT);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
test,
|
|
233
|
+
beforeAll,
|
|
234
|
+
beforeEach,
|
|
235
|
+
afterAll,
|
|
236
|
+
afterEach,
|
|
237
|
+
deploy: (stack, callOpts) =>
|
|
238
|
+
Core.deploy(options, stack, { ...callOpts, scope: sharedScope }),
|
|
239
|
+
destroy: (stack, callOpts) =>
|
|
240
|
+
Core.destroy(options, stack, { ...callOpts, scope: sharedScope }).pipe(
|
|
241
|
+
Effect.ensuring(closeScope),
|
|
242
|
+
),
|
|
243
|
+
};
|
|
244
|
+
};
|