alchemy 2.0.0-beta.3 → 2.0.0-beta.test-export-fix-3

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.
Files changed (55) hide show
  1. package/bin/{alchemy-effect.js → alchemy.js} +172 -172
  2. package/bin/alchemy.js.map +1 -0
  3. package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
  4. package/lib/cli/index.js +169 -169
  5. package/lib/cli/index.js.map +1 -1
  6. package/package.json +60 -61
  7. package/src/AWS/AGENTS.md +10 -10
  8. package/src/AWS/Assets.ts +1 -1
  9. package/src/AWS/Credentials.ts +0 -1
  10. package/src/AWS/DynamoDB/Table.ts +63 -10
  11. package/src/AWS/EC2/hosted.ts +1 -1
  12. package/src/AWS/ECS/Task.ts +1 -1
  13. package/src/AWS/Kinesis/Stream.ts +44 -8
  14. package/src/AWS/Lambda/Function.ts +218 -10
  15. package/src/AWS/S3/Bucket.ts +26 -19
  16. package/src/AWS/S3/BucketNotifications.ts +1 -1
  17. package/src/AWS/SNS/Topic.ts +47 -10
  18. package/src/AWS/SQS/Queue.ts +66 -0
  19. package/src/Cloudflare/Container/ContainerApplication.ts +3 -3
  20. package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
  21. package/src/Cloudflare/Container/StartContainer.ts +1 -1
  22. package/src/Cloudflare/Providers.ts +1 -6
  23. package/src/Cloudflare/R2/R2Bucket.ts +2 -2
  24. package/src/Cloudflare/Website/StaticSite.ts +22 -6
  25. package/src/Cloudflare/Website/Vite.ts +15 -5
  26. package/src/Cloudflare/Workers/Assets.ts +170 -207
  27. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +230 -370
  28. package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
  29. package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
  30. package/src/Cloudflare/Workers/InferEnv.ts +11 -7
  31. package/src/Cloudflare/Workers/Rpc.ts +13 -2
  32. package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
  33. package/src/Cloudflare/Workers/WebSocket.ts +1 -1
  34. package/src/Cloudflare/Workers/Worker.ts +95 -43
  35. package/src/Cloudflare/Workers/index.ts +3 -0
  36. package/src/Construct.ts +2 -2
  37. package/src/GitHub/Secret.ts +52 -7
  38. package/src/GitHub/Variable.ts +45 -2
  39. package/src/Kubernetes/client.ts +2 -2
  40. package/src/Output.ts +1 -1
  41. package/src/Platform.ts +1 -1
  42. package/src/Test/Vitest.ts +4 -3
  43. package/src/Util/PlatformServices.ts +21 -0
  44. package/src/Util/dedent.ts +4 -1
  45. package/bin/alchemy-effect.js.map +0 -1
  46. package/src/Daemon/Client.ts +0 -116
  47. package/src/Daemon/Config.ts +0 -28
  48. package/src/Daemon/Errors.ts +0 -48
  49. package/src/Daemon/Lock.ts +0 -162
  50. package/src/Daemon/ProcessRegistry.ts +0 -284
  51. package/src/Daemon/RpcSchema.ts +0 -43
  52. package/src/Daemon/RpcServer.ts +0 -231
  53. package/src/Daemon/index.ts +0 -27
  54. package/src/Spawn.ts +0 -23
  55. /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
@@ -26,7 +26,7 @@ import { findCwdForBundle } from "../../Bundle/TempRoot.ts";
26
26
  import type { ScopedPlanStatusSession } from "../../Cli/Cli.ts";
27
27
  import { isResolved } from "../../Diff.ts";
28
28
  import type { HttpEffect } from "../../Http.ts";
29
- import type { Input, InputProps } from "../../Input.ts";
29
+ import type { InputProps } from "../../Input.ts";
30
30
  import * as Output from "../../Output.ts";
31
31
  import { createPhysicalName } from "../../PhysicalName.ts";
