alchemy 2.0.0-beta.39 → 2.0.0-beta.40

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 (99) hide show
  1. package/bin/exec.js +5 -26
  2. package/bin/exec.js.map +1 -1
  3. package/lib/Auth/Lock.d.ts +4 -3
  4. package/lib/Auth/Lock.d.ts.map +1 -1
  5. package/lib/Auth/Lock.js +6 -5
  6. package/lib/Auth/Lock.js.map +1 -1
  7. package/lib/Axiom/Chart.d.ts +52 -2
  8. package/lib/Axiom/Chart.d.ts.map +1 -1
  9. package/lib/Bundle/Bundle.d.ts +7 -4
  10. package/lib/Bundle/Bundle.d.ts.map +1 -1
  11. package/lib/Bundle/Bundle.js +37 -14
  12. package/lib/Bundle/Bundle.js.map +1 -1
  13. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +11 -11
  14. package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -1
  15. package/lib/Cloudflare/AiGateway/AiGateway.js +28 -63
  16. package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -1
  17. package/lib/Cloudflare/Bridge.d.ts +3 -0
  18. package/lib/Cloudflare/Bridge.d.ts.map +1 -0
  19. package/lib/Cloudflare/Bridge.js +3 -0
  20. package/lib/Cloudflare/Bridge.js.map +1 -0
  21. package/lib/Cloudflare/Container/StartContainer.d.ts +2 -2
  22. package/lib/Cloudflare/Container/StartContainer.d.ts.map +1 -1
  23. package/lib/Cloudflare/Container/StartContainer.js +3 -3
  24. package/lib/Cloudflare/Container/StartContainer.js.map +1 -1
  25. package/lib/Cloudflare/Local/Sidecar.d.ts +45 -21
  26. package/lib/Cloudflare/Local/Sidecar.d.ts.map +1 -1
  27. package/lib/Cloudflare/Local/Sidecar.js +30 -6
  28. package/lib/Cloudflare/Local/Sidecar.js.map +1 -1
  29. package/lib/Cloudflare/Local/SidecarHandlers.d.ts +5 -2
  30. package/lib/Cloudflare/Local/SidecarHandlers.d.ts.map +1 -1
  31. package/lib/Cloudflare/Local/SidecarHandlers.js +323 -70
  32. package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
  33. package/lib/Cloudflare/Local/SidecarServer.js +9 -5
  34. package/lib/Cloudflare/Local/SidecarServer.js.map +1 -1
  35. package/lib/Cloudflare/StateStore/Api.d.ts +1 -1
  36. package/lib/Cloudflare/StateStore/Api.js +1 -1
  37. package/lib/Cloudflare/StateStore/Store.d.ts +1 -1
  38. package/lib/Cloudflare/Workers/HttpServer.d.ts.map +1 -1
  39. package/lib/Cloudflare/Workers/HttpServer.js +3 -25
  40. package/lib/Cloudflare/Workers/HttpServer.js.map +1 -1
  41. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +1 -2
  42. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  43. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +16 -97
  44. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  45. package/lib/Cloudflare/Workers/Vite.d.ts +9 -0
  46. package/lib/Cloudflare/Workers/Vite.d.ts.map +1 -0
  47. package/lib/Cloudflare/Workers/Vite.js +95 -0
  48. package/lib/Cloudflare/Workers/Vite.js.map +1 -0
  49. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  50. package/lib/Cloudflare/Workers/Worker.js +9 -95
  51. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  52. package/lib/Cloudflare/Workers/WorkerBundle.d.ts +1 -2
  53. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  54. package/lib/Cloudflare/Workers/WorkerBundle.js +5 -4
  55. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  56. package/lib/Diff.d.ts +15 -1
  57. package/lib/Diff.d.ts.map +1 -1
  58. package/lib/Diff.js +13 -7
  59. package/lib/Diff.js.map +1 -1
  60. package/lib/Http.d.ts.map +1 -1
  61. package/lib/Http.js +12 -7
  62. package/lib/Http.js.map +1 -1
  63. package/lib/Sidecar/Lock.d.ts +4 -5
  64. package/lib/Sidecar/Lock.d.ts.map +1 -1
  65. package/lib/Sidecar/Lock.js +22 -85
  66. package/lib/Sidecar/Lock.js.map +1 -1
  67. package/lib/Sidecar/RpcHandler.d.ts +4 -1
  68. package/lib/Sidecar/RpcHandler.d.ts.map +1 -1
  69. package/lib/Sidecar/RpcHandler.js +21 -2
  70. package/lib/Sidecar/RpcHandler.js.map +1 -1
  71. package/lib/Util/PlatformServices.d.ts.map +1 -1
  72. package/lib/Util/PlatformServices.js +0 -2
  73. package/lib/Util/PlatformServices.js.map +1 -1
  74. package/lib/tsconfig.test.tsbuildinfo +1 -1
  75. package/package.json +17 -12
  76. package/src/AWS/EC2/hosted.ts +1 -1
  77. package/src/AWS/ECS/Task.ts +1 -1
  78. package/src/AWS/Lambda/Function.ts +1 -1
  79. package/src/Auth/Lock.ts +6 -5
  80. package/src/Axiom/Chart.ts +56 -2
  81. package/src/Bundle/Bundle.ts +41 -17
  82. package/src/Cloudflare/AiGateway/AiGateway.ts +43 -76
  83. package/src/Cloudflare/Bridge.ts +2 -0
  84. package/src/Cloudflare/Container/ContainerApplication.ts +1 -1
  85. package/src/Cloudflare/Container/StartContainer.ts +10 -3
  86. package/src/Cloudflare/Local/Sidecar.ts +50 -21
  87. package/src/Cloudflare/Local/SidecarHandlers.ts +395 -91
  88. package/src/Cloudflare/Local/SidecarServer.ts +12 -4
  89. package/src/Cloudflare/StateStore/Api.ts +1 -1
  90. package/src/Cloudflare/Workers/HttpServer.ts +5 -37
  91. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +18 -111
  92. package/src/Cloudflare/Workers/Vite.ts +114 -0
  93. package/src/Cloudflare/Workers/Worker.ts +27 -109
  94. package/src/Cloudflare/Workers/WorkerBundle.ts +7 -7
  95. package/src/Diff.ts +29 -8
  96. package/src/Http.ts +14 -10
  97. package/src/Sidecar/Lock.ts +30 -154
  98. package/src/Sidecar/RpcHandler.ts +67 -40
  99. package/src/Util/PlatformServices.ts +0 -2
