alchemy 2.0.0-beta.36 → 2.0.0-beta.37
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/exec.js +166 -43
- package/bin/exec.js.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +22 -2
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/Apply.d.ts.map +1 -1
- package/lib/Apply.js +6 -1
- package/lib/Apply.js.map +1 -1
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts +54 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js +17 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts +37 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js +45 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/index.d.ts +3 -0
- package/lib/Cloudflare/AnalyticsEngine/index.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/index.js +3 -0
- package/lib/Cloudflare/AnalyticsEngine/index.js.map +1 -0
- package/lib/Cloudflare/D1/D1Connection.d.ts +47 -3
- package/lib/Cloudflare/D1/D1Connection.d.ts.map +1 -1
- package/lib/Cloudflare/D1/D1Connection.js +62 -5
- package/lib/Cloudflare/D1/D1Connection.js.map +1 -1
- package/lib/Cloudflare/Local/SidecarHandlers.js +9 -1
- package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +4 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.js +16 -0
- package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +6 -1
- package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Api.js +23 -1
- package/lib/Cloudflare/StateStore/Api.js.map +1 -1
- package/lib/Cloudflare/StateStore/Store.d.ts +16 -1
- package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Store.js +22 -5
- package/lib/Cloudflare/StateStore/Store.js.map +1 -1
- package/lib/Cloudflare/Workers/CronEventSource.d.ts +35 -0
- package/lib/Cloudflare/Workers/CronEventSource.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/CronEventSource.js +55 -0
- package/lib/Cloudflare/Workers/CronEventSource.js.map +1 -0
- package/lib/Cloudflare/Workers/InferEnv.d.ts +37 -2
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +2 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Rpc.d.ts +76 -3
- package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Rpc.js +197 -13
- package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +22 -2
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +174 -43
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js +15 -5
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
- package/lib/Cloudflare/Workers/Workflow.d.ts +50 -47
- package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Workflow.js +41 -35
- package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
- package/lib/Cloudflare/Workers/index.d.ts +1 -0
- package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/index.js +1 -0
- package/lib/Cloudflare/Workers/index.js.map +1 -1
- package/lib/Cloudflare/index.d.ts +1 -0
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +1 -0
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/Deploy.d.ts +4 -1
- package/lib/Deploy.d.ts.map +1 -1
- package/lib/Deploy.js +2 -2
- package/lib/Deploy.js.map +1 -1
- package/lib/Destroy.d.ts +4 -1
- package/lib/Destroy.d.ts.map +1 -1
- package/lib/Destroy.js +2 -2
- package/lib/Destroy.js.map +1 -1
- package/lib/Output.d.ts +45 -5
- package/lib/Output.d.ts.map +1 -1
- package/lib/Output.js +100 -4
- package/lib/Output.js.map +1 -1
- package/lib/Plan.d.ts.map +1 -1
- package/lib/Plan.js +93 -12
- package/lib/Plan.js.map +1 -1
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js +16 -3
- package/lib/Platform.js.map +1 -1
- package/lib/Ref.d.ts +4 -1
- package/lib/Ref.d.ts.map +1 -1
- package/lib/Ref.js +1 -1
- package/lib/Ref.js.map +1 -1
- package/lib/Resource.d.ts +8 -0
- package/lib/Resource.d.ts.map +1 -1
- package/lib/Resource.js +10 -0
- package/lib/Resource.js.map +1 -1
- package/lib/RuntimeContext.d.ts +3 -1
- package/lib/RuntimeContext.d.ts.map +1 -1
- package/lib/RuntimeContext.js.map +1 -1
- package/lib/Secret.d.ts +50 -0
- package/lib/Secret.d.ts.map +1 -0
- package/lib/Secret.js +23 -0
- package/lib/Secret.js.map +1 -0
- package/lib/Serverless/Function.d.ts +9 -1
- package/lib/Serverless/Function.d.ts.map +1 -1
- package/lib/Stack.d.ts +14 -5
- package/lib/Stack.d.ts.map +1 -1
- package/lib/Stack.js +31 -19
- package/lib/Stack.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +15 -1
- package/lib/State/HttpStateApi.d.ts.map +1 -1
- package/lib/State/HttpStateApi.js +11 -0
- package/lib/State/HttpStateApi.js.map +1 -1
- package/lib/State/HttpStateStore.d.ts.map +1 -1
- package/lib/State/HttpStateStore.js +11 -0
- package/lib/State/HttpStateStore.js.map +1 -1
- package/lib/State/InMemoryState.d.ts +2 -2
- package/lib/State/InMemoryState.d.ts.map +1 -1
- package/lib/State/InMemoryState.js +12 -6
- package/lib/State/InMemoryState.js.map +1 -1
- package/lib/State/LocalState.d.ts.map +1 -1
- package/lib/State/LocalState.js +11 -2
- package/lib/State/LocalState.js.map +1 -1
- package/lib/State/State.d.ts +21 -0
- package/lib/State/State.d.ts.map +1 -1
- package/lib/Test/Bun.d.ts.map +1 -1
- package/lib/Test/Bun.js +30 -7
- package/lib/Test/Bun.js.map +1 -1
- package/lib/Test/Core.d.ts +17 -2
- package/lib/Test/Core.d.ts.map +1 -1
- package/lib/Test/Core.js +29 -11
- package/lib/Test/Core.js.map +1 -1
- package/lib/Test/Vitest.d.ts.map +1 -1
- package/lib/Test/Vitest.js +24 -5
- package/lib/Test/Vitest.js.map +1 -1
- package/lib/Util/LocalhostDns.d.ts +14 -10
- package/lib/Util/LocalhostDns.d.ts.map +1 -1
- package/lib/Util/LocalhostDns.js +27 -36
- package/lib/Util/LocalhostDns.js.map +1 -1
- package/lib/Util/effect.d.ts +7 -0
- package/lib/Util/effect.d.ts.map +1 -1
- package/lib/Util/effect.js +6 -0
- package/lib/Util/effect.js.map +1 -1
- package/lib/Variable.d.ts +29 -0
- package/lib/Variable.d.ts.map +1 -0
- package/lib/Variable.js +9 -0
- package/lib/Variable.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +6 -7
- package/src/AWS/Lambda/Function.ts +30 -2
- package/src/Apply.ts +8 -1
- package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.ts +82 -0
- package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.ts +106 -0
- package/src/Cloudflare/AnalyticsEngine/index.ts +2 -0
- package/src/Cloudflare/D1/D1Connection.ts +95 -12
- package/src/Cloudflare/Local/SidecarHandlers.ts +9 -1
- package/src/Cloudflare/Providers.ts +5 -0
- package/src/Cloudflare/R2/R2Bucket.ts +31 -0
- package/src/Cloudflare/StateStore/Api.ts +34 -1
- package/src/Cloudflare/StateStore/Store.ts +40 -10
- package/src/Cloudflare/Workers/CronEventSource.ts +89 -0
- package/src/Cloudflare/Workers/InferEnv.ts +94 -20
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +9 -1
- package/src/Cloudflare/Workers/Rpc.ts +227 -18
- package/src/Cloudflare/Workers/Worker.ts +277 -63
- package/src/Cloudflare/Workers/WorkerBundle.ts +15 -5
- package/src/Cloudflare/Workers/Workflow.ts +74 -70
- package/src/Cloudflare/Workers/index.ts +1 -0
- package/src/Cloudflare/index.ts +1 -0
- package/src/Deploy.ts +5 -1
- package/src/Destroy.ts +5 -1
- package/src/Output.ts +121 -12
- package/src/Plan.ts +101 -12
- package/src/Platform.ts +19 -4
- package/src/Ref.ts +10 -5
- package/src/Resource.ts +22 -0
- package/src/RuntimeContext.ts +1 -0
- package/src/Secret.ts +87 -0
- package/src/Serverless/Function.ts +10 -1
- package/src/Stack.ts +52 -29
- package/src/State/HttpStateApi.ts +21 -0
- package/src/State/HttpStateStore.ts +21 -0
- package/src/State/InMemoryState.ts +29 -8
- package/src/State/LocalState.ts +28 -4
- package/src/State/State.ts +21 -0
- package/src/Test/Bun.ts +43 -7
- package/src/Test/Core.ts +29 -8
- package/src/Test/Vitest.ts +33 -4
- package/src/Util/LocalhostDns.ts +30 -36
- package/src/Util/effect.ts +25 -0
- package/src/Variable.ts +44 -0
- package/src/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.37",
|
|
4
4
|
"homepage": "https://alchemy.run",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Sam Goodwin <sam@alchemy.run>",
|
|
@@ -275,14 +275,14 @@
|
|
|
275
275
|
"@ai-sdk/provider": "^3.0.8",
|
|
276
276
|
"@aws-sdk/credential-providers": "^3.0.0",
|
|
277
277
|
"@clack/prompts": "^0.11.0",
|
|
278
|
-
"@distilled.cloud/aws": "^0.19.
|
|
279
|
-
"@distilled.cloud/axiom": "^0.19.
|
|
280
|
-
"@distilled.cloud/cloudflare": "^0.19.
|
|
278
|
+
"@distilled.cloud/aws": "^0.19.1",
|
|
279
|
+
"@distilled.cloud/axiom": "^0.19.1",
|
|
280
|
+
"@distilled.cloud/cloudflare": "^0.19.1",
|
|
281
281
|
"@distilled.cloud/cloudflare-rolldown-plugin": "0.3.0",
|
|
282
282
|
"@distilled.cloud/cloudflare-runtime": "0.3.2",
|
|
283
283
|
"@distilled.cloud/cloudflare-vite-plugin": "0.2.0",
|
|
284
|
-
"@distilled.cloud/core": "^0.19.
|
|
285
|
-
"@distilled.cloud/neon": "^0.19.
|
|
284
|
+
"@distilled.cloud/core": "^0.19.1",
|
|
285
|
+
"@distilled.cloud/neon": "^0.19.1",
|
|
286
286
|
"pg": "^8.13.0",
|
|
287
287
|
"@effect/vitest": ">=4.0.0-beta.60 || >=4.0.0",
|
|
288
288
|
"@libsql/client": "^0.17.0",
|
|
@@ -291,7 +291,6 @@
|
|
|
291
291
|
"@smithy/shared-ini-file-loader": "^4.3.4",
|
|
292
292
|
"@smithy/types": "^4.8.1",
|
|
293
293
|
"@types/aws-lambda": "^8.10.152",
|
|
294
|
-
"@types/libsodium-wrappers": "^0.8.2",
|
|
295
294
|
"ai": "^6.0.62",
|
|
296
295
|
"aws4fetch": "^1.0.20",
|
|
297
296
|
"capnweb": "^0.6.1",
|
|
@@ -10,6 +10,7 @@ import * as Context from "effect/Context";
|
|
|
10
10
|
import * as Effect from "effect/Effect";
|
|
11
11
|
import * as FileSystem from "effect/FileSystem";
|
|
12
12
|
import * as Option from "effect/Option";
|
|
13
|
+
import * as Redacted from "effect/Redacted";
|
|
13
14
|
import * as Schedule from "effect/Schedule";
|
|
14
15
|
import * as Stream from "effect/Stream";
|
|
15
16
|
import type * as rolldown from "rolldown";
|
|
@@ -368,7 +369,21 @@ export const Function: Platform<
|
|
|
368
369
|
set: (id: string, output: Output.Output) =>
|
|
369
370
|
Effect.sync(() => {
|
|
370
371
|
const key = id.replaceAll(/[^a-zA-Z0-9]/g, "_");
|
|
371
|
-
|
|
372
|
+
// Preserve `Redacted`-ness across the Output → Lambda env var
|
|
373
|
+
// round-trip. `JSON.stringify(Redacted)` would emit the literal
|
|
374
|
+
// string `"<redacted>"` and lose the value, so secrets are
|
|
375
|
+
// serialized with a `{_tag: "Redacted", value: ...}` marker
|
|
376
|
+
// that the runtime `get` path detects and rebuilds.
|
|
377
|
+
env[key] = output.pipe(
|
|
378
|
+
Output.map((value) =>
|
|
379
|
+
Redacted.isRedacted(value)
|
|
380
|
+
? JSON.stringify({
|
|
381
|
+
_tag: "Redacted",
|
|
382
|
+
value: Redacted.value(value),
|
|
383
|
+
})
|
|
384
|
+
: JSON.stringify(value),
|
|
385
|
+
),
|
|
386
|
+
);
|
|
372
387
|
return key;
|
|
373
388
|
}),
|
|
374
389
|
get: <T>(key: string) =>
|
|
@@ -377,7 +392,20 @@ export const Function: Platform<
|
|
|
377
392
|
.pipe(
|
|
378
393
|
Effect.flatMap((val) =>
|
|
379
394
|
Effect.try({
|
|
380
|
-
try: () =>
|
|
395
|
+
try: () => {
|
|
396
|
+
const value = JSON.parse(val);
|
|
397
|
+
if (
|
|
398
|
+
value !== null &&
|
|
399
|
+
typeof value === "object" &&
|
|
400
|
+
(value as { _tag?: unknown })._tag === "Redacted" &&
|
|
401
|
+
"value" in (value as object)
|
|
402
|
+
) {
|
|
403
|
+
return Redacted.make(
|
|
404
|
+
(value as { value: unknown }).value,
|
|
405
|
+
) as unknown as T;
|
|
406
|
+
}
|
|
407
|
+
return value as T;
|
|
408
|
+
},
|
|
381
409
|
catch: () => val, // assume it's just a string
|
|
382
410
|
}),
|
|
383
411
|
),
|
package/src/Apply.ts
CHANGED
|
@@ -185,7 +185,14 @@ export const apply = <P extends Plan>(
|
|
|
185
185
|
const outputs = Object.fromEntries(
|
|
186
186
|
Object.entries(tracker).map(([fqn, t]) => [fqn, t.output]),
|
|
187
187
|
);
|
|
188
|
-
|
|
188
|
+
const resolved = yield* Output.evaluate(plan.output, outputs);
|
|
189
|
+
|
|
190
|
+
// Persist the stack's evaluated outputs so cross-stack references
|
|
191
|
+
// (`yield* OtherStack` / `OtherStack.stage.<name>` / `Output.stackRef`)
|
|
192
|
+
// can read them back out of the state store.
|
|
193
|
+
yield* state.setOutput({ stack: stackName, stage, value: resolved });
|
|
194
|
+
|
|
195
|
+
return resolved;
|
|
189
196
|
}).pipe(
|
|
190
197
|
ensureArtifactStore,
|
|
191
198
|
Effect.withSpan("apply", {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { AnalyticsEngineDatasetBinding } from "./AnalyticsEngineDatasetBinding.ts";
|
|
3
|
+
|
|
4
|
+
type AnalyticsEngineDatasetTypeId = typeof AnalyticsEngineDatasetTypeId;
|
|
5
|
+
const AnalyticsEngineDatasetTypeId =
|
|
6
|
+
"Cloudflare.AnalyticsEngineDataset" as const;
|
|
7
|
+
|
|
8
|
+
export type AnalyticsEngineDatasetProps = {
|
|
9
|
+
/**
|
|
10
|
+
* Dataset name. If omitted, the logical ID is used.
|
|
11
|
+
*/
|
|
12
|
+
dataset?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A Cloudflare Workers Analytics Engine dataset binding.
|
|
17
|
+
*
|
|
18
|
+
* Analytics Engine datasets are configured as Worker bindings. The binding
|
|
19
|
+
* exposes `writeDataPoint()` at runtime and does not require separate
|
|
20
|
+
* provisioning through the Cloudflare API.
|
|
21
|
+
*
|
|
22
|
+
* @resource
|
|
23
|
+
*
|
|
24
|
+
* @section Binding to a Worker
|
|
25
|
+
* @example Basic Analytics Engine binding
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const Analytics = yield* Cloudflare.AnalyticsEngineDataset("Analytics", {
|
|
28
|
+
* dataset: "app-events",
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* export const Worker = Cloudflare.Worker("Worker", {
|
|
32
|
+
* main: "./src/worker.ts",
|
|
33
|
+
* bindings: { Analytics },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Effect-style worker
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const analytics = yield* Cloudflare.AnalyticsEngineDataset.bind(Analytics);
|
|
40
|
+
* yield* analytics.writeDataPoint({ blobs: ["signup"] });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export type AnalyticsEngineDataset = {
|
|
44
|
+
kind: AnalyticsEngineDatasetTypeId;
|
|
45
|
+
name: string;
|
|
46
|
+
dataset: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const isAnalyticsEngineDataset = (
|
|
50
|
+
value: unknown,
|
|
51
|
+
): value is AnalyticsEngineDataset =>
|
|
52
|
+
typeof value === "object" &&
|
|
53
|
+
value !== null &&
|
|
54
|
+
"kind" in value &&
|
|
55
|
+
(value as AnalyticsEngineDataset).kind === AnalyticsEngineDatasetTypeId;
|
|
56
|
+
|
|
57
|
+
export const AnalyticsEngineDataset: {
|
|
58
|
+
(
|
|
59
|
+
name: string,
|
|
60
|
+
props?: AnalyticsEngineDatasetProps,
|
|
61
|
+
): Effect.Effect<AnalyticsEngineDataset>;
|
|
62
|
+
/**
|
|
63
|
+
* Bind Analytics Engine to the surrounding Worker, returning an
|
|
64
|
+
* Effect-native client with access to the native Workers runtime binding.
|
|
65
|
+
*/
|
|
66
|
+
bind: typeof AnalyticsEngineDatasetBinding.bind;
|
|
67
|
+
} = Object.assign(
|
|
68
|
+
Effect.fnUntraced(function* (
|
|
69
|
+
name: string,
|
|
70
|
+
props?: AnalyticsEngineDatasetProps,
|
|
71
|
+
) {
|
|
72
|
+
return {
|
|
73
|
+
kind: AnalyticsEngineDatasetTypeId,
|
|
74
|
+
name,
|
|
75
|
+
dataset: props?.dataset ?? name,
|
|
76
|
+
} satisfies AnalyticsEngineDataset;
|
|
77
|
+
}),
|
|
78
|
+
{
|
|
79
|
+
bind: (...args: Parameters<typeof AnalyticsEngineDatasetBinding.bind>) =>
|
|
80
|
+
AnalyticsEngineDatasetBinding.bind(...args),
|
|
81
|
+
},
|
|
82
|
+
);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Binding from "../../Binding.ts";
|
|
5
|
+
import type { ResourceLike } from "../../Resource.ts";
|
|
6
|
+
import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
|
|
7
|
+
import type { AnalyticsEngineDataset as AnalyticsEngineDatasetLike } from "./AnalyticsEngineDataset.ts";
|
|
8
|
+
|
|
9
|
+
export interface AnalyticsEngineDataPoint {
|
|
10
|
+
indexes?: string[];
|
|
11
|
+
blobs?: string[];
|
|
12
|
+
doubles?: number[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RuntimeAnalyticsEngineDataset {
|
|
16
|
+
writeDataPoint(dataPoint: AnalyticsEngineDataPoint): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class AnalyticsEngineDatasetError extends Data.TaggedError(
|
|
20
|
+
"AnalyticsEngineDatasetError",
|
|
21
|
+
)<{
|
|
22
|
+
message: string;
|
|
23
|
+
cause: Error;
|
|
24
|
+
}> {}
|
|
25
|
+
|
|
26
|
+
export interface AnalyticsEngineDatasetClient {
|
|
27
|
+
raw: Effect.Effect<RuntimeAnalyticsEngineDataset, never, WorkerEnvironment>;
|
|
28
|
+
writeDataPoint(
|
|
29
|
+
dataPoint: AnalyticsEngineDataPoint,
|
|
30
|
+
): Effect.Effect<void, AnalyticsEngineDatasetError, WorkerEnvironment>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class AnalyticsEngineDatasetBinding extends Binding.Service<
|
|
34
|
+
AnalyticsEngineDatasetBinding,
|
|
35
|
+
(
|
|
36
|
+
dataset: AnalyticsEngineDatasetLike,
|
|
37
|
+
) => Effect.Effect<AnalyticsEngineDatasetClient>
|
|
38
|
+
>()("Cloudflare.AnalyticsEngineDataset.Binding") {}
|
|
39
|
+
|
|
40
|
+
export const AnalyticsEngineDatasetBindingLive = Layer.effect(
|
|
41
|
+
AnalyticsEngineDatasetBinding,
|
|
42
|
+
Effect.gen(function* () {
|
|
43
|
+
const bind = yield* AnalyticsEngineDatasetBindingPolicy;
|
|
44
|
+
|
|
45
|
+
return Effect.fnUntraced(function* (dataset: AnalyticsEngineDatasetLike) {
|
|
46
|
+
yield* bind(dataset);
|
|
47
|
+
|
|
48
|
+
const raw = WorkerEnvironment.asEffect().pipe(
|
|
49
|
+
Effect.map(
|
|
50
|
+
(env) =>
|
|
51
|
+
(env as Record<string, RuntimeAnalyticsEngineDataset>)[
|
|
52
|
+
dataset.name
|
|
53
|
+
]!,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
raw,
|
|
59
|
+
writeDataPoint: (dataPoint) =>
|
|
60
|
+
raw.pipe(
|
|
61
|
+
Effect.flatMap((raw) =>
|
|
62
|
+
Effect.try({
|
|
63
|
+
try: () => raw.writeDataPoint(dataPoint),
|
|
64
|
+
catch: (error: any) =>
|
|
65
|
+
new AnalyticsEngineDatasetError({
|
|
66
|
+
message: error?.message ?? "Unknown error",
|
|
67
|
+
cause: error,
|
|
68
|
+
}),
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
} satisfies AnalyticsEngineDatasetClient;
|
|
73
|
+
});
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
export class AnalyticsEngineDatasetBindingPolicy extends Binding.Policy<
|
|
78
|
+
AnalyticsEngineDatasetBindingPolicy,
|
|
79
|
+
(dataset: AnalyticsEngineDatasetLike) => Effect.Effect<void>
|
|
80
|
+
>()("Cloudflare.AnalyticsEngineDataset.Binding") {}
|
|
81
|
+
|
|
82
|
+
export const AnalyticsEngineDatasetBindingPolicyLive =
|
|
83
|
+
AnalyticsEngineDatasetBindingPolicy.layer.succeed(
|
|
84
|
+
Effect.fnUntraced(function* (
|
|
85
|
+
host: ResourceLike,
|
|
86
|
+
dataset: AnalyticsEngineDatasetLike,
|
|
87
|
+
) {
|
|
88
|
+
if (isWorker(host)) {
|
|
89
|
+
yield* host.bind(dataset.name, {
|
|
90
|
+
bindings: [
|
|
91
|
+
{
|
|
92
|
+
type: "analytics_engine",
|
|
93
|
+
name: dataset.name,
|
|
94
|
+
dataset: dataset.dataset,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
return yield* Effect.die(
|
|
100
|
+
new Error(
|
|
101
|
+
`AnalyticsEngineDatasetBinding does not support runtime '${host.Type}'`,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
@@ -7,6 +7,86 @@ import { WorkerEnvironment } from "../Workers/Worker.ts";
|
|
|
7
7
|
import type { D1Database } from "./D1Database.ts";
|
|
8
8
|
import { DatabaseBinding } from "./D1DatabaseBinding.ts";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Effect-native wrapper around a Cloudflare D1 prepared statement.
|
|
12
|
+
*
|
|
13
|
+
* Construction (`prepare`) and binding (`bind`) are synchronous —
|
|
14
|
+
* they're plan builders and don't touch D1. Only the executors
|
|
15
|
+
* (`all`, `first`, `run`, `raw`) round-trip to the database, and
|
|
16
|
+
* those return Effects so they participate in the Effect runtime.
|
|
17
|
+
*/
|
|
18
|
+
export class D1PreparedStatement {
|
|
19
|
+
/** @internal */
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly query: string,
|
|
22
|
+
private readonly binds: ReadonlyArray<unknown>,
|
|
23
|
+
private readonly rawEff: Effect.Effect<runtime.D1Database>,
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Return a new prepared statement bound to `values`. Subsequent
|
|
28
|
+
* calls replace the previous binding, matching the underlying
|
|
29
|
+
* Cloudflare runtime semantics.
|
|
30
|
+
*/
|
|
31
|
+
bind(...values: unknown[]): D1PreparedStatement {
|
|
32
|
+
return new D1PreparedStatement(this.query, values, this.rawEff);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Run the query and return all matching rows. */
|
|
36
|
+
all<T = unknown>(): Effect.Effect<runtime.D1Result<T>> {
|
|
37
|
+
return this.withRuntime((stmt) => stmt.all<T>());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Run the query and return the first row, or `null` if no rows. */
|
|
41
|
+
first<T = unknown>(): Effect.Effect<T | null>;
|
|
42
|
+
first<T = unknown>(column: string): Effect.Effect<T | null>;
|
|
43
|
+
first<T = unknown>(column?: string): Effect.Effect<T | null> {
|
|
44
|
+
return this.withRuntime((stmt) =>
|
|
45
|
+
column !== undefined ? stmt.first<T>(column) : stmt.first<T>(),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Run the query as a mutation; returns row metadata. */
|
|
50
|
+
run<T = unknown>(): Effect.Effect<runtime.D1Result<T>> {
|
|
51
|
+
return this.withRuntime((stmt) => stmt.run<T>());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Run the query and return rows as flat arrays. */
|
|
55
|
+
raw<T = unknown[]>(): Effect.Effect<T[]>;
|
|
56
|
+
raw<T = unknown[]>(options: {
|
|
57
|
+
columnNames: true;
|
|
58
|
+
}): Effect.Effect<[string[], ...T[]]>;
|
|
59
|
+
raw<T = unknown[]>(options?: {
|
|
60
|
+
columnNames: true;
|
|
61
|
+
}): Effect.Effect<T[] | [string[], ...T[]]> {
|
|
62
|
+
return this.withRuntime((stmt) =>
|
|
63
|
+
options
|
|
64
|
+
? stmt.raw<T>(options)
|
|
65
|
+
: (stmt.raw<T>() as Promise<T[] | [string[], ...T[]]>),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Materialize the underlying Cloudflare prepared statement against
|
|
71
|
+
* a concrete D1 binding. Used by `D1ConnectionClient.batch` to
|
|
72
|
+
* collect statements for a single transactional call.
|
|
73
|
+
*
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
_build(raw: runtime.D1Database): runtime.D1PreparedStatement {
|
|
77
|
+
const stmt = raw.prepare(this.query);
|
|
78
|
+
return this.binds.length > 0 ? stmt.bind(...this.binds) : stmt;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private withRuntime<A>(
|
|
82
|
+
fn: (stmt: runtime.D1PreparedStatement) => Promise<A>,
|
|
83
|
+
): Effect.Effect<A> {
|
|
84
|
+
return Effect.flatMap(this.rawEff, (raw) =>
|
|
85
|
+
Effect.promise(() => fn(this._build(raw))),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
10
90
|
export interface D1ConnectionClient {
|
|
11
91
|
/**
|
|
12
92
|
* An Effect that resolves to the raw underlying Cloudflare D1Database binding.
|
|
@@ -14,9 +94,11 @@ export interface D1ConnectionClient {
|
|
|
14
94
|
*/
|
|
15
95
|
raw: Effect.Effect<runtime.D1Database>;
|
|
16
96
|
/**
|
|
17
|
-
* Prepare a SQL
|
|
97
|
+
* Prepare a SQL statement. Returns synchronously — the network
|
|
98
|
+
* round-trip happens when you yield one of the statement's
|
|
99
|
+
* executors (`all`, `first`, `run`, `raw`).
|
|
18
100
|
*/
|
|
19
|
-
prepare: (query: string) =>
|
|
101
|
+
prepare: (query: string) => D1PreparedStatement;
|
|
20
102
|
/**
|
|
21
103
|
* Execute raw SQL without prepared statements.
|
|
22
104
|
*/
|
|
@@ -26,7 +108,7 @@ export interface D1ConnectionClient {
|
|
|
26
108
|
* Statements execute sequentially and are rolled back on failure.
|
|
27
109
|
*/
|
|
28
110
|
batch: <T = unknown>(
|
|
29
|
-
statements:
|
|
111
|
+
statements: D1PreparedStatement[],
|
|
30
112
|
) => Effect.Effect<runtime.D1Result<T>[]>;
|
|
31
113
|
}
|
|
32
114
|
|
|
@@ -42,22 +124,23 @@ export const D1ConnectionLive = Layer.effect(
|
|
|
42
124
|
|
|
43
125
|
return Effect.fn(function* (database: D1Database) {
|
|
44
126
|
yield* Policy(database);
|
|
45
|
-
const
|
|
127
|
+
const rawEff = yield* Effect.serviceOption(WorkerEnvironment).pipe(
|
|
46
128
|
Effect.map(Option.getOrUndefined),
|
|
47
129
|
Effect.map((env) => env?.[database.LogicalId]! as runtime.D1Database),
|
|
48
130
|
Effect.cached,
|
|
49
131
|
);
|
|
50
132
|
|
|
51
133
|
return {
|
|
52
|
-
raw:
|
|
53
|
-
prepare: (query: string) =>
|
|
54
|
-
d1.pipe(Effect.map((d1) => d1.prepare(query))),
|
|
134
|
+
raw: rawEff,
|
|
135
|
+
prepare: (query: string) => new D1PreparedStatement(query, [], rawEff),
|
|
55
136
|
exec: (query: string) =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
137
|
+
Effect.flatMap(rawEff, (raw) =>
|
|
138
|
+
Effect.promise(() => raw.exec(query)),
|
|
139
|
+
),
|
|
140
|
+
batch: <T = unknown>(statements: D1PreparedStatement[]) =>
|
|
141
|
+
Effect.flatMap(rawEff, (raw) =>
|
|
142
|
+
Effect.promise(() =>
|
|
143
|
+
raw.batch<T>(statements.map((s) => s._build(raw))),
|
|
61
144
|
),
|
|
62
145
|
),
|
|
63
146
|
} satisfies D1ConnectionClient;
|
|
@@ -169,7 +169,15 @@ function bundleOutputToWorkerModules(
|
|
|
169
169
|
): WorkerModule[] {
|
|
170
170
|
const modules: WorkerModule[] = [];
|
|
171
171
|
for (const file of bundle.files) {
|
|
172
|
-
if (file.path.endsWith(".map")
|
|
172
|
+
if (file.path.endsWith(".map")) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (file.content instanceof Uint8Array) {
|
|
176
|
+
modules.push({
|
|
177
|
+
name: file.path,
|
|
178
|
+
type: file.path.endsWith(".wasm") ? "Wasm" : "Data",
|
|
179
|
+
content: file.content,
|
|
180
|
+
});
|
|
173
181
|
continue;
|
|
174
182
|
}
|
|
175
183
|
modules.push({
|
|
@@ -13,6 +13,7 @@ import { ProfileLive } from "../Auth/Profile.ts";
|
|
|
13
13
|
import { Random, RandomProvider } from "../Random.ts";
|
|
14
14
|
import * as Access from "./Access.ts";
|
|
15
15
|
import * as AiGateway from "./AiGateway/index.ts";
|
|
16
|
+
import * as AnalyticsEngine from "./AnalyticsEngine/index.ts";
|
|
16
17
|
import * as ApiToken from "./ApiToken/index.ts";
|
|
17
18
|
import * as Artifacts from "./Artifacts/index.ts";
|
|
18
19
|
import { CloudflareAuth } from "./Auth/AuthProvider.ts";
|
|
@@ -50,6 +51,7 @@ export const providers = () =>
|
|
|
50
51
|
ApiToken.UserApiToken,
|
|
51
52
|
AiGateway.AiGateway,
|
|
52
53
|
AiGateway.AiGatewayBindingPolicy,
|
|
54
|
+
AnalyticsEngine.AnalyticsEngineDatasetBindingPolicy,
|
|
53
55
|
Artifacts.ArtifactsBindingPolicy,
|
|
54
56
|
Command,
|
|
55
57
|
Containers.Container,
|
|
@@ -73,6 +75,7 @@ export const providers = () =>
|
|
|
73
75
|
VpcService.VpcService,
|
|
74
76
|
Random,
|
|
75
77
|
Workers.BindWorkerPolicy,
|
|
78
|
+
Workers.CronEventSourcePolicy,
|
|
76
79
|
Workers.FetchPolicy,
|
|
77
80
|
Workers.Worker,
|
|
78
81
|
Workflows.WorkflowResource,
|
|
@@ -84,6 +87,7 @@ export const providers = () =>
|
|
|
84
87
|
ApiToken.UserApiTokenProvider(),
|
|
85
88
|
AiGateway.AiGatewayProvider(),
|
|
86
89
|
AiGateway.AiGatewayBindingPolicyLive,
|
|
90
|
+
AnalyticsEngine.AnalyticsEngineDatasetBindingPolicyLive,
|
|
87
91
|
Artifacts.ArtifactsBindingPolicyLive,
|
|
88
92
|
Containers.ContainerProvider(),
|
|
89
93
|
D1.D1ConnectionPolicyLive,
|
|
@@ -105,6 +109,7 @@ export const providers = () =>
|
|
|
105
109
|
Tunnel.TunnelProvider(),
|
|
106
110
|
VpcService.VpcServiceProvider(),
|
|
107
111
|
Workers.BindWorkerPolicyLive,
|
|
112
|
+
Workers.CronEventSourcePolicyLive,
|
|
108
113
|
Workers.FetchPolicyLive,
|
|
109
114
|
Workers.WorkerProvider(),
|
|
110
115
|
Workflows.WorkflowProvider(),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as r2 from "@distilled.cloud/cloudflare/r2";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Schedule from "effect/Schedule";
|
|
4
|
+
import * as Stream from "effect/Stream";
|
|
4
5
|
|
|
5
6
|
import { deepEqual, isResolved } from "../../Diff.ts";
|
|
6
7
|
import { createPhysicalName } from "../../PhysicalName.ts";
|
|
@@ -355,6 +356,7 @@ export const R2BucketProvider = () =>
|
|
|
355
356
|
const patchBucket = yield* r2.patchBucket;
|
|
356
357
|
const deleteBucket = yield* r2.deleteBucket;
|
|
357
358
|
const getBucket = yield* r2.getBucket;
|
|
359
|
+
const deleteObjects = yield* r2.deleteObjects;
|
|
358
360
|
const listBucketDomainCustoms = yield* r2.listBucketDomainCustoms;
|
|
359
361
|
const createBucketDomainCustom = yield* r2.createBucketDomainCustom;
|
|
360
362
|
const updateBucketDomainCustom = yield* r2.updateBucketDomainCustom;
|
|
@@ -362,6 +364,34 @@ export const R2BucketProvider = () =>
|
|
|
362
364
|
const getBucketLifecycle = yield* r2.getBucketLifecycle;
|
|
363
365
|
const putBucketLifecycle = yield* r2.putBucketLifecycle;
|
|
364
366
|
|
|
367
|
+
const emptyBucket = (
|
|
368
|
+
bucketName: string,
|
|
369
|
+
jurisdiction: R2Bucket.Jurisdiction,
|
|
370
|
+
) =>
|
|
371
|
+
r2.listObjects
|
|
372
|
+
.items({
|
|
373
|
+
accountId,
|
|
374
|
+
bucketName,
|
|
375
|
+
cfR2Jurisdiction: jurisdiction,
|
|
376
|
+
perPage: 1000,
|
|
377
|
+
})
|
|
378
|
+
.pipe(
|
|
379
|
+
Stream.filter(
|
|
380
|
+
(o): o is typeof o & { key: string } =>
|
|
381
|
+
typeof o.key === "string" && o.key !== "",
|
|
382
|
+
),
|
|
383
|
+
Stream.map((o) => o.key),
|
|
384
|
+
Stream.runForEachArray((chunk) =>
|
|
385
|
+
deleteObjects({
|
|
386
|
+
accountId,
|
|
387
|
+
bucketName,
|
|
388
|
+
cfR2Jurisdiction: jurisdiction,
|
|
389
|
+
body: [...chunk],
|
|
390
|
+
}),
|
|
391
|
+
),
|
|
392
|
+
Effect.catchTag("NoSuchBucket", () => Effect.void),
|
|
393
|
+
);
|
|
394
|
+
|
|
365
395
|
const createBucketName = (id: string, name: string | undefined) =>
|
|
366
396
|
Effect.gen(function* () {
|
|
367
397
|
if (name) return name;
|
|
@@ -683,6 +713,7 @@ export const R2BucketProvider = () =>
|
|
|
683
713
|
Effect.catchIf(isMissingCustomDomainOrBucket, () => Effect.void),
|
|
684
714
|
);
|
|
685
715
|
}
|
|
716
|
+
yield* emptyBucket(output.bucketName, output.jurisdiction);
|
|
686
717
|
yield* deleteBucket({
|
|
687
718
|
accountId: output.accountId,
|
|
688
719
|
bucketName: output.bucketName,
|
|
@@ -29,7 +29,7 @@ export const STATE_STORE_SCRIPT_NAME = "alchemy-state-store" as const;
|
|
|
29
29
|
* compare against this constant; a mismatch (or 404) triggers a
|
|
30
30
|
* forced redeploy via the bootstrap flow.
|
|
31
31
|
*/
|
|
32
|
-
export const STATE_STORE_VERSION =
|
|
32
|
+
export const STATE_STORE_VERSION = 4 as const;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Hard-coded OTLP/HTTP endpoints. Point at the public ingest relay
|
|
@@ -245,6 +245,39 @@ export default Worker(
|
|
|
245
245
|
}),
|
|
246
246
|
),
|
|
247
247
|
)
|
|
248
|
+
.handle("getStackOutput", ({ params }) =>
|
|
249
|
+
store
|
|
250
|
+
.getByName(params.stack)
|
|
251
|
+
.getOutput({ stage: params.stage })
|
|
252
|
+
.pipe(
|
|
253
|
+
Effect.withSpan("state_store.getStackOutput", {
|
|
254
|
+
attributes: {
|
|
255
|
+
"alchemy.state_store.op": "getStackOutput",
|
|
256
|
+
"alchemy.state_store.stack": params.stack,
|
|
257
|
+
"alchemy.state_store.stage": params.stage,
|
|
258
|
+
},
|
|
259
|
+
}),
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
.handle("setStackOutput", ({ params, payload }) =>
|
|
263
|
+
store
|
|
264
|
+
.getByName(params.stack)
|
|
265
|
+
.setOutput({ stage: params.stage, value: payload as any })
|
|
266
|
+
.pipe(
|
|
267
|
+
Effect.tap(() =>
|
|
268
|
+
store
|
|
269
|
+
.getByName(Store.ROOT_DO_NAME)
|
|
270
|
+
.registerStack({ stack: params.stack }),
|
|
271
|
+
),
|
|
272
|
+
Effect.withSpan("state_store.setStackOutput", {
|
|
273
|
+
attributes: {
|
|
274
|
+
"alchemy.state_store.op": "setStackOutput",
|
|
275
|
+
"alchemy.state_store.stack": params.stack,
|
|
276
|
+
"alchemy.state_store.stage": params.stage,
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
),
|
|
280
|
+
)
|
|
248
281
|
.handle("deleteStack", ({ params, query }) =>
|
|
249
282
|
store
|
|
250
283
|
.getByName(params.stack)
|
|
@@ -36,7 +36,7 @@ export default class Store extends DurableObjectNamespace<Store>()(
|
|
|
36
36
|
),
|
|
37
37
|
).pipe(Effect.orDie);
|
|
38
38
|
|
|
39
|
-
const encryptValue = (value:
|
|
39
|
+
const encryptValue = (value: unknown) =>
|
|
40
40
|
Effect.tryPromise(async () => {
|
|
41
41
|
const plaintext = new TextEncoder().encode(
|
|
42
42
|
JSON.stringify(encodeState(value)),
|
|
@@ -188,15 +188,39 @@ export default class Store extends DurableObjectNamespace<Store>()(
|
|
|
188
188
|
deleteStack: ({ stage }: { stage?: string } = {}) =>
|
|
189
189
|
stage === undefined
|
|
190
190
|
? storage.deleteAll()
|
|
191
|
-
: storage
|
|
192
|
-
.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
191
|
+
: storage.list<string>({ prefix: stagePrefix(stage) }).pipe(
|
|
192
|
+
Effect.flatMap((entries) => {
|
|
193
|
+
const keys = [...entries.keys(), stackOutputKey(stage)];
|
|
194
|
+
return storage.delete(keys).pipe(Effect.asVoid);
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* (Stack DO only) Read the persisted stack output for `stage`.
|
|
200
|
+
* Returns `undefined` when the stage has not been deployed.
|
|
201
|
+
*/
|
|
202
|
+
getOutput: ({ stage }: { stage: string }) =>
|
|
203
|
+
storage
|
|
204
|
+
.get<string>(stackOutputKey(stage))
|
|
205
|
+
.pipe(
|
|
206
|
+
Effect.flatMap((entry) =>
|
|
207
|
+
entry == null ? Effect.succeed(undefined) : decryptEntry(entry),
|
|
208
|
+
),
|
|
209
|
+
),
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* (Stack DO only) Persist the resolved stack output for
|
|
213
|
+
* `stage`. Returns the stored value unchanged.
|
|
214
|
+
*/
|
|
215
|
+
setOutput: ({ stage, value }: { stage: string; value: any }) =>
|
|
216
|
+
encryptValue(value).pipe(
|
|
217
|
+
Effect.flatMap((encrypted) =>
|
|
218
|
+
storage
|
|
219
|
+
.put<string>(stackOutputKey(stage), encrypted)
|
|
220
|
+
.pipe(Effect.asVoid),
|
|
221
|
+
),
|
|
222
|
+
Effect.map(() => value),
|
|
223
|
+
),
|
|
200
224
|
|
|
201
225
|
/**
|
|
202
226
|
* (Stack DO only) Return every resource in a stage whose
|
|
@@ -236,6 +260,9 @@ const SEP = "\x00";
|
|
|
236
260
|
/** Key prefix for resource entries in a stack DO. */
|
|
237
261
|
const RESOURCE_PREFIX = `r${SEP}`;
|
|
238
262
|
|
|
263
|
+
/** Key prefix for stack-output entries in a stack DO. */
|
|
264
|
+
const STACK_OUTPUT_PREFIX = `o${SEP}`;
|
|
265
|
+
|
|
239
266
|
/** Key prefix for stack-index entries in the root DO. */
|
|
240
267
|
const STACK_INDEX_PREFIX = "s:";
|
|
241
268
|
|
|
@@ -249,6 +276,9 @@ const resourceKey = (stage: string, fqn: string) =>
|
|
|
249
276
|
/** Prefix matching every resource key inside a specific stage. */
|
|
250
277
|
const stagePrefix = (stage: string) => `${RESOURCE_PREFIX}${stage}${SEP}`;
|
|
251
278
|
|
|
279
|
+
/** Build the stack-output key inside a *stack DO*. */
|
|
280
|
+
const stackOutputKey = (stage: string) => `${STACK_OUTPUT_PREFIX}${stage}`;
|
|
281
|
+
|
|
252
282
|
/**
|
|
253
283
|
* Parse a resource key back into its (stage, fqn) tuple. Returns
|
|
254
284
|
* undefined for keys that do not match the expected shape.
|