32
32
  import {
@@ -47,10 +47,20 @@ import { fromCloudflareFetcher } from "../Fetcher.ts";
47
47
  import { CloudflareLogs } from "../Logs.ts";
48
48
  import type { Providers } from "../Providers.ts";
49
49
  import type { R2Bucket } from "../R2/R2Bucket.ts";
50
- import type { AssetsConfig, AssetsProps } from "./Assets.ts";
51
- import * as Assets from "./Assets.ts";
50
+ import {
51
+ isAssets,
52
+ readAssets,
53
+ uploadAssets,
54
+ type Assets,
55
+ type AssetsConfig,
56
+ type AssetsProps,
57
+ } from "./Assets.ts";
52
58
  import cloudflare_workers from "./cloudflare_workers.ts";
53
- import { isDurableObjectExport } from "./DurableObjectNamespace.ts";
59
+ import {
60
+ isDurableObjectExport,
61
+ isDurableObjectNamespaceLike,
62
+ type DurableObjectNamespaceLike,
63
+ } from "./DurableObjectNamespace.ts";
54
64
  import { workersHttpHandler } from "./HttpServer.ts";
55
65
  import { Request } from "./Request.ts";
56
66
  import { makeRpcStub } from "./Rpc.ts";
@@ -105,12 +115,12 @@ export interface AssetsWithHash {
105
115
  /**
106
116
  * Path to the assets directory.
107
117
  */
108
- path: Input<string>;
118
+ path: string;
109
119
  /**
110
120
  * Pre-computed hash of the assets. When provided, this hash is used for diffing
111
121
  * to determine if the worker needs to be redeployed.
112
122
  */
113
- hash: Input<string>;
123
+ hash: string;
114
124
  /**
115
125
  * Optional assets configuration.
116
126
  */
@@ -161,7 +171,11 @@ export type WorkerServices = Worker | WorkerEnvironment | Request;
161
171
 
162
172
  export type WorkerShape = Main<WorkerServices>;
163
173
 
164
- export type WorkerBindingResource = R2Bucket | D1Database;
174
+ export type WorkerBindingResource =
175
+ | Assets
176
+ | R2Bucket
177
+ | D1Database
178
+ | DurableObjectNamespaceLike<any>;
165
179
 
166
180
  export type WorkerBindings = {
167
181
  [bindingName in string]: WorkerBindingResource;
@@ -173,8 +187,26 @@ export type WorkerBindingProps = {
173
187
  | Effect.Effect<WorkerBindingResource, any, any>;
174
188
  };
175
189
 
190
+ type NormalizedBindings<
191
+ Bindings extends WorkerBindingProps = {},
192
+ AssetsConfig extends WorkerAssetsConfig | undefined = undefined,
193
+ > = {
194
+ [B in keyof Bindings]: Bindings[B] extends Effect.Effect<
195
+ infer T extends WorkerBindingResource,
196
+ any,
197
+ any
198
+ >
199
+ ? T
200
+ : Extract<Bindings[B], WorkerBindingResource>;
201
+ } & (undefined extends AssetsConfig ? {} : { ASSETS: Assets });
202
+
203
+ export type WorkerAssetsConfig = string | AssetsProps | AssetsWithHash;
204
+
176
205
  export interface WorkerProps<
177
206
  Bindings extends WorkerBindingProps = any,
207
+ Assets extends WorkerAssetsConfig | undefined =
208
+ | WorkerAssetsConfig
209
+ | undefined,
178
210
  > extends PlatformProps {
179
211
  /**
180
212
  * Worker name override. If omitted, Alchemy derives a deterministic physical
@@ -192,11 +224,7 @@ export interface WorkerProps<
192
224
  * - An AssetsProps object with directory and config
193
225
  * - An object with path and hash (e.g., from a Build resource)
194
226
  */
195
- assets?:
196
- | string
197
- | AssetsProps
198
- | AssetsWithHash
199
- | (AssetsWithHash & { [K: string]: any });
227
+ assets?: Assets;
200
228
  subdomain?: {
201
229
  enabled?: boolean;
202
230
  previewsEnabled?: boolean;
@@ -295,6 +323,10 @@ export type Worker<Bindings extends WorkerBindings = any> = Resource<
295
323
  * at runtime, and `Cloudflare.InferEnv` to extract a fully typed
296
324
  * `env` object from those bindings.
297
325
  *
326
+ * See the {@link https://alchemy.run/guides/async-worker | Async Workers Guide}
327
+ * for a comprehensive walkthrough of all binding types (R2, D1,
328
+ * Durable Objects, Assets, and more).
329
+ *
298
330
  * @example Defining an async Worker in your stack
299
331
  * ```typescript
300
332
  * // alchemy.run.ts
@@ -579,18 +611,18 @@ export const Worker: Platform<
579
611
  WorkerShape,
580
612
  WorkerExecutionContext
581
613
  > & {
582
- <const Bindings extends WorkerBindingProps>(
614
+ <
615
+ const Bindings extends WorkerBindingProps,
616
+ const Assets extends WorkerAssetsConfig | undefined = undefined,
617
+ >(
583
618
  id: string,
584
- props: InputProps<WorkerProps<Bindings>>,
619
+ props: InputProps<WorkerProps<Bindings, Assets>>,
585
620
  ): Effect.Effect<
586
621
  Worker<{
587
- [B in keyof Bindings]: Bindings[B] extends Effect.Effect<
588
- infer T extends WorkerBindingResource,
589
- any,
590
- any
591
- >
592
- ? T
593
- : Extract<Bindings[B], WorkerBindingResource>;
622
+ [binding in keyof NormalizedBindings<
623
+ Bindings,
624
+ Assets
625
+ >]: NormalizedBindings<Bindings, Assets>[binding];
594
626
  }>
595
627
  >;
596
628
  } = Platform(WorkerTypeId, {
@@ -608,31 +640,45 @@ export const Worker: Platform<
608
640
  ? yield* bindingEff
609
641
  : bindingEff;
610
642
 
611
- const bindingMeta: InputProps<WorkerBinding> | undefined =
612
- binding.Type === "Cloudflare.D1Database"
643
+ const bindingMeta: InputProps<WorkerBinding> | undefined = isAssets(
644
+ binding,
645
+ )
646
+ ? {
647
+ type: "assets",
648
+ name: bindingName,
649
+ }
650
+ : isDurableObjectNamespaceLike(binding)
613
651
  ? {
614
- type: "d1",
615
- id: binding.databaseId,
652
+ type: "durable_object_namespace",
616
653
  name: bindingName,
654
+ className: binding.className ?? binding.name,
617
655
  }
618
- : binding.Type === "Cloudflare.R2Bucket"
656
+ : binding.Type === "Cloudflare.D1Database"
619
657
  ? {
620
- type: "r2_bucket",
658
+ type: "d1",
659
+ id: binding.databaseId,
621
660
  name: bindingName,
622
- bucketName: binding.bucketName,
623
- jurisdiction: binding.jurisdiction.pipe(
624
- Output.map((jurisdiction) =>
625
- jurisdiction === "default" ? undefined : jurisdiction,
626
- ),
627
- ),
628
661
  }
629
- : // TODO(sam): handle others
630
- undefined;
662
+ : binding.Type === "Cloudflare.R2Bucket"
663
+ ? {
664
+ type: "r2_bucket",
665
+ name: bindingName,
666
+ bucketName: binding.bucketName,
667
+ jurisdiction: binding.jurisdiction.pipe(
668
+ Output.map((jurisdiction) =>
669
+ jurisdiction === "default" ? undefined : jurisdiction,
670
+ ),
671
+ ),
672
+ }
673
+ : // TODO(sam): handle others
674
+ undefined;
631
675
 
632
676
  if (bindingMeta) {
633
677
  yield* resource.bind`${bindingName}`({
634
678
  bindings: [bindingMeta],
635
679
  });
680
+ } else {
681
+ return yield* Effect.die(`Unknown binding type: ${bindingName}`);
636
682
  }
637
683
  }
638
684
  }
@@ -857,7 +903,6 @@ export const WorkerProvider = () =>
857
903
  const virtualEntryPlugin = yield* Bundle.virtualEntryPlugin;
858
904
  const stack = yield* Stack;
859
905
 
860
- const { read, upload } = yield* Assets.Assets;
861
906
  const createScriptSubdomain = yield* workers.createScriptSubdomain;
862
907
  const createScriptTail = yield* workers.createScriptTail;
863
908
  const deleteScript = yield* workers.deleteScript;
@@ -974,7 +1019,9 @@ export const WorkerProvider = () =>
974
1019
  const prepareAssets = Effect.fnUntraced(function* (
975
1020
  assets: WorkerProps["assets"],
976
1021
  ) {
977
- if (!assets) return undefined;
1022
+ if (!assets) {
1023
+ return undefined;
1024
+ }
978
1025
 
979
1026
  // Handle AssetsWithHash (from Build resource)
980
1027
  // Props are resolved by Plan, so Input<string> values are already strings at runtime
@@ -983,7 +1030,7 @@ export const WorkerProvider = () =>
983
1030
  "path" in assets &&
984
1031
  "hash" in assets
985
1032
  ) {
986
- const result = yield* read({
1033
+ const result = yield* readAssets({
987
1034
  directory: assets.path as string,
988
1035
  config: assets.config,
989
1036
  });
@@ -994,7 +1041,7 @@ export const WorkerProvider = () =>
994
1041
  }
995
1042
 
996
1043
  // Handle string path or AssetsProps
997
- return yield* read(
1044
+ return yield* readAssets(
998
1045
  typeof assets === "string" ? { directory: assets } : assets,
999
1046
  );
1000
1047
  });
@@ -1065,8 +1112,8 @@ import * as Stream from "effect/Stream";
1065
1112
  import { env, DurableObject${hasWfClasses ? ", WorkflowEntrypoint" : ""} } from "cloudflare:workers";
1066
1113
  import { MinimumLogLevel } from "effect/References";
1067
1114
  import { NodeServices } from "@effect/platform-node";
1068
- import { Stack } from "alchemy-effect/Stack";
1069
- import { WorkerEnvironment, makeDurableObjectBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy-effect/Cloudflare";
1115
+ import { Stack } from "alchemy/Stack";
1116
+ import { WorkerEnvironment, makeDurableObjectBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy/Cloudflare";
1070
1117
 
1071
1118
  import entry from "${importPath}";
1072
1119
 
@@ -1232,7 +1279,7 @@ ${[
1232
1279
  const [assets, bundle] = yield* Effect.all(
1233
1280
  [
1234
1281
  assetsDirectory
1235
- ? read({
1282
+ ? readAssets({
1236
1283
  directory: assetsDirectory,
1237
1284
  config:
1238
1285
  typeof props.assets === "object" && "config" in props.assets
@@ -1312,7 +1359,12 @@ ${[
1312
1359
  yield* Effect.logInfo(
1313
1360
  `Cloudflare Worker ${olds ? "update" : "create"}: uploading assets for ${name}`,
1314
1361
  );
1315
- const { jwt } = yield* upload(accountId, name, assets, session);
1362
+ const { jwt } = yield* uploadAssets(
1363
+ accountId,
1364
+ name,
1365
+ assets,
1366
+ session,
1367
+ );
1316
1368
  metadataAssets = {
1317
1369
  jwt,
1318
1370
  config: assets.config,
@@ -1,12 +1,15 @@
1
1
  export * from "./Assets.ts";
2
2
  export * from "./ConfigProvider.ts";
3
3
  export * from "./DurableObjectNamespace.ts";
4
+ export * from "./DurableObjectState.ts";
5
+ export * from "./DurableObjectStorage.ts";
4
6
  export * from "./DynamicWorkerLoader.ts";
5
7
  export * from "./Fetch.ts";
6
8
  export * from "./HttpServer.ts";
7
9
  export * from "./InferEnv.ts";
8
10
  export * from "./Request.ts";
9
11
  export * from "./Rpc.ts";
12
+ export * from "./ScheduledEvents.ts";
10
13
  export * from "./WebSocket.ts";
11
14
  export * from "./Worker.ts";
12
15
  export * from "./Workflow.ts";
package/src/Construct.ts CHANGED
@@ -12,8 +12,8 @@ import * as Namespace from "./Namespace.ts";
12
12
  * @section Creating Constructs
13
13
  * @example Simple Reusable Construct
14
14
  * ```typescript
15
- * import * as Construct from "alchemy-effect/Construct";
16
- * import { Bucket } from "alchemy-effect/AWS/S3";
15
+ * import * as Construct from "alchemy/Construct";
16
+ * import { Bucket } from "alchemy/AWS/S3";
17
17
  *
18
18
  * export const Logs = Construct.fn(function* (
19
19
  * id: string,
@@ -53,11 +53,19 @@ export interface Secret extends Resource<
53
53
  /**
54
54
  * A GitHub Actions repository or environment secret.
55
55
  *
56
+ * `Secret` manages the lifecycle of an encrypted secret in GitHub Actions.
56
57
  * Secrets are encrypted using the repository's (or environment's) public
57
- * key before being stored. The resource is idempotent — calling it with
58
- * the same name will update the secret value in place.
58
+ * key via `libsodium` before being stored. The resource is idempotent —
59
+ * calling it with the same name will update the secret value in place.
60
+ *
61
+ * Authentication is resolved in order: explicit `token` prop,
62
+ * `GITHUB_ACCESS_TOKEN` env var, `GITHUB_TOKEN` env var. The token needs
63
+ * `repo` scope for private repositories or `public_repo` for public ones.
59
64
  *
60
65
  * @section Repository Secrets
66
+ * Store secrets accessible to all GitHub Actions workflows in the
67
+ * repository.
68
+ *
61
69
  * @example Create a Repository Secret
62
70
  * ```typescript
63
71
  * yield* GitHub.Secret("aws-role", {
@@ -69,6 +77,10 @@ export interface Secret extends Resource<
69
77
  * ```
70
78
  *
71
79
  * @section Environment Secrets
80
+ * Scope a secret to a specific GitHub Actions environment (e.g.
81
+ * `production`, `staging`). Environment secrets require environment
82
+ * protection rules to be satisfied before workflows can access them.
83
+ *
72
84
  * @example Create an Environment Secret
73
85
  * ```typescript
74
86
  * yield* GitHub.Secret("deploy-key", {
@@ -79,6 +91,42 @@ export interface Secret extends Resource<
79
91
  * value: Redacted.make("my-secret-value"),
80
92
  * });
81
93
  * ```
94
+ *
95
+ * @section Wiring with Other Resources
96
+ * A common pattern is wiring the output of another resource — like an
97
+ * IAM role ARN or a database URL — directly into a GitHub secret so
98
+ * that CI workflows can use it.
99
+ *
100
+ * @example Store an IAM Role ARN for CI
101
+ * ```typescript
102
+ * const role = yield* AWS.IAM.Role("ci-role", { ... });
103
+ *
104
+ * yield* GitHub.Secret("ci-role-arn", {
105
+ * owner: "my-org",
106
+ * repository: "my-repo",
107
+ * name: "AWS_ROLE_ARN",
108
+ * value: Redacted.make(role.roleArn),
109
+ * });
110
+ * ```
111
+ *
112
+ * @example Store Multiple Secrets
113
+ * ```typescript
114
+ * yield* GitHub.Secret("db-url", {
115
+ * owner: "my-org",
116
+ * repository: "my-repo",
117
+ * environment: "production",
118
+ * name: "DATABASE_URL",
119
+ * value: Redacted.make(database.connectionString),
120
+ * });
121
+ *
122
+ * yield* GitHub.Secret("api-key", {
123
+ * owner: "my-org",
124
+ * repository: "my-repo",
125
+ * environment: "production",
126
+ * name: "API_KEY",
127
+ * value: Redacted.make(apiKey),
128
+ * });
129
+ * ```
82
130
  */
83
131
  export const Secret = Resource<Secret>("GitHub.Secret");
84
132
 
@@ -98,10 +146,7 @@ async function encryptValue(
98
146
  ): Promise<string> {
99
147
  const sodium = await import("libsodium-wrappers");
100
148
  await sodium.ready;
101
- const binKey = sodium.from_base64(
102
- publicKey,
103
- sodium.base64_variants.ORIGINAL,
104
- );
149
+ const binKey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
105
150
  const binMessage = sodium.from_string(plaintext);
106
151
  const encrypted = sodium.crypto_box_seal(binMessage, binKey);
107
152
  return sodium.to_base64(encrypted, sodium.base64_variants.ORIGINAL);
@@ -115,7 +160,7 @@ export const SecretProvider = () =>
115
160
  return { updatedAt: new Date().toISOString() };
116
161
  }),
117
162
 
118
- update: Effect.fn(function* ({ news, olds, output }) {
163
+ update: Effect.fn(function* ({ news, olds }) {
119
164
  const octokit = createClient(news);
120
165
 
121
166
  const wasEnv = !!olds.environment;
@@ -45,10 +45,20 @@ export interface Variable extends Resource<
45
45
  /**
46
46
  * A GitHub Actions repository variable.
47
47
  *
48
- * Variables are stored in plain text and are suitable for non-sensitive
49
- * configuration like region names or role ARNs.
48
+ * `Variable` manages the lifecycle of a plain-text configuration variable
49
+ * in GitHub Actions. Variables are visible in workflow logs and are
50
+ * suitable for non-sensitive configuration like region names, environment
51
+ * labels, or feature flags. For sensitive values, use `GitHub.Secret`
52
+ * instead.
53
+ *
54
+ * Authentication is resolved in order: explicit `token` prop,
55
+ * `GITHUB_ACCESS_TOKEN` env var, `GITHUB_TOKEN` env var. The token needs
56
+ * `repo` scope for private repositories or `public_repo` for public ones.
50
57
  *
51
58
  * @section Repository Variables
59
+ * Store variables accessible to all GitHub Actions workflows in the
60
+ * repository.
61
+ *
52
62
  * @example Create a Repository Variable
53
63
  * ```typescript
54
64
  * yield* GitHub.Variable("aws-region", {
@@ -58,6 +68,39 @@ export interface Variable extends Resource<
58
68
  * value: "us-east-1",
59
69
  * });
60
70
  * ```
71
+ *
72
+ * @section Wiring with Other Resources
73
+ * Pass output attributes from other resources into GitHub variables so
74
+ * that CI workflows can reference them.
75
+ *
76
+ * @example Store a Worker URL for CI
77
+ * ```typescript
78
+ * const worker = yield* Cloudflare.Worker("Api", { ... });
79
+ *
80
+ * yield* GitHub.Variable("api-url", {
81
+ * owner: "my-org",
82
+ * repository: "my-repo",
83
+ * name: "API_URL",
84
+ * value: worker.url!,
85
+ * });
86
+ * ```
87
+ *
88
+ * @example Multiple Variables
89
+ * ```typescript
90
+ * yield* GitHub.Variable("region", {
91
+ * owner: "my-org",
92
+ * repository: "my-repo",
93
+ * name: "AWS_REGION",
94
+ * value: "us-east-1",
95
+ * });
96
+ *
97
+ * yield* GitHub.Variable("stage", {
98
+ * owner: "my-org",
99
+ * repository: "my-repo",
100
+ * name: "DEPLOY_STAGE",
101
+ * value: "production",
102
+ * });
103
+ * ```
61
104
  */
62
105
  export const Variable = Resource<Variable>("GitHub.Variable");
63
106
 
@@ -1,6 +1,6 @@
1
- import { AwsClient } from "aws4fetch";
2
1
  import { Credentials } from "@distilled.cloud/aws/Credentials";
3
2
  import { Region } from "@distilled.cloud/aws/Region";
3
+ import { AwsClient } from "aws4fetch";
4
4
  import * as Data from "effect/Data";
5
5
  import * as Effect from "effect/Effect";
6
6
  import * as Redacted from "effect/Redacted";
@@ -28,7 +28,7 @@ export interface KubernetesClusterConnection {
28
28
  certificateAuthorityData: string;
29
29
  }
30
30
 
31
- const fieldManager = "alchemy-effect";
31
+ const fieldManager = "alchemy";
32
32
 
33
33
  const createBearerToken = Effect.fn(function* (clusterName: string) {
34
34
  const credentials = yield* yield* Credentials;
package/src/Output.ts CHANGED
@@ -90,7 +90,7 @@ export abstract class BaseExpr<A = any, Req = any> implements Output<A, Req> {
90
90
  declare readonly A: A;
91
91
  declare readonly src: ResourceLike;
92
92
  declare readonly req: Req;
93
- // we use a kind tag instead of instanceof to protect ourselves from duplicate alchemy-effect module imports
93
+ // we use a kind tag instead of instanceof to protect ourselves from duplicate alchemy module imports
94
94
  constructor() {}
95
95
  as<T>(): Output<T, Req> {
96
96
  return this as any;
package/src/Platform.ts CHANGED
@@ -33,7 +33,7 @@ export type Main<Services = never> = void | {
33
33
  };
34
34
 
35
35
  export type Rpc<Shape> = {
36
- "~alchemy-effect/rpc": Shape;
36
+ "~alchemy/rpc": Shape;
37
37
  };
38
38
 
39
39
  // services provided to the Resource
@@ -1,6 +1,6 @@
1
1
  import type * as aws from "@distilled.cloud/aws";
2
2
  import * as cf from "@distilled.cloud/cloudflare";
3
- import { NodeServices } from "@effect/platform-node";
3
+ // import { NodeServices } from "@effect/platform-node";
4
4
  import { expect, it } from "@effect/vitest";
5
5
  import { Logger } from "effect";
6
6
  import * as Config from "effect/Config";
@@ -44,6 +44,7 @@ import * as Serverless from "../Serverless/index.ts";
44
44
  import * as Stack from "../Stack.ts";
45
45
  import * as Stage from "../Stage.ts";
46
46
  import * as State from "../State/index.ts";
47
+ import { PlatformServices } from "../Util/PlatformServices.ts";
47
48
  import { TestCli } from "./TestCli.ts";
48
49
 
49
50
  export const expectEmptyObject = () =>
@@ -90,7 +91,7 @@ const quietLogger = Logger.make(() => {
90
91
  });
91
92
 
92
93
  const platform = Layer.mergeAll(
93
- NodeServices.layer,
94
+ PlatformServices,
94
95
  FetchHttpClient.layer,
95
96
  Logger.layer([process.env.VERBOSE ? Logger.consolePretty() : quietLogger]),
96
97
  );
@@ -212,7 +213,7 @@ const runWithContext = <A, Err>(
212
213
  MinimumLogLevel,
213
214
  process.env.DEBUG ? "Debug" : "Info",
214
215
  ),
215
- Effect.provide(NodeServices.layer),
216
+ Effect.provide(PlatformServices),
216
217
  );
217
218
  };
218
219
 
@@ -0,0 +1,21 @@
1
+ import type { BunServices } from "@effect/platform-bun/BunServices";
2
+ import type { NodeServices } from "@effect/platform-node/NodeServices";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+
6
+ const isBun = typeof Bun !== "undefined";
7
+
8
+ export const PlatformServices: Layer.Layer<
9
+ NodeServices | BunServices,
10
+ never,
11
+ never
12
+ > = Effect.promise(() => {
13
+ if (isBun) {
14
+ return import("@effect/platform-bun").then(
15
+ (platform) => platform.BunServices.layer,
16
+ );
17
+ }
18
+ return import("@effect/platform-node").then(
19
+ (platform) => platform.NodeServices.layer,
20
+ );
21
+ }).pipe(Layer.unwrap);
@@ -3,7 +3,10 @@
3
3
  *
4
4
  * If the first line is empty, it will be ignored.
5
5
  */
6
- export function dedent(strings: TemplateStringsArray, ...values: unknown[]): string;
6
+ export function dedent(
7
+ strings: TemplateStringsArray,
8
+ ...values: unknown[]
9
+ ): string;
7
10
  export function dedent(text: string): string;
8
11
  export function dedent(
9
12
  stringsOrText: TemplateStringsArray | string,