@@ -1,11 +1,8 @@
1
+ import * as lockfile from "@alchemy.run/node-utils";
1
2
  import * as Context from "effect/Context";
2
- import * as Duration from "effect/Duration";
3
3
  import * as Effect from "effect/Effect";
4
- import * as Fiber from "effect/Fiber";
5
- import * as FileSystem from "effect/FileSystem";
4
+ import type * as Fiber from "effect/Fiber";
6
5
  import * as Layer from "effect/Layer";
7
- import * as Option from "effect/Option";
8
- import * as Schedule from "effect/Schedule";
9
6
  import * as Schema from "effect/Schema";
10
7
  import type * as Scope from "effect/Scope";
11
8
  import * as RpcPaths from "./RpcPaths.ts";
@@ -13,13 +10,7 @@ import * as RpcPaths from "./RpcPaths.ts";
13
10
  export class LockError extends Schema.TaggedErrorClass<LockError>()(
14
11
  "LockError",
15
12
  {
16
- reason: Schema.Literals([
17
- "Cancelled",
18
- "Conflict",
19
- "Invalid",
20
- "Timeout",
21
- "PlatformError",
22
- ]),
13
+ reason: Schema.Literals(["Conflict", "PlatformError"]),
23
14
  message: Schema.String,
24
15
  cause: Schema.optional(Schema.DefectWithStack),
25
16
  },
@@ -30,161 +21,46 @@ export class Lock extends Context.Service<
30
21
  {
31
22
  readonly check: Effect.Effect<boolean>;
32
23
  readonly acquire: Effect.Effect<
33
- Fiber.Fiber<number, LockError>,
24
+ Fiber.Fiber<never, LockError>,
34
25
  LockError,
35
26
  Scope.Scope
36
27
  >;
37
28
  }
38
29
  >()("Lock") {}
39
30
 
40
- const LOCK_TTL = Duration.seconds(10);
41
- const TOUCH_INTERVAL = Duration.seconds(1);
31
+ const STALE_MS = 10_000;
32
+ const UPDATE_MS = 1_000;
42
33
 
43
34
  const make = Effect.gen(function* () {
44
- const fs = yield* FileSystem.FileSystem;
45
35
  const paths = yield* RpcPaths.RpcPaths;
46
36
 
47
- const readLockFile = fs.readFileString(paths.lock).pipe(
48
- Effect.mapError(
49
- (e) =>
50
- new LockError({
51
- reason: "PlatformError",
52
- message:
53
- e.reason._tag === "NotFound"
54
- ? "Lock file not found"
55
- : "Failed to read lock file",
56
- cause: e,
57
- }),
58
- ),
59
- Effect.flatMap((text) => {
60
- const pid = Number.parseInt(text);
61
- return Number.isNaN(pid)
62
- ? Effect.fail(
63
- new LockError({
64
- reason: "Invalid",
65
- message: "Lock file is invalid",
66
- }),
67
- )
68
- : Effect.succeed(pid);
69
- }),
70
- );
71
-
72
- const isLockProcessAlive = readLockFile.pipe(
73
- Effect.flatMap((pid) =>
74
- Effect.sync(() => {
75
- try {
76
- process.kill(pid, 0);
77
- return true;
78
- } catch {
79
- return false;
80
- }
37
+ const check = Effect.tryPromise(() =>
38
+ lockfile.check(paths.lock, { stale: STALE_MS, realpath: false }),
39
+ ).pipe(Effect.orElseSucceed(() => false));
40
+
41
+ const acquire = Effect.gen(function* () {
42
+ yield* Effect.acquireRelease(
43
+ Effect.tryPromise({
44
+ try: () =>
45
+ lockfile.lock(paths.lock, {
46
+ stale: STALE_MS,
47
+ update: UPDATE_MS,
48
+ retries: 0,
49
+ realpath: false,
50
+ }),
51
+ catch: (cause) =>
52
+ new LockError({
53
+ reason: "Conflict",
54
+ message: "Lock already held by another process",
55
+ cause,
56
+ }),
81
57
  }),
82
- ),
83
- Effect.orElseSucceed(() => false),
84
- );
85
-
86
- const isLockFileStale = fs.stat(paths.lock).pipe(
87
- Effect.map((stat) => {
88
- const mtime = Option.getOrElse(stat.mtime, () => new Date(0));
89
- return mtime.getTime() < Date.now() - Duration.toMillis(LOCK_TTL);
90
- }),
91
- Effect.orElseSucceed(() => true),
92
- );
93
-
94
- const isLockValid = Effect.zipWith(
95
- isLockProcessAlive,
96
- isLockFileStale,
97
- (alive, stale) => alive && !stale,
98
- {
99
- concurrent: true,
100
- },
101
- );
102
-
103
- const makeLockFile = fs
104
- .writeFileString(paths.lock, process.pid.toString(), { flag: "wx" })
105
- .pipe(
106
- Effect.mapError((e) =>
107
- e.reason._tag === "AlreadyExists"
108
- ? new LockError({
109
- reason: "Conflict",
110
- message: "Lock already held by another process",
111
- })
112
- : new LockError({
113
- reason: "PlatformError",
114
- message: "Failed to create lock file",
115
- cause: e,
116
- }),
117
- ),
58
+ (release) => Effect.promise(() => release().catch(() => {})),
118
59
  );
60
+ return yield* Effect.never;
61
+ }).pipe(Effect.forkScoped);
119
62
 
120
- const assertOwnLock = readLockFile.pipe(
121
- Effect.flatMap((pid) =>
122
- pid === process.pid
123
- ? Effect.void
124
- : Effect.fail(
125
- new LockError({
126
- reason: "Conflict",
127
- message: "Lock not held by this process",
128
- }),
129
- ),
130
- ),
131
- );
132
-
133
- const removeLock = fs.remove(paths.lock, { force: true }).pipe(
134
- Effect.mapError(
135
- (e) =>
136
- new LockError({
137
- reason: "PlatformError",
138
- message: "Failed to remove lock file",
139
- cause: e,
140
- }),
141
- ),
142
- );
143
-
144
- const releaseLock = assertOwnLock.pipe(Effect.andThen(() => removeLock));
145
-
146
- const acquireLock = makeLockFile.pipe(
147
- Effect.catchIf(
148
- (e) => e.reason === "Conflict",
149
- (e) =>
150
- isLockValid.pipe(
151
- Effect.flatMap((valid) =>
152
- valid
153
- ? Effect.fail(e)
154
- : removeLock.pipe(Effect.andThen(() => makeLockFile)),
155
- ),
156
- ),
157
- ),
158
- );
159
-
160
- const touchLock = assertOwnLock.pipe(
161
- Effect.flatMap(() => {
162
- const now = Date.now();
163
- return fs.utimes(paths.lock, now, now).pipe(
164
- Effect.mapError(
165
- (e) =>
166
- new LockError({
167
- reason: "PlatformError",
168
- message: "Failed to update lock file",
169
- cause: e,
170
- }),
171
- ),
172
- );
173
- }),
174
- );
175
-
176
- return Lock.of({
177
- check: isLockValid,
178
- acquire: acquireLock.pipe(
179
- Effect.flatMap(() =>
180
- touchLock.pipe(
181
- Effect.repeat(Schedule.spaced(TOUCH_INTERVAL)),
182
- Effect.ensuring(releaseLock.pipe(Effect.ignore)),
183
- Effect.forkScoped,
184
- ),
185
- ),
186
- ),
187
- });
63
+ return Lock.of({ check, acquire });
188
64
  });
189
65
 
190
66
  export const LockLive = Layer.effect(Lock, make);
@@ -2,14 +2,18 @@ import type { RpcCompatible } from "capnweb";
2
2
  import * as Cause from "effect/Cause";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Exit from "effect/Exit";
5
+ import { flow } from "effect/Function";
6
+ import * as Redacted from "effect/Redacted";
5
7
  import * as Schema from "effect/Schema";
6
8
 
7
9
  type RpcHandler<Args extends Array<any>, Success, Error> = (
8
10
  ...args: Args
9
11
  ) => Effect.Effect<Success, Error>;
10
12
 
11
- type SerializedRpcHandler<Args extends Array<any>, Success, Error> = (
12
- ...args: Args
13
+ type SerializedArgs<T> = string & { __serializedArgs: T };
14
+
15
+ type SerializedRpcHandler<Args, Success, Error> = (
16
+ args: SerializedArgs<Args>,
13
17
  ) => Promise<SerializedExit<RpcCompatible<Success>, RpcCompatible<Error>>>;
14
18
 
15
19
  type SerializedCause<Error> =
@@ -60,45 +64,59 @@ export type RpcHandlerDecoders<T extends RpcHandlers> = {
60
64
  : never;
61
65
  };
62
66
 
63
- const serializeRpcHandler =
64
- <Args extends Array<any>, Success, Error>(
65
- handler: RpcHandler<Args, Success, Error>,
66
- schema: RpcHandlerEncoder<Success, Error>,
67
- ): SerializedRpcHandler<Args, Success, Error> =>
68
- (...args) =>
69
- handler(...args).pipe(
70
- Effect.exit,
71
- Effect.map((exit): SerializedExit<Success, Error> => {
72
- if (exit._tag === "Success") {
73
- return {
74
- _tag: "Success",
75
- value: Schema.encodeSync(schema.success)(exit.value),
76
- } as const;
77
- }
78
- return {
79
- _tag: "Failure",
80
- cause: exit.cause.reasons.map((reason): SerializedCause<Error> => {
81
- switch (reason._tag) {
82
- case "Fail":
83
- return {
84
- _tag: "Fail",
85
- error: Schema.encodeSync(schema.error)(reason.error),
86
- };
87
- case "Die":
88
- return {
89
- _tag: "Die",
90
- defect: Schema.encodeSync(Schema.DefectWithStack)(
91
- reason.defect,
92
- ),
93
- };
94
- case "Interrupt":
95
- return { _tag: "Interrupt", fiberId: reason.fiberId };
67
+ const serializeRpcHandler = <Args extends Array<any>, Success, Error>(
68
+ handler: RpcHandler<Args, Success, Error>,
69
+ schema: RpcHandlerEncoder<Success, Error>,
70
+ ): SerializedRpcHandler<Args, Success, Error> =>
71
+ flow(
72
+ (args) =>
73
+ Effect.sync(
74
+ () =>
75
+ JSON.parse(args, (_, value) => {
76
+ if (
77
+ typeof value === "object" &&
78
+ value !== null &&
79
+ "_tag" in value &&
80
+ value._tag === "Redacted"
81
+ ) {
82
+ return Redacted.make(value.value);
96
83
  }
97
- }),
84
+ return value;
85
+ }) as Args,
86
+ ),
87
+ Effect.flatMap((args) => handler(...args)),
88
+ Effect.exit,
89
+ Effect.map((exit): SerializedExit<Success, Error> => {
90
+ if (exit._tag === "Success") {
91
+ return {
92
+ _tag: "Success",
93
+ value: Schema.encodeSync(schema.success)(exit.value),
98
94
  } as const;
99
- }),
100
- Effect.runPromise,
101
- );
95
+ }
96
+ return {
97
+ _tag: "Failure",
98
+ cause: exit.cause.reasons.map((reason): SerializedCause<Error> => {
99
+ switch (reason._tag) {
100
+ case "Fail":
101
+ return {
102
+ _tag: "Fail",
103
+ error: Schema.encodeSync(schema.error)(reason.error),
104
+ };
105
+ case "Die":
106
+ return {
107
+ _tag: "Die",
108
+ defect: Schema.encodeSync(Schema.DefectWithStack)(
109
+ reason.defect,
110
+ ),
111
+ };
112
+ case "Interrupt":
113
+ return { _tag: "Interrupt", fiberId: reason.fiberId };
114
+ }
115
+ }),
116
+ } as const;
117
+ }),
118
+ Effect.runPromise,
119
+ );
102
120
 
103
121
  export const serializeRpcHandlers = <T extends RpcHandlers>(
104
122
  handlers: T,
@@ -119,7 +137,16 @@ const deserializeRpcHandler =
119
137
  (...args) =>
120
138
  Effect.promise(async () => {
121
139
  try {
122
- return await handler(...args);
140
+ const serializedArgs = JSON.stringify(args, (_, value) => {
141
+ if (Redacted.isRedacted(value)) {
142
+ return {
143
+ _tag: "Redacted",
144
+ value: Redacted.value(value),
145
+ };
146
+ }
147
+ return value;
148
+ }) as SerializedArgs<Args>;
149
+ return await handler(serializedArgs);
123
150
  } catch (error) {
124
151
  console.error("Error calling handler", error);
125
152
  throw error;
@@ -9,7 +9,6 @@ import type { HttpServer } from "effect/unstable/http/HttpServer";
9
9
  import type { ServeError } from "effect/unstable/http/HttpServerError";
10
10
  import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
11
11
  import type { WebSocketConstructor } from "effect/unstable/socket/Socket";
12
- import { installLocalhostDns } from "./LocalhostDns.ts";
13
12
 
14
13
  const isBun = typeof Bun !== "undefined";
15
14
 
@@ -53,7 +52,6 @@ export const runMain = <E, A>(
53
52
  readonly teardown?: Teardown | undefined;
54
53
  },
55
54
  ): void => {
56
- installLocalhostDns();
57
55
  if (isBun) {
58
56
  void import("@effect/platform-bun/BunRuntime").then((BunRuntime) =>
59
57
  BunRuntime.runMain(effect, options),