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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "2.0.0-beta.39",
3
+ "version": "2.0.0-beta.40",
4
4
  "homepage": "https://alchemy.run",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Sam Goodwin <sam@alchemy.run>",
@@ -130,6 +130,12 @@
130
130
  "worker": "./src/Cloudflare/index.ts",
131
131
  "import": "./lib/Cloudflare/index.js"
132
132
  },
133
+ "./Cloudflare/Bridge": {
134
+ "types": "./lib/Cloudflare/Bridge.d.ts",
135
+ "bun": "./src/Cloudflare/Bridge.ts",
136
+ "worker": "./src/Cloudflare/Bridge.ts",
137
+ "import": "./lib/Cloudflare/Bridge.js"
138
+ },
133
139
  "./Cloudflare/Live": {
134
140
  "types": "./lib/Cloudflare/Live.d.ts",
135
141
  "bun": "./src/Cloudflare/Live.ts",
@@ -270,17 +276,17 @@
270
276
  "access": "public"
271
277
  },
272
278
  "dependencies": {
279
+ "@alchemy.run/node-utils": "0.0.2",
273
280
  "@aws-sdk/credential-providers": "^3.0.0",
274
281
  "@clack/prompts": "^0.11.0",
275
- "@distilled.cloud/aws": "^0.20.1",
276
- "@distilled.cloud/axiom": "^0.20.1",
277
- "@distilled.cloud/cloudflare": "^0.20.1",
278
- "@distilled.cloud/cloudflare-rolldown-plugin": "0.3.0",
279
- "@distilled.cloud/cloudflare-runtime": "0.3.2",
280
- "@distilled.cloud/cloudflare-vite-plugin": "0.2.0",
281
- "@distilled.cloud/core": "^0.20.1",
282
- "@distilled.cloud/neon": "^0.20.1",
283
- "pg": "^8.13.0",
282
+ "@distilled.cloud/aws": "^0.21.0",
283
+ "@distilled.cloud/axiom": "^0.21.0",
284
+ "@distilled.cloud/cloudflare": "^0.21.0",
285
+ "@distilled.cloud/cloudflare-rolldown-plugin": "0.5.4",
286
+ "@distilled.cloud/cloudflare-runtime": "0.5.4",
287
+ "@distilled.cloud/cloudflare-vite-plugin": "0.5.4",
288
+ "@distilled.cloud/core": "^0.21.0",
289
+ "@distilled.cloud/neon": "^0.21.0",
284
290
  "@effect/vitest": ">=4.0.0-beta.66 || >=4.0.0",
285
291
  "@libsql/client": "^0.17.0",
286
292
  "@octokit/rest": "^22.0.1",
@@ -298,8 +304,8 @@
298
304
  "libsodium-wrappers": "^0.8.3",
299
305
  "magic-string": "^0.30.21",
300
306
  "pathe": "^2.0.3",
307
+ "pg": "^8.13.0",
301
308
  "picomatch": "^4.0.4",
302
- "proper-lockfile": "^4.1.2",
303
309
  "react": "^19.2.0",
304
310
  "rolldown": "1.0.0-rc.17",
305
311
  "sonda": "^0.11.1",
@@ -318,7 +324,6 @@
318
324
  "@types/bun": "latest",
319
325
  "@types/node": "latest",
320
326
  "@types/picomatch": "^4.0.0",
321
- "@types/proper-lockfile": "^4.1.4",
322
327
  "@types/react": "^19.2.2",
323
328
  "@types/ws": "^8.18.1",
324
329
  "better-auth": "^1.6.2",
@@ -231,7 +231,7 @@ import * as Layer from "effect/Layer";
231
231
  import * as Logger from "effect/Logger";
232
232
  import * as Region from "@distilled.cloud/aws/Region";
233
233
 
234
- import { ${handler} as handler } from "${importPath}";
234
+ import { ${handler} as handler } from ${JSON.stringify(importPath)};
235
235
 
236
236
  const platform = Layer.mergeAll(
237
237
  NodeServices.layer,
@@ -492,7 +492,7 @@ import * as Layer from "effect/Layer";
492
492
  import * as Logger from "effect/Logger";
493
493
  import * as Region from "@distilled.cloud/aws/Region";
494
494
 
495
- import { ${handler} as handler } from "${importPath}";
495
+ import { ${handler} as handler } from ${JSON.stringify(importPath)};
496
496
 
497
497
  const platform = Layer.mergeAll(
498
498
  NodeServices.layer,
@@ -679,7 +679,7 @@ import * as Region from "@distilled.cloud/aws/Region";
679
679
  import * as Context from "effect/Context";
680
680
  import { MinimumLogLevel } from "effect/References";
681
681
 
682
- import entrypoint from "${importPath}";
682
+ import entrypoint from ${JSON.stringify(importPath)};
683
683
 
684
684
  const tag = Context.Service("${Self.key}")
685
685
  const layer = makeEntrypointLayer(tag, entrypoint);
package/src/Auth/Lock.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import * as lockfile from "@alchemy.run/node-utils";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as fs from "node:fs/promises";
3
4
  import * as path from "pathe";
4
- import lockfile from "proper-lockfile";
5
5
  import { rootDir } from "./Profile.ts";
6
6
 
7
7
  const lockDir = path.join(rootDir, "lock");
@@ -11,9 +11,10 @@ const lockDir = path.join(rootDir, "lock");
11
11
  * critical section concurrently for the same `key`, both within this
12
12
  * process and across other processes on the same machine.
13
13
  *
14
- * Uses `proper-lockfile` for both: it tracks in-process holders by path
15
- * (so same-process callers wait via `retries`) and uses an OS file lock
16
- * for cross-process coordination, with stale-lock detection at 60s.
14
+ * Uses `@alchemy.run/node-utils` lockfile for both: it tracks in-process
15
+ * holders by path (so same-process callers wait via `retries`) and uses
16
+ * an OS file lock for cross-process coordination, with stale-lock
17
+ * detection at 60s.
17
18
  */
18
19
  export const withLock = <A, E, R>(
19
20
  key: string,
@@ -23,10 +24,10 @@ export const withLock = <A, E, R>(
23
24
  return Effect.acquireUseRelease(
24
25
  Effect.promise(async () => {
25
26
  await fs.mkdir(lockDir, { recursive: true });
26
- await fs.writeFile(lockPath, "", { flag: "a" });
27
27
  return await lockfile.lock(lockPath, {
28
28
  retries: { retries: 600, minTimeout: 50, maxTimeout: 50 },
29
29
  stale: 5_000,
30
+ realpath: false,
30
31
  });
31
32
  }),
32
33
  () => effect,
@@ -24,7 +24,8 @@ export type ChartKind =
24
24
  | "Statistic"
25
25
  | "Heatmap"
26
26
  | "LogStream"
27
- | "Note";
27
+ | "Note"
28
+ | "SmartFilter";
28
29
 
29
30
  /**
30
31
  * Minimum chart payload Axiom's `POST /v2/dashboards` accepts.
@@ -67,6 +68,58 @@ export interface NoteChart extends BaseChart {
67
68
  readonly type: "Note";
68
69
  }
69
70
 
71
+ /**
72
+ * A dashboard filter bar (Axiom calls it `SmartFilter` on the wire,
73
+ * `Filter bar` in the UI). Holds one or more filters whose IDs can
74
+ * be referenced from other charts' APL via
75
+ * `declare query_parameters (<filter-id>:string = "")`.
76
+ *
77
+ * Unlike data charts, `SmartFilter` has no top-level `query` field —
78
+ * each filter inside `filters` may carry its own option-source APL.
79
+ */
80
+ export interface SmartFilterChart {
81
+ readonly id: string;
82
+ readonly type: "SmartFilter";
83
+ /** Optional display name for the filter bar element. */
84
+ readonly name?: string;
85
+ readonly filters: readonly SmartFilter[];
86
+ }
87
+
88
+ /**
89
+ * One filter inside a {@link SmartFilterChart}. Either a free-text
90
+ * `search` filter or a dropdown `select` filter. Select filters draw
91
+ * their options from either an inline `list` or an APL `query`.
92
+ */
93
+ export type SmartFilter = SmartFilterSearch | SmartFilterSelect;
94
+
95
+ export interface SmartFilterSearch {
96
+ readonly id: string;
97
+ readonly type: "search";
98
+ readonly name?: string;
99
+ readonly active?: boolean;
100
+ }
101
+
102
+ export interface SmartFilterSelect {
103
+ readonly id: string;
104
+ readonly type: "select";
105
+ readonly name?: string;
106
+ readonly active?: boolean;
107
+ /** `"list"` for inline `options`, `"apl"` for a query-driven dropdown. */
108
+ readonly selectType?: "list" | "apl";
109
+ /**
110
+ * APL query that returns rows shaped `{ key, value }`. Axiom shows
111
+ * `key` in the dropdown and substitutes `value` into chart queries.
112
+ */
113
+ readonly apl?: { readonly apl: string };
114
+ /** Inline key/value options when `selectType === "list"`. */
115
+ readonly options?: readonly {
116
+ readonly id?: string;
117
+ readonly key: string;
118
+ readonly value: string;
119
+ readonly default?: boolean;
120
+ }[];
121
+ }
122
+
70
123
  export type Chart =
71
124
  | TimeSeriesChart
72
125
  | TableChart
@@ -74,7 +127,8 @@ export type Chart =
74
127
  | StatisticChart
75
128
  | HeatmapChart
76
129
  | LogStreamChart
77
- | NoteChart;
130
+ | NoteChart
131
+ | SmartFilterChart;
78
132
 
79
133
  /**
80
134
  * Mirrors Axiom's `CreateDashboardInput.dashboard.layout` cell schema.
@@ -186,34 +186,58 @@ export const watch = (
186
186
  ),
187
187
  );
188
188
 
189
- const ENTRY_MODULE_ID = "virtual:alchemy-entry";
190
- const ENTRY_MODULE_REGEX = new RegExp(`^${ENTRY_MODULE_ID}$`);
189
+ const ENTRY_PREFIX = "\0virtual:alchemy-entry:";
190
+ // oxlint-disable-next-line no-control-regex
191
+ const ENTRY_REGEX = /^\0virtual:alchemy-entry:/;
191
192
 
192
193
  export const virtualEntryPlugin = Effect.gen(function* () {
193
194
  const path = yield* Path.Path;
195
+
196
+ const normalizeInput = (
197
+ input: rolldown.InputOption,
198
+ ): Record<string, string> => {
199
+ if (typeof input === "string") {
200
+ return { [path.parse(input).name || "index"]: input };
201
+ } else if (Array.isArray(input)) {
202
+ return Object.fromEntries(input.map((p) => [path.parse(p).name, p]));
203
+ } else {
204
+ return input;
205
+ }
206
+ };
207
+
194
208
  return (content: (importPath: string) => string) => {
195
- let importPath: string | undefined;
209
+ const entries = new Map<string, string>();
196
210
  return {
197
211
  name: "alchemy:virtual-entry",
198
- options(inputOptions) {
199
- assert(
200
- typeof inputOptions.input === "string",
201
- "input must be a string",
202
- );
203
- importPath = `./${path.relative(inputOptions.cwd ?? process.cwd(), inputOptions.input).replaceAll("\\", "/")}`;
204
- inputOptions.input = ENTRY_MODULE_ID;
212
+ options: {
213
+ order: "pre",
214
+ handler(inputOptions) {
215
+ const cwd = inputOptions.cwd ?? process.cwd();
216
+ const input = normalizeInput(inputOptions.input ?? {});
217
+ inputOptions.input = Object.fromEntries(
218
+ Object.entries(input).map(([name, p]) => {
219
+ const id = `${ENTRY_PREFIX}${name}`;
220
+ entries.set(id, path.resolve(cwd, p));
221
+ return [name, id];
222
+ }),
223
+ );
224
+ },
205
225
  },
206
226
  resolveId: {
207
- filter: { id: ENTRY_MODULE_REGEX },
208
- handler() {
209
- return { id: ENTRY_MODULE_ID };
227
+ filter: { id: ENTRY_REGEX },
228
+ handler(id) {
229
+ return entries.has(id) ? { id } : null;
210
230
  },
211
231
  },
212
232
  load: {
213
- filter: { id: ENTRY_MODULE_REGEX },
214
- handler() {
215
- assert(importPath !== undefined, "importPath must be defined");
216
- return { code: content(importPath), moduleType: "ts" };
233
+ filter: { id: ENTRY_REGEX },
234
+ handler(id) {
235
+ const entry = entries.get(id);
236
+ assert(entry !== undefined, `unknown alchemy entry: ${id}`);
237
+ return {
238
+ code: content(entry),
239
+ moduleType: "ts",
240
+ };
217
241
  },
218
242
  },
219
243
  } satisfies rolldown.Plugin;
@@ -200,17 +200,17 @@ export type AiGateway = Resource<
200
200
  rateLimitingInterval: number | null;
201
201
  rateLimitingLimit: number | null;
202
202
  rateLimitingTechnique: AiGatewayRateLimitingTechnique;
203
- authentication: boolean | undefined;
204
- dlp: AiGatewayDlp | null | undefined;
205
- isDefault: boolean | undefined;
206
- logManagement: number | null | undefined;
207
- logManagementStrategy: AiGatewayLogManagementStrategy | null | undefined;
208
- logpush: boolean | undefined;
209
- logpushPublicKey: string | null | undefined;
210
- otel: AiGatewayOtel[] | null | undefined;
211
- storeId: string | null | undefined;
212
- stripe: AiGatewayStripe | null | undefined;
213
- zdr: boolean | undefined;
203
+ authentication: boolean;
204
+ dlp: AiGatewayDlp | undefined;
205
+ isDefault: boolean;
206
+ logManagement: number;
207
+ logManagementStrategy: AiGatewayLogManagementStrategy;
208
+ logpush: boolean;
209
+ logpushPublicKey: string | undefined;
210
+ otel: AiGatewayOtel[] | undefined;
211
+ storeId: string;
212
+ stripe: AiGatewayStripe | undefined;
213
+ zdr: boolean;
214
214
  },
215
215
  never,
216
216
  Providers
@@ -301,17 +301,21 @@ export const AiGatewayProvider = () =>
301
301
  rateLimitingInterval: props?.rateLimitingInterval ?? null,
302
302
  rateLimitingLimit: props?.rateLimitingLimit ?? null,
303
303
  rateLimitingTechnique: props?.rateLimitingTechnique ?? "fixed",
304
- authentication: props?.authentication,
305
- dlp: props?.dlp,
306
- isDefault: props?.isDefault,
307
- logManagement: props?.logManagement,
308
- logManagementStrategy: props?.logManagementStrategy,
309
- logpush: props?.logpush,
310
- logpushPublicKey: props?.logpushPublicKey,
311
- otel: props?.otel,
312
- storeId: props?.storeId,
313
- stripe: props?.stripe,
314
- zdr: props?.zdr,
304
+ // Defaults align with what Cloudflare's API returns for an
305
+ // unconfigured gateway, so the reconciler converges to noop
306
+ // when the user didn't explicitly set the field.
307
+ authentication: props?.authentication ?? false,
308
+ dlp: props?.dlp ?? undefined,
309
+ isDefault: props?.isDefault ?? false,
310
+ logManagement: props?.logManagement ?? 100_000,
311
+ logManagementStrategy:
312
+ props?.logManagementStrategy ?? "STOP_INSERTING",
313
+ logpush: props?.logpush ?? false,
314
+ logpushPublicKey: props?.logpushPublicKey ?? undefined,
315
+ otel: props?.otel ?? undefined,
316
+ storeId: props?.storeId ?? "",
317
+ stripe: props?.stripe ?? undefined,
318
+ zdr: props?.zdr ?? false,
315
319
  };
316
320
  });
317
321
 
@@ -334,17 +338,18 @@ export const AiGatewayProvider = () =>
334
338
  rateLimitingInterval: nullIfZero(gateway.rateLimitingInterval),
335
339
  rateLimitingLimit: nullIfZero(gateway.rateLimitingLimit),
336
340
  rateLimitingTechnique: gateway.rateLimitingTechnique ?? "fixed",
337
- authentication: gateway.authentication ?? undefined,
338
- dlp: gateway.dlp,
339
- isDefault: gateway.isDefault ?? undefined,
340
- logManagement: gateway.logManagement,
341
- logManagementStrategy: gateway.logManagementStrategy,
342
- logpush: gateway.logpush ?? undefined,
343
- logpushPublicKey: gateway.logpushPublicKey,
344
- otel: gateway.otel,
345
- storeId: gateway.storeId,
346
- stripe: gateway.stripe,
347
- zdr: gateway.zdr ?? undefined,
341
+ authentication: gateway.authentication ?? false,
342
+ dlp: gateway.dlp ?? undefined,
343
+ isDefault: gateway.isDefault ?? false,
344
+ logManagement: gateway.logManagement ?? 100_000,
345
+ logManagementStrategy:
346
+ gateway.logManagementStrategy ?? "STOP_INSERTING",
347
+ logpush: gateway.logpush ?? false,
348
+ logpushPublicKey: gateway.logpushPublicKey ?? undefined,
349
+ otel: gateway.otel ?? undefined,
350
+ storeId: gateway.storeId ?? "",
351
+ stripe: gateway.stripe ?? undefined,
352
+ zdr: gateway.zdr ?? false,
348
353
  });
349
354
 
350
355
  const mutable = (gateway: AiGateway["Attributes"]) => ({
@@ -355,7 +360,7 @@ export const AiGatewayProvider = () =>
355
360
  rateLimitingLimit: gateway.rateLimitingLimit,
356
361
  rateLimitingTechnique: gateway.rateLimitingTechnique,
357
362
  authentication: gateway.authentication,
358
- dlp: gateway.dlp ?? undefined,
363
+ dlp: gateway.dlp,
359
364
  isDefault: gateway.isDefault,
360
365
  logManagement: gateway.logManagement,
361
366
  logManagementStrategy: gateway.logManagementStrategy,
@@ -434,48 +439,10 @@ export const AiGatewayProvider = () =>
434
439
  return { action: "replace" } as const;
435
440
  }
436
441
 
437
- const oldMutable = output
438
- ? mutable(output)
439
- : yield* desired(id, olds).pipe(
440
- Effect.map((old) => ({
441
- cacheInvalidateOnUpdate: old.cacheInvalidateOnUpdate,
442
- cacheTtl: old.cacheTtl,
443
- collectLogs: old.collectLogs,
444
- rateLimitingInterval: old.rateLimitingInterval,
445
- rateLimitingLimit: old.rateLimitingLimit,
446
- rateLimitingTechnique: old.rateLimitingTechnique,
447
- authentication: old.authentication,
448
- dlp: old.dlp,
449
- isDefault: old.isDefault,
450
- logManagement: old.logManagement,
451
- logManagementStrategy: old.logManagementStrategy,
452
- logpush: old.logpush,
453
- logpushPublicKey: old.logpushPublicKey,
454
- otel: old.otel,
455
- storeId: old.storeId,
456
- stripe: old.stripe,
457
- zdr: old.zdr,
458
- })),
459
- );
460
- const nextMutable = {
461
- cacheInvalidateOnUpdate: next.cacheInvalidateOnUpdate,
462
- cacheTtl: next.cacheTtl,
463
- collectLogs: next.collectLogs,
464
- rateLimitingInterval: next.rateLimitingInterval,
465
- rateLimitingLimit: next.rateLimitingLimit,
466
- rateLimitingTechnique: next.rateLimitingTechnique,
467
- authentication: next.authentication,
468
- dlp: next.dlp,
469
- isDefault: next.isDefault,
470
- logManagement: next.logManagement,
471
- logManagementStrategy: next.logManagementStrategy,
472
- logpush: next.logpush,
473
- logpushPublicKey: next.logpushPublicKey,
474
- otel: next.otel,
475
- storeId: next.storeId,
476
- stripe: next.stripe,
477
- zdr: next.zdr,
478
- };
442
+ const oldMutable = mutable(
443
+ output ?? ((yield* desired(id, olds)) as AiGateway["Attributes"]),
444
+ );
445
+ const nextMutable = mutable(next as AiGateway["Attributes"]);
479
446
  if (!deepEqual(oldMutable, nextMutable)) {
480
447
  return { action: "update" } as const;
481
448
  }
@@ -0,0 +1,2 @@
1
+ export * from "./Workers/InferEnv.ts";
2
+ export * from "./Workers/Rpc.ts";
@@ -540,7 +540,7 @@ import * as Logger from "effect/Logger";
540
540
  import * as Context from "effect/Context";
541
541
  import { MinimumLogLevel } from "effect/References";
542
542
 
543
- import ${handler === "default" ? "entrypoint" : `{ ${handler} as entrypoint }`} from "${importPath}";
543
+ import ${handler === "default" ? "entrypoint" : `{ ${handler} as entrypoint }`} from ${JSON.stringify(importPath)};
544
544
 
545
545
  const tag = Context.Service("${Self.key}")
546
546
  const layer = makeEntrypointLayer(tag, entrypoint);
@@ -7,7 +7,11 @@ import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
7
7
  import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
8
8
  import { type Fetcher } from "../Fetcher.ts";
9
9
  import { DurableObjectState } from "../Workers/DurableObjectState.ts";
10
- import { type Container, ContainerError } from "./Container.ts";
10
+ import {
11
+ type Container,
12
+ type ContainerStartupOptions,
13
+ ContainerError,
14
+ } from "./Container.ts";
11
15
 
12
16
  /**
13
17
  * Runs the Container in a Durable Object and monitors it, providing a durable fetch and RPC interface to it.
@@ -15,13 +19,16 @@ import { type Container, ContainerError } from "./Container.ts";
15
19
  export const start = Effect.fnUntraced(function* <
16
20
  Shape extends Container,
17
21
  Req = never,
18
- >(containerEff: Effect.Effect<Shape, never, Req | DurableObjectState>) {
22
+ >(
23
+ containerEff: Effect.Effect<Shape, never, Req | DurableObjectState>,
24
+ options?: ContainerStartupOptions,
25
+ ) {
19
26
  const container = yield* containerEff;
20
27
 
21
28
  const ensureRunning = Effect.gen(function* () {
22
29
  if (yield* container.running) return;
23
30
  yield* Effect.logInfo("Container not running, starting...");
24
- yield* container.start();
31
+ yield* container.start(options);
25
32
  yield* Effect.logInfo("Container started, launching monitor");
26
33
  yield* Effect.forkDetach(
27
34
  container.monitor().pipe(
@@ -1,48 +1,77 @@
1
1
  import { RuntimeError } from "@distilled.cloud/cloudflare-runtime/RuntimeError";
2
- import * as Worker from "@distilled.cloud/cloudflare-runtime/Worker";
3
2
  import * as Effect from "effect/Effect";
4
3
  import * as Schema from "effect/Schema";
5
4
  import { BundleError } from "../../Bundle/Bundle.ts";
5
+ import type { ResourceBinding } from "../../Resource.ts";
6
6
  import * as RpcClient from "../../Sidecar/RpcClient.ts";
7
7
  import { defineSchema } from "../../Sidecar/RpcHandler.ts";
8
- import type { WorkerBinding } from "../Workers/Worker.ts";
9
- import type { WorkerBundleOptions } from "../Workers/WorkerBundle.ts";
10
-
11
- export interface ServeOptions extends WorkerBundleOptions {
12
- name: string;
13
- bindings: WorkerBinding[];
14
- /**
15
- * Local-mode hyperdrive connection details, keyed by binding name.
16
- * Forwarded to the runtime as the worker's `hyperdrives` field.
17
- */
18
- hyperdrives?: Record<string, Worker.HyperdriveOrigin>;
19
- durableObjectNamespaces: Worker.DurableObjectNamespace[];
8
+ import type { Worker, WorkerProps } from "../Workers/Worker.ts";
9
+
10
+ export interface ReconcileOptions {
11
+ id: string;
12
+ props: WorkerProps;
13
+ bindings: ResourceBinding<Worker["Binding"]>[];
14
+ stack: { name: string; stage: string };
15
+ instanceId: string;
20
16
  }
21
17
 
18
+ export class ValidationError extends Schema.TaggedErrorClass<ValidationError>()(
19
+ "ValidationError",
20
+ {
21
+ message: Schema.String,
22
+ hint: Schema.optional(Schema.String),
23
+ value: Schema.Unknown,
24
+ },
25
+ ) {}
26
+
22
27
  export const ServeResult = Schema.Struct({
23
- name: Schema.String,
24
- address: Schema.String,
28
+ workerId: Schema.String,
29
+ workerName: Schema.String,
30
+ logpush: Schema.UndefinedOr(Schema.Boolean),
31
+ url: Schema.String,
32
+ tags: Schema.Array(Schema.String).pipe(Schema.mutable),
33
+ durableObjectNamespaces: Schema.Record(Schema.String, Schema.String),
34
+ domains: Schema.Array(
35
+ Schema.Struct({
36
+ hostname: Schema.String,
37
+ id: Schema.String,
38
+ zoneId: Schema.String,
39
+ }),
40
+ ).pipe(Schema.mutable),
41
+ crons: Schema.Array(Schema.String).pipe(Schema.mutable),
42
+ accountId: Schema.String,
25
43
  });
26
44
  export type ServeResult = typeof ServeResult.Type;
27
45
 
28
- export const ServeError = Schema.Union([RuntimeError, BundleError]);
46
+ export const ServeError = Schema.Union([
47
+ RuntimeError,
48
+ BundleError,
49
+ ValidationError,
50
+ ]);
29
51
  export type ServeError = typeof ServeError.Type;
30
52
 
53
+ export const DiffResult = Schema.Struct({
54
+ action: Schema.Literals(["update", "noop"]),
55
+ });
56
+ export type DiffResult = typeof DiffResult.Type;
57
+
31
58
  export const SidecarSchema = defineSchema<Sidecar["Service"]>({
32
- serve: {
59
+ diff: { success: DiffResult, error: Schema.Never },
60
+ reconcile: {
33
61
  success: ServeResult,
34
62
  error: ServeError,
35
63
  },
36
- stop: { success: Schema.Void, error: Schema.Never },
64
+ delete: { success: Schema.Void, error: Schema.Never },
37
65
  });
38
66
 
39
67
  export class Sidecar extends RpcClient.RpcClientService<
40
68
  Sidecar,
41
69
  {
42
- readonly serve: (
43
- options: ServeOptions,
70
+ readonly diff: (options: ReconcileOptions) => Effect.Effect<DiffResult>;
71
+ readonly reconcile: (
72
+ options: ReconcileOptions,
44
73
  ) => Effect.Effect<ServeResult, ServeError>;
45
- readonly stop: (name: string) => Effect.Effect<void>;
74
+ readonly delete: (id: string) => Effect.Effect<void>;
46
75
  }
47
76
  >()("Sidecar") {}
48
77