alchemy-effect 0.6.4 → 0.7.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-effect.js +90 -20
- package/bin/alchemy-effect.js.map +1 -1
- package/bin/alchemy-effect.ts +27 -24
- package/lib/cli/index.d.ts +1 -0
- package/lib/cli/index.d.ts.map +1 -1
- package/package.json +13 -3
- package/src/AWS/Website/StaticSite.ts +5 -7
- package/src/AWS/Website/shared.ts +15 -3
- package/src/Apply.ts +143 -107
- package/src/Artifacts.ts +147 -0
- package/src/Build/Command.ts +21 -99
- package/src/Build/Memo.ts +187 -0
- package/src/Bundle/Bundle.ts +23 -17
- package/src/Cloudflare/Website/Vite.ts +65 -0
- package/src/Cloudflare/Website/index.ts +1 -1
- package/src/Cloudflare/Workers/Assets.ts +7 -2
- package/src/Cloudflare/Workers/Worker.ts +189 -59
- package/src/Destroy.ts +3 -2
- package/src/Output.ts +4 -0
- package/src/Plan.ts +606 -585
- package/src/Stack.ts +18 -6
- package/src/Test/Vitest.ts +20 -11
- package/src/Util/gitignore-rules-to-globs.ts +80 -0
- package/src/Cloudflare/Website/TanstackStart.ts +0 -14
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type * as cf from "@cloudflare/workers-types";
|
|
2
|
-
import
|
|
2
|
+
import cloudflareRolldown from "@distilled.cloud/cloudflare-rolldown-plugin";
|
|
3
|
+
import cloudflareVite from "@distilled.cloud/cloudflare-vite-plugin";
|
|
3
4
|
import * as workers from "@distilled.cloud/cloudflare/workers";
|
|
4
5
|
import type * as Cause from "effect/Cause";
|
|
5
6
|
import * as Effect from "effect/Effect";
|
|
@@ -8,12 +9,16 @@ import * as Layer from "effect/Layer";
|
|
|
8
9
|
import * as Option from "effect/Option";
|
|
9
10
|
import * as Path from "effect/Path";
|
|
10
11
|
import * as Queue from "effect/Queue";
|
|
12
|
+
import * as Redacted from "effect/Redacted";
|
|
11
13
|
import * as Schedule from "effect/Schedule";
|
|
12
14
|
import * as ServiceMap from "effect/ServiceMap";
|
|
13
15
|
import * as Stream from "effect/Stream";
|
|
14
16
|
import * as Socket from "effect/unstable/socket/Socket";
|
|
15
17
|
import type * as rolldown from "rolldown";
|
|
18
|
+
import type * as vite from "vite";
|
|
19
|
+
import * as Artifacts from "../../Artifacts.ts";
|
|
16
20
|
import * as Binding from "../../Binding.ts";
|
|
21
|
+
import { hashDirectory, type MemoOptions } from "../../Build/Memo.ts";
|
|
17
22
|
import * as Bundle from "../../Bundle/Bundle.ts";
|
|
18
23
|
import { findCwdForBundle } from "../../Bundle/TempRoot.ts";
|
|
19
24
|
import type { ScopedPlanStatusSession } from "../../Cli/Cli.ts";
|
|
@@ -33,7 +38,6 @@ import { Resource, type ResourceBinding } from "../../Resource.ts";
|
|
|
33
38
|
import { Self } from "../../Self.ts";
|
|
34
39
|
import * as Serverless from "../../Serverless/index.ts";
|
|
35
40
|
import { Stack } from "../../Stack.ts";
|
|
36
|
-
import { sha256 } from "../../Util/index.ts";
|
|
37
41
|
import { Account } from "../Account.ts";
|
|
38
42
|
import { CloudflareLogs } from "../Logs.ts";
|
|
39
43
|
import type { AssetsConfig, AssetsProps } from "./Assets.ts";
|
|
@@ -42,6 +46,7 @@ import cloudflare_workers from "./cloudflare_workers.ts";
|
|
|
42
46
|
import { isDurableObjectExport } from "./DurableObject.ts";
|
|
43
47
|
import { fromCloudflareFetcher } from "./Fetcher.ts";
|
|
44
48
|
import { workersHttpHandler } from "./HttpServer.ts";
|
|
49
|
+
import { Request } from "./Request.ts";
|
|
45
50
|
import { makeRpcStub } from "./Rpc.ts";
|
|
46
51
|
import { isWorkflowExport } from "./Workflow.ts";
|
|
47
52
|
|
|
@@ -163,6 +168,11 @@ export interface WorkerProps extends PlatformProps {
|
|
|
163
168
|
enabled?: boolean;
|
|
164
169
|
previewsEnabled?: boolean;
|
|
165
170
|
};
|
|
171
|
+
/** @internal used by Cloudflare.Vite resource */
|
|
172
|
+
vite?: {
|
|
173
|
+
rootDir?: string;
|
|
174
|
+
memo?: MemoOptions;
|
|
175
|
+
};
|
|
166
176
|
logpush?: boolean;
|
|
167
177
|
observability?: WorkerObservability;
|
|
168
178
|
tags?: string[];
|
|
@@ -181,7 +191,7 @@ export interface WorkerExecutionContext extends Serverless.FunctionContext {
|
|
|
181
191
|
export(name: string, value: any): Effect.Effect<void>;
|
|
182
192
|
}
|
|
183
193
|
|
|
184
|
-
export type WorkerServices = Worker | WorkerEnvironment;
|
|
194
|
+
export type WorkerServices = Worker | WorkerEnvironment | Request;
|
|
185
195
|
|
|
186
196
|
export type WorkerShape = Main<WorkerServices>;
|
|
187
197
|
|
|
@@ -197,7 +207,8 @@ export interface Worker extends Resource<
|
|
|
197
207
|
accountId: string;
|
|
198
208
|
hash?: {
|
|
199
209
|
assets: string | undefined;
|
|
200
|
-
bundle: string;
|
|
210
|
+
bundle: string | undefined;
|
|
211
|
+
input: string | undefined;
|
|
201
212
|
};
|
|
202
213
|
},
|
|
203
214
|
{
|
|
@@ -249,11 +260,31 @@ export const Worker: Platform<
|
|
|
249
260
|
? Effect.succeed(value)
|
|
250
261
|
: Effect.die(`Environment variable '${key}' not found`),
|
|
251
262
|
),
|
|
263
|
+
Effect.map((json) => {
|
|
264
|
+
try {
|
|
265
|
+
const value = JSON.parse(json);
|
|
266
|
+
if (!Redacted.isRedacted(value)) {
|
|
267
|
+
return Redacted.make(value.value);
|
|
268
|
+
}
|
|
269
|
+
return value;
|
|
270
|
+
} catch {
|
|
271
|
+
return json;
|
|
272
|
+
}
|
|
273
|
+
}),
|
|
252
274
|
) as any,
|
|
253
275
|
set: (id: string, output: Output.Output) =>
|
|
254
276
|
Effect.sync(() => {
|
|
255
277
|
const key = id.replaceAll(/[^a-zA-Z0-9]/g, "_");
|
|
256
|
-
env[key] = output.pipe(
|
|
278
|
+
env[key] = output.pipe(
|
|
279
|
+
Output.map((value) =>
|
|
280
|
+
Redacted.isRedacted(value)
|
|
281
|
+
? JSON.stringify({
|
|
282
|
+
_tag: "Redacted",
|
|
283
|
+
value: Redacted.value(value),
|
|
284
|
+
})
|
|
285
|
+
: JSON.stringify(value),
|
|
286
|
+
),
|
|
287
|
+
);
|
|
257
288
|
return key;
|
|
258
289
|
}),
|
|
259
290
|
serve: <Req = never>(handler: HttpEffect<Req>) =>
|
|
@@ -389,6 +420,10 @@ export const WorkerProvider = () =>
|
|
|
389
420
|
const listScripts = yield* workers.listScripts;
|
|
390
421
|
const putScript = yield* workers.putScript;
|
|
391
422
|
const telemetry = yield* CloudflareLogs;
|
|
423
|
+
const defaultCompatibilityDate = yield* Effect.promise(() =>
|
|
424
|
+
// @ts-expect-error no types for workerd
|
|
425
|
+
import("workerd").then((m) => m.compatibilityDate as string),
|
|
426
|
+
);
|
|
392
427
|
|
|
393
428
|
const getAccountSubdomain = (accountId: string) =>
|
|
394
429
|
getSubdomain({
|
|
@@ -436,44 +471,34 @@ export const WorkerProvider = () =>
|
|
|
436
471
|
"path" in assets &&
|
|
437
472
|
"hash" in assets
|
|
438
473
|
) {
|
|
439
|
-
const path = assets.path as string;
|
|
440
|
-
const hash = assets.hash as string;
|
|
441
474
|
const result = yield* read({
|
|
442
|
-
directory: path,
|
|
475
|
+
directory: assets.path as string,
|
|
443
476
|
config: assets.config,
|
|
444
477
|
});
|
|
445
478
|
return {
|
|
446
479
|
...result,
|
|
447
|
-
hash,
|
|
480
|
+
hash: assets.hash as string,
|
|
448
481
|
};
|
|
449
482
|
}
|
|
450
483
|
|
|
451
484
|
// Handle string path or AssetsProps
|
|
452
|
-
|
|
485
|
+
return yield* read(
|
|
453
486
|
typeof assets === "string" ? { directory: assets } : assets,
|
|
454
487
|
);
|
|
455
|
-
return {
|
|
456
|
-
...result,
|
|
457
|
-
hash: yield* sha256(JSON.stringify(result)),
|
|
458
|
-
};
|
|
459
488
|
});
|
|
460
489
|
|
|
461
|
-
const prepareBundle = Effect.fnUntraced(function* (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const buildBundle = Effect.fnUntraced(function* (
|
|
467
|
-
entry: string,
|
|
468
|
-
plugins?: rolldown.RolldownPluginOption,
|
|
469
|
-
) {
|
|
470
|
-
const { files, hash } = yield* Bundle.build(
|
|
490
|
+
const prepareBundle = Effect.fnUntraced(function* (props: WorkerProps) {
|
|
491
|
+
const main = yield* fs.realPath(props.main);
|
|
492
|
+
const cwd = yield* findCwdForBundle(main);
|
|
493
|
+
const buildBundle = (plugins?: rolldown.RolldownPluginOption) =>
|
|
494
|
+
Bundle.build(
|
|
471
495
|
{
|
|
472
|
-
input:
|
|
473
|
-
cwd
|
|
496
|
+
input: main,
|
|
497
|
+
cwd,
|
|
474
498
|
plugins: [
|
|
475
|
-
|
|
476
|
-
compatibilityDate:
|
|
499
|
+
cloudflareRolldown({
|
|
500
|
+
compatibilityDate:
|
|
501
|
+
props.compatibility?.date ?? defaultCompatibilityDate,
|
|
477
502
|
compatibilityFlags: props.compatibility?.flags,
|
|
478
503
|
}),
|
|
479
504
|
plugins,
|
|
@@ -490,20 +515,10 @@ export const WorkerProvider = () =>
|
|
|
490
515
|
keepNames: true,
|
|
491
516
|
},
|
|
492
517
|
);
|
|
493
|
-
return {
|
|
494
|
-
files: files.map(
|
|
495
|
-
(file) =>
|
|
496
|
-
new File([file.content as BlobPart], file.path, {
|
|
497
|
-
type: contentTypeFromExtension(path.extname(file.path)),
|
|
498
|
-
}),
|
|
499
|
-
),
|
|
500
|
-
mainModule: files[0].path,
|
|
501
|
-
hash,
|
|
502
|
-
};
|
|
503
|
-
});
|
|
504
518
|
|
|
505
519
|
if (props.isExternal) {
|
|
506
|
-
|
|
520
|
+
const bundle = yield* buildBundle();
|
|
521
|
+
return bundle;
|
|
507
522
|
}
|
|
508
523
|
|
|
509
524
|
const exportMap = (props.exports ?? {}) as Record<string, unknown>;
|
|
@@ -574,7 +589,10 @@ const exportsEffect = tag.asEffect().pipe(
|
|
|
574
589
|
Layer.provideMerge(
|
|
575
590
|
Layer.succeed(
|
|
576
591
|
ConfigProvider.ConfigProvider,
|
|
577
|
-
ConfigProvider.
|
|
592
|
+
ConfigProvider.orElse(
|
|
593
|
+
ConfigProvider.fromUnknown({ ALCHEMY_PHASE: "runtime" }),
|
|
594
|
+
ConfigProvider.fromUnknown(env),
|
|
595
|
+
),
|
|
578
596
|
)
|
|
579
597
|
),
|
|
580
598
|
Layer.provideMerge(
|
|
@@ -631,9 +649,111 @@ ${[
|
|
|
631
649
|
].join("\n")}
|
|
632
650
|
`;
|
|
633
651
|
|
|
634
|
-
return yield* buildBundle(
|
|
652
|
+
return yield* buildBundle(virtualEntryPlugin(script));
|
|
635
653
|
});
|
|
636
654
|
|
|
655
|
+
const viteBuild = Effect.fnUntraced(function* (props: WorkerProps) {
|
|
656
|
+
const vite = yield* Effect.promise(() => import("vite"));
|
|
657
|
+
let assetsDirectory: string | undefined;
|
|
658
|
+
let serverBundle: vite.Rolldown.OutputBundle | undefined;
|
|
659
|
+
|
|
660
|
+
yield* Effect.promise(async () => {
|
|
661
|
+
const builder = await vite.createBuilder(
|
|
662
|
+
{
|
|
663
|
+
root: props.vite?.rootDir,
|
|
664
|
+
plugins: [
|
|
665
|
+
cloudflareVite({
|
|
666
|
+
compatibilityDate:
|
|
667
|
+
props.compatibility?.date ?? defaultCompatibilityDate,
|
|
668
|
+
compatibilityFlags: props.compatibility?.flags,
|
|
669
|
+
}),
|
|
670
|
+
{
|
|
671
|
+
name: "output:ssr",
|
|
672
|
+
applyToEnvironment(environment) {
|
|
673
|
+
return environment.name === "ssr";
|
|
674
|
+
},
|
|
675
|
+
generateBundle(_outputOptions, bundle) {
|
|
676
|
+
serverBundle = bundle;
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: "output:client",
|
|
681
|
+
applyToEnvironment(environment) {
|
|
682
|
+
return environment.name === "client";
|
|
683
|
+
},
|
|
684
|
+
generateBundle(outputOptions) {
|
|
685
|
+
assetsDirectory = outputOptions.dir;
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
],
|
|
689
|
+
},
|
|
690
|
+
// This is the `useLegacyBuilder` option. The Vite CLI implementation uses `null` here.
|
|
691
|
+
// Originally we used `undefined` here, but this caused the static site build to fail.
|
|
692
|
+
// https://github.com/vitejs/vite/blob/a07a4bd052ac75f916391c999c408ad5f2867e61/packages/vite/src/node/cli.ts#L367
|
|
693
|
+
null,
|
|
694
|
+
);
|
|
695
|
+
await builder.buildApp();
|
|
696
|
+
});
|
|
697
|
+
if (!assetsDirectory && !serverBundle) {
|
|
698
|
+
return yield* Effect.die(
|
|
699
|
+
new Error("Vite build produced neither server nor client output"),
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
const [assets, bundle] = yield* Effect.all(
|
|
703
|
+
[
|
|
704
|
+
assetsDirectory
|
|
705
|
+
? read({
|
|
706
|
+
directory: assetsDirectory,
|
|
707
|
+
config:
|
|
708
|
+
typeof props.assets === "object" && "config" in props.assets
|
|
709
|
+
? props.assets.config
|
|
710
|
+
: undefined,
|
|
711
|
+
})
|
|
712
|
+
: Effect.succeed(undefined),
|
|
713
|
+
serverBundle
|
|
714
|
+
? Bundle.bundleOutputFromRolldownOutputBundle(serverBundle)
|
|
715
|
+
: Effect.succeed(undefined),
|
|
716
|
+
],
|
|
717
|
+
{ concurrency: "unbounded" },
|
|
718
|
+
);
|
|
719
|
+
return { assets, bundle };
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
const prepareAssetsAndBundle = (props: WorkerProps) =>
|
|
723
|
+
Effect.gen(function* () {
|
|
724
|
+
if (props.vite) {
|
|
725
|
+
const [{ assets, bundle }, input] = yield* Effect.all(
|
|
726
|
+
[viteBuild(props), hashDirectory(props.vite)],
|
|
727
|
+
{ concurrency: "unbounded" },
|
|
728
|
+
);
|
|
729
|
+
return { assets, bundle, input };
|
|
730
|
+
}
|
|
731
|
+
const [assets, bundle] = yield* Effect.all(
|
|
732
|
+
[prepareAssets(props.assets), prepareBundle(props)],
|
|
733
|
+
{ concurrency: "unbounded" },
|
|
734
|
+
);
|
|
735
|
+
return { assets, bundle };
|
|
736
|
+
}).pipe(
|
|
737
|
+
Effect.map(({ assets, bundle, input }) => ({
|
|
738
|
+
assets,
|
|
739
|
+
bundle: {
|
|
740
|
+
main: bundle?.files[0].path,
|
|
741
|
+
files: bundle?.files.map(
|
|
742
|
+
(file) =>
|
|
743
|
+
new File([file.content as BlobPart], file.path, {
|
|
744
|
+
type: contentTypeFromExtension(path.extname(file.path)),
|
|
745
|
+
}),
|
|
746
|
+
),
|
|
747
|
+
},
|
|
748
|
+
hash: {
|
|
749
|
+
assets: assets?.hash,
|
|
750
|
+
bundle: bundle?.hash,
|
|
751
|
+
input,
|
|
752
|
+
} satisfies Worker["Attributes"]["hash"],
|
|
753
|
+
})),
|
|
754
|
+
Artifacts.cached("build"),
|
|
755
|
+
);
|
|
756
|
+
|
|
637
757
|
const putWorker = Effect.fnUntraced(function* (
|
|
638
758
|
id: string,
|
|
639
759
|
news: WorkerProps,
|
|
@@ -647,10 +767,7 @@ ${[
|
|
|
647
767
|
yield* Effect.logInfo(
|
|
648
768
|
`Cloudflare Worker ${olds ? "update" : "create"}: preparing bundle for ${name}`,
|
|
649
769
|
);
|
|
650
|
-
const
|
|
651
|
-
prepareAssets(news.assets),
|
|
652
|
-
prepareBundle(id, news),
|
|
653
|
-
]);
|
|
770
|
+
const { assets, bundle, hash } = yield* prepareAssetsAndBundle(news);
|
|
654
771
|
const metadataBindings = bindings.flatMap((b) => b.data.bindings);
|
|
655
772
|
let metadataAssets:
|
|
656
773
|
| workers.PutScriptRequest["metadata"]["assets"]
|
|
@@ -867,7 +984,7 @@ ${[
|
|
|
867
984
|
keepBindings: undefined,
|
|
868
985
|
limits: news.limits,
|
|
869
986
|
logpush: news.logpush,
|
|
870
|
-
mainModule: bundle.
|
|
987
|
+
mainModule: bundle.main,
|
|
871
988
|
migrations,
|
|
872
989
|
observability: news.observability ?? {
|
|
873
990
|
enabled: true,
|
|
@@ -936,13 +1053,33 @@ ${[
|
|
|
936
1053
|
: undefined,
|
|
937
1054
|
tags: metadata.tags,
|
|
938
1055
|
accountId,
|
|
939
|
-
hash
|
|
940
|
-
assets: assets?.hash,
|
|
941
|
-
bundle: bundle.hash,
|
|
942
|
-
},
|
|
1056
|
+
hash,
|
|
943
1057
|
} satisfies Worker["Attributes"];
|
|
944
1058
|
});
|
|
945
1059
|
|
|
1060
|
+
const hasChanged = Effect.fnUntraced(function* (
|
|
1061
|
+
props: WorkerProps,
|
|
1062
|
+
output: Worker["Attributes"],
|
|
1063
|
+
) {
|
|
1064
|
+
if (props.vite) {
|
|
1065
|
+
const input = yield* hashDirectory(props.vite);
|
|
1066
|
+
return input !== output.hash?.input;
|
|
1067
|
+
}
|
|
1068
|
+
const [assetsHash, bundleHash] = yield* Effect.all(
|
|
1069
|
+
[
|
|
1070
|
+
"assets" in output && output.hash?.assets
|
|
1071
|
+
? Effect.succeed(output.hash.assets)
|
|
1072
|
+
: prepareAssets(props.assets).pipe(Effect.map((a) => a?.hash)),
|
|
1073
|
+
prepareBundle(props).pipe(Effect.map((b) => b.hash)),
|
|
1074
|
+
],
|
|
1075
|
+
{ concurrency: "unbounded" },
|
|
1076
|
+
);
|
|
1077
|
+
return (
|
|
1078
|
+
assetsHash !== output.hash?.assets ||
|
|
1079
|
+
bundleHash !== output.hash?.bundle
|
|
1080
|
+
);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
946
1083
|
return Worker.provider.of({
|
|
947
1084
|
stables: ["workerId", "workerName"],
|
|
948
1085
|
diff: Effect.fnUntraced(function* ({ id, news, olds, output }) {
|
|
@@ -960,14 +1097,7 @@ ${[
|
|
|
960
1097
|
if (!output) {
|
|
961
1098
|
return;
|
|
962
1099
|
}
|
|
963
|
-
|
|
964
|
-
prepareAssets(news.assets),
|
|
965
|
-
prepareBundle(id, news),
|
|
966
|
-
]);
|
|
967
|
-
if (
|
|
968
|
-
assets?.hash !== output.hash?.assets ||
|
|
969
|
-
bundle.hash !== output.hash?.bundle
|
|
970
|
-
) {
|
|
1100
|
+
if (yield* hasChanged(news, output)) {
|
|
971
1101
|
return {
|
|
972
1102
|
action: "update",
|
|
973
1103
|
stables:
|
package/src/Destroy.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import { apply } from "./Apply.ts";
|
|
3
|
+
import { provideFreshArtifactStore } from "./Artifacts.ts";
|
|
3
4
|
import * as Plan from "./Plan.ts";
|
|
4
5
|
import { Stack } from "./Stack.ts";
|
|
5
6
|
|
|
@@ -11,5 +12,5 @@ export const destroy = () =>
|
|
|
11
12
|
resources: {},
|
|
12
13
|
bindings: {},
|
|
13
14
|
output: {},
|
|
14
|
-
}),
|
|
15
|
-
)
|
|
15
|
+
}).pipe(Effect.flatMap(apply), provideFreshArtifactStore),
|
|
16
|
+
);
|
package/src/Output.ts
CHANGED
|
@@ -53,6 +53,7 @@ export interface Output<A = any, Req = any> extends Pipeable {
|
|
|
53
53
|
>;
|
|
54
54
|
bind(id: string): Effect.Effect<Effect.Effect<A>, never, ExecutionContext>;
|
|
55
55
|
asEffect(): Effect.Effect<Accessor<A>, never, Req>;
|
|
56
|
+
as<T>(): Output<T, Req>;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export interface Accessor<A> extends Effect.Effect<A> {}
|
|
@@ -91,6 +92,9 @@ export abstract class BaseExpr<A = any, Req = any> implements Output<A, Req> {
|
|
|
91
92
|
declare readonly req: Req;
|
|
92
93
|
// we use a kind tag instead of instanceof to protect ourselves from duplicate alchemy-effect module imports
|
|
93
94
|
constructor() {}
|
|
95
|
+
as<T>(): Output<T, Req> {
|
|
96
|
+
return this as any;
|
|
97
|
+
}
|
|
94
98
|
|
|
95
99
|
[Symbol.iterator](): Iterator<
|
|
96
100
|
Yieldable<any, void, never, Req>,
|