alchemy 2.0.0-beta.13 → 2.0.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/alchemy.js +8386 -8205
- package/bin/alchemy.js.map +1 -1
- package/bin/alchemy.ts +4 -2
- package/bin/cli.js +61 -0
- package/bin/commands/deploy.ts +6 -3
- package/bin/commands/login.ts +1 -1
- package/bin/commands/logs.ts +1 -1
- package/bin/commands/state.ts +230 -0
- package/bin/commands/tail.ts +1 -1
- package/lib/AWS/AutoScaling/LaunchTemplate.d.ts +1 -1
- package/lib/AWS/EC2/Instance.d.ts +1 -1
- package/lib/AWS/EC2/hosted.d.ts +2 -2
- package/lib/AWS/ECS/Task.d.ts +4 -4
- package/lib/AWS/ECS/Task.d.ts.map +1 -1
- package/lib/AWS/ECS/Task.js +2 -2
- package/lib/AWS/ECS/Task.js.map +1 -1
- package/lib/AWS/Environment.d.ts +2 -2
- package/lib/AWS/Lambda/BucketEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/EventBridgeEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/EventBridgeEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts +2 -2
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +1 -2
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/AWS/Lambda/QueueEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/QueueEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/StreamEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/StreamEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/TableEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/TableEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/TopicEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/TopicEventSource.d.ts.map +1 -1
- package/lib/AWS/SQS/QueueSink.d.ts.map +1 -1
- package/lib/AWS/SQS/QueueSink.js +1 -2
- package/lib/AWS/SQS/QueueSink.js.map +1 -1
- package/lib/Agent/coding/bash.d.ts +2 -6
- package/lib/Agent/coding/bash.d.ts.map +1 -1
- package/lib/Agent/coding/bash.js +2 -3
- package/lib/Agent/coding/bash.js.map +1 -1
- package/lib/Agent/coding/edit.d.ts +1 -1
- package/lib/Agent/coding/glob.d.ts +2 -4
- package/lib/Agent/coding/glob.d.ts.map +1 -1
- package/lib/Agent/coding/glob.js +1 -2
- package/lib/Agent/coding/glob.js.map +1 -1
- package/lib/Agent/coding/grep.d.ts +2 -4
- package/lib/Agent/coding/grep.d.ts.map +1 -1
- package/lib/Agent/coding/grep.js +1 -2
- package/lib/Agent/coding/grep.js.map +1 -1
- package/lib/Agent/coding/read.d.ts +1 -1
- package/lib/Agent/coding/write.d.ts +1 -1
- package/lib/Agent/util/ripgrep.d.ts +2 -2
- package/lib/AlchemyContext.d.ts +13 -0
- package/lib/AlchemyContext.d.ts.map +1 -0
- package/lib/AlchemyContext.js +18 -0
- package/lib/AlchemyContext.js.map +1 -0
- package/lib/Auth/AuthProvider.js +1 -1
- package/lib/Auth/AuthProvider.js.map +1 -1
- package/lib/Auth/Profile.d.ts +1 -1
- package/lib/Auth/index.d.ts +3 -1
- package/lib/Auth/index.d.ts.map +1 -1
- package/lib/Auth/index.js +3 -0
- package/lib/Auth/index.js.map +1 -1
- package/lib/Build/Command.d.ts +1 -1
- package/lib/Build/Memo.d.ts +1 -1
- package/lib/Bundle/Docker.d.ts +3 -3
- package/lib/Bundle/TempRoot.d.ts +3 -3
- package/lib/Cli/InkCLI.d.ts +1 -0
- package/lib/Cli/InkCLI.d.ts.map +1 -1
- package/lib/Cli/InkCLI.js +5 -0
- package/lib/Cli/InkCLI.js.map +1 -1
- package/lib/Cli/LoggingCli.d.ts +4 -0
- package/lib/Cli/LoggingCli.d.ts.map +1 -0
- package/lib/{Test/TestCli.js → Cli/LoggingCli.js} +3 -3
- package/lib/Cli/LoggingCli.js.map +1 -0
- package/lib/Cloudflare/CloudflareEnvironment.d.ts +1 -1
- package/lib/Cloudflare/Container/ContainerApplication.d.ts +2 -2
- package/lib/Cloudflare/Container/ContainerApplication.d.ts.map +1 -1
- package/lib/Cloudflare/Container/ContainerApplication.js +7 -2
- package/lib/Cloudflare/Container/ContainerApplication.js.map +1 -1
- package/lib/Cloudflare/EdgeSession.d.ts +53 -0
- package/lib/Cloudflare/EdgeSession.d.ts.map +1 -0
- package/lib/Cloudflare/EdgeSession.js +78 -0
- package/lib/Cloudflare/EdgeSession.js.map +1 -0
- package/lib/Cloudflare/Providers.d.ts +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +62 -0
- package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -0
- package/lib/Cloudflare/StateStore/Api.js +101 -0
- package/lib/Cloudflare/StateStore/Api.js.map +1 -0
- package/lib/Cloudflare/StateStore/State.d.ts +39 -0
- package/lib/Cloudflare/StateStore/State.d.ts.map +1 -0
- package/lib/Cloudflare/StateStore/State.js +217 -0
- package/lib/Cloudflare/StateStore/State.js.map +1 -0
- package/lib/Cloudflare/StateStore/Store.d.ts +82 -0
- package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -0
- package/lib/Cloudflare/StateStore/Store.js +189 -0
- package/lib/Cloudflare/StateStore/Store.js.map +1 -0
- package/lib/Cloudflare/StateStore/Token.d.ts +41 -0
- package/lib/Cloudflare/StateStore/Token.d.ts.map +1 -0
- package/lib/Cloudflare/StateStore/Token.js +57 -0
- package/lib/Cloudflare/StateStore/Token.js.map +1 -0
- package/lib/Cloudflare/StateStore/index.d.ts +2 -0
- package/lib/Cloudflare/StateStore/index.d.ts.map +1 -0
- package/lib/Cloudflare/StateStore/index.js +2 -0
- package/lib/Cloudflare/StateStore/index.js.map +1 -0
- package/lib/Cloudflare/Workers/Assets.d.ts +2 -2
- package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts +2 -2
- package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/DurableObjectNamespace.js +0 -1
- package/lib/Cloudflare/Workers/DurableObjectNamespace.js.map +1 -1
- package/lib/Cloudflare/Workers/HttpServer.d.ts +1 -1
- package/lib/Cloudflare/Workers/HttpServer.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/HttpServer.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +5 -5
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +0 -1
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/index.d.ts +3 -1
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +3 -1
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/Deploy.d.ts +10 -0
- package/lib/Deploy.d.ts.map +1 -0
- package/lib/Deploy.js +12 -0
- package/lib/Deploy.js.map +1 -0
- package/lib/Destroy.d.ts +8 -3
- package/lib/Destroy.d.ts.map +1 -1
- package/lib/Destroy.js +8 -8
- package/lib/Destroy.js.map +1 -1
- package/lib/Http.d.ts +1 -1
- package/lib/Http.d.ts.map +1 -1
- package/lib/Http.js +7 -3
- package/lib/Http.js.map +1 -1
- package/lib/Kubernetes/client.d.ts +10 -10
- package/lib/Output.d.ts.map +1 -1
- package/lib/Output.js.map +1 -1
- package/lib/Platform.d.ts +2 -2
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js.map +1 -1
- package/lib/Provider.d.ts.map +1 -1
- package/lib/Provider.js +0 -1
- package/lib/Provider.js.map +1 -1
- package/lib/Stack.d.ts +23 -12
- package/lib/Stack.d.ts.map +1 -1
- package/lib/Stack.js +32 -14
- package/lib/Stack.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +96 -0
- package/lib/State/HttpStateApi.d.ts.map +1 -0
- package/lib/State/HttpStateApi.js +96 -0
- package/lib/State/HttpStateApi.js.map +1 -0
- package/lib/State/HttpStateStore.d.ts +10 -0
- package/lib/State/HttpStateStore.d.ts.map +1 -0
- package/lib/State/HttpStateStore.js +102 -0
- package/lib/State/HttpStateStore.js.map +1 -0
- package/lib/State/InMemoryState.d.ts +2 -30
- package/lib/State/InMemoryState.d.ts.map +1 -1
- package/lib/State/InMemoryState.js +23 -18
- package/lib/State/InMemoryState.js.map +1 -1
- package/lib/State/LocalState.d.ts +4 -2
- package/lib/State/LocalState.d.ts.map +1 -1
- package/lib/State/LocalState.js +9 -41
- package/lib/State/LocalState.js.map +1 -1
- package/lib/State/State.d.ts +11 -4
- package/lib/State/State.d.ts.map +1 -1
- package/lib/State/State.js +1 -1
- package/lib/State/State.js.map +1 -1
- package/lib/State/StateEncoding.d.ts +31 -0
- package/lib/State/StateEncoding.d.ts.map +1 -0
- package/lib/State/StateEncoding.js +82 -0
- package/lib/State/StateEncoding.js.map +1 -0
- package/lib/State/Sync.d.ts +23 -0
- package/lib/State/Sync.d.ts.map +1 -0
- package/lib/State/Sync.js +49 -0
- package/lib/State/Sync.js.map +1 -0
- package/lib/State/index.d.ts +4 -0
- package/lib/State/index.d.ts.map +1 -1
- package/lib/State/index.js +4 -0
- package/lib/State/index.js.map +1 -1
- package/lib/Test/Bun.d.ts +13 -17
- package/lib/Test/Bun.d.ts.map +1 -1
- package/lib/Test/Bun.js +16 -33
- package/lib/Test/Bun.js.map +1 -1
- package/lib/Test/TestState.d.ts +1 -2
- package/lib/Test/TestState.d.ts.map +1 -1
- package/lib/Test/TestState.js +5 -9
- package/lib/Test/TestState.js.map +1 -1
- package/lib/Test/Vitest.d.ts +3 -2
- package/lib/Test/Vitest.d.ts.map +1 -1
- package/lib/Test/Vitest.js +20 -15
- package/lib/Test/Vitest.js.map +1 -1
- package/lib/Test/index.d.ts +0 -1
- package/lib/Test/index.d.ts.map +1 -1
- package/lib/Test/index.js +0 -1
- package/lib/Test/index.js.map +1 -1
- package/lib/Util/FileLogger.d.ts +2 -2
- package/lib/Util/FileLogger.d.ts.map +1 -1
- package/lib/Util/FileLogger.js +2 -2
- package/lib/Util/FileLogger.js.map +1 -1
- package/lib/cli/index.d.ts +10 -1
- package/lib/cli/index.d.ts.map +1 -1
- package/lib/cli/index.js +9 -4
- package/lib/cli/index.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/package.json +26 -11
- package/src/AWS/ECS/Task.ts +4 -4
- package/src/AWS/Lambda/Function.ts +2 -3
- package/src/AWS/SQS/QueueSink.ts +1 -2
- package/src/Agent/coding/bash.ts +2 -3
- package/src/Agent/coding/glob.ts +1 -2
- package/src/Agent/coding/grep.ts +1 -2
- package/src/AlchemyContext.ts +27 -0
- package/src/Auth/AuthProvider.ts +1 -1
- package/src/Auth/index.ts +3 -1
- package/src/Cli/InkCLI.tsx +10 -0
- package/src/{Test/TestCli.ts → Cli/LoggingCli.ts} +2 -2
- package/src/Cloudflare/Container/ContainerApplication.ts +8 -2
- package/src/Cloudflare/EdgeSession.ts +144 -0
- package/src/Cloudflare/StateStore/Api.ts +157 -0
- package/src/Cloudflare/StateStore/State.ts +318 -0
- package/src/Cloudflare/StateStore/Store.ts +288 -0
- package/src/Cloudflare/StateStore/Token.ts +62 -0
- package/src/Cloudflare/StateStore/index.ts +1 -0
- package/src/Cloudflare/Workers/DurableObjectNamespace.ts +1 -6
- package/src/Cloudflare/Workers/HttpServer.ts +1 -1
- package/src/Cloudflare/Workers/Worker.ts +7 -7
- package/src/Cloudflare/index.ts +3 -1
- package/src/Deploy.ts +25 -0
- package/src/Destroy.ts +25 -13
- package/src/Http.ts +31 -16
- package/src/Output.ts +0 -2
- package/src/Platform.ts +8 -2
- package/src/Provider.ts +0 -2
- package/src/Stack.ts +122 -47
- package/src/State/HttpStateApi.ts +155 -0
- package/src/State/HttpStateStore.ts +155 -0
- package/src/State/InMemoryState.ts +15 -17
- package/src/State/LocalState.ts +13 -45
- package/src/State/State.ts +13 -4
- package/src/State/StateEncoding.ts +83 -0
- package/src/State/Sync.ts +89 -0
- package/src/State/index.ts +4 -0
- package/src/Test/Bun.ts +29 -87
- package/src/Test/TestState.ts +6 -8
- package/src/Test/Vitest.ts +32 -23
- package/src/Test/index.ts +0 -1
- package/src/Util/FileLogger.ts +2 -2
- package/src/index.ts +2 -1
- package/lib/Config.d.ts +0 -20
- package/lib/Config.d.ts.map +0 -1
- package/lib/Config.js +0 -22
- package/lib/Config.js.map +0 -1
- package/lib/Observability.d.ts +0 -2
- package/lib/Observability.d.ts.map +0 -1
- package/lib/Observability.js +0 -1
- package/lib/Observability.js.map +0 -1
- package/lib/Test/TestCli.d.ts +0 -4
- package/lib/Test/TestCli.d.ts.map +0 -1
- package/lib/Test/TestCli.js.map +0 -1
- package/src/Config.ts +0 -31
- package/src/Observability.ts +0 -0
package/src/Stack.ts
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
|
+
import { ConfigProvider } from "effect/ConfigProvider";
|
|
1
2
|
import * as Context from "effect/Context";
|
|
2
3
|
import * as Effect from "effect/Effect";
|
|
3
4
|
import { FileSystem } from "effect/FileSystem";
|
|
4
5
|
import * as Layer from "effect/Layer";
|
|
6
|
+
import * as Logger from "effect/Logger";
|
|
5
7
|
import * as Option from "effect/Option";
|
|
6
8
|
import { Path } from "effect/Path";
|
|
7
9
|
import type { Scope } from "effect/Scope";
|
|
10
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
8
11
|
import type { HttpClient } from "effect/unstable/http/HttpClient";
|
|
9
12
|
import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
13
|
+
import { AlchemyContext, AlchemyContextLive } from "./AlchemyContext.ts";
|
|
14
|
+
import { provideFreshArtifactStore } from "./Artifacts.ts";
|
|
15
|
+
import { AuthProviders } from "./Auth/AuthProvider.ts";
|
|
16
|
+
import { inkCLIAutoApprove } from "./Cli/InkCLI.tsx";
|
|
12
17
|
import type { Input, InputProps } from "./Input.ts";
|
|
13
18
|
import * as Output from "./Output.ts";
|
|
14
19
|
import { ref } from "./Ref.ts";
|
|
15
20
|
import type { ResourceBinding, ResourceLike } from "./Resource.ts";
|
|
16
21
|
import { Stage } from "./Stage.ts";
|
|
22
|
+
import type { State } from "./State/State.ts";
|
|
23
|
+
import { loadConfigProvider } from "./Util/ConfigProvider.ts";
|
|
17
24
|
import { taggedFunction } from "./Util/effect.ts";
|
|
25
|
+
import { fileLogger } from "./Util/FileLogger.ts";
|
|
26
|
+
import { PlatformServices } from "./Util/PlatformServices.ts";
|
|
18
27
|
|
|
19
28
|
export type StackServices =
|
|
20
29
|
| Stack
|
|
@@ -22,16 +31,33 @@ export type StackServices =
|
|
|
22
31
|
| Scope
|
|
23
32
|
| FileSystem
|
|
24
33
|
| Path
|
|
25
|
-
|
|
|
34
|
+
| AlchemyContext
|
|
26
35
|
| HttpClient
|
|
27
36
|
| ChildProcessSpawner
|
|
28
37
|
| AuthProviders;
|
|
29
38
|
|
|
39
|
+
export type StackEffect<A, Err = never, Req = never> = Effect.Effect<
|
|
40
|
+
A,
|
|
41
|
+
Err,
|
|
42
|
+
| PlatformServices
|
|
43
|
+
| HttpClient
|
|
44
|
+
| Scope
|
|
45
|
+
| AuthProviders
|
|
46
|
+
| AlchemyContext
|
|
47
|
+
| State
|
|
48
|
+
| Req
|
|
49
|
+
>;
|
|
50
|
+
|
|
30
51
|
export type Stack = Context.ServiceClass.Shape<
|
|
31
52
|
"Stack",
|
|
32
53
|
Omit<StackSpec, "output">
|
|
33
54
|
>;
|
|
34
55
|
|
|
56
|
+
export interface StackProps<Req> {
|
|
57
|
+
providers: Layer.Layer<NoInfer<Req>, never, StackServices>;
|
|
58
|
+
state: Layer.Layer<State, never, StackServices>;
|
|
59
|
+
}
|
|
60
|
+
|
|
35
61
|
export const Stack: Context.ServiceClass<
|
|
36
62
|
Stack,
|
|
37
63
|
"Stack",
|
|
@@ -50,9 +76,7 @@ export const Stack: Context.ServiceClass<
|
|
|
50
76
|
<Self>(): {
|
|
51
77
|
<A, Req>(
|
|
52
78
|
stackName: string,
|
|
53
|
-
options:
|
|
54
|
-
providers: Layer.Layer<NoInfer<Req>, never, StackServices>;
|
|
55
|
-
},
|
|
79
|
+
options: StackProps<NoInfer<Req>>,
|
|
56
80
|
eff: Effect.Effect<A, never, Req>,
|
|
57
81
|
): Effect.Effect<Self> & {
|
|
58
82
|
new (_: never): A extends object ? A : {};
|
|
@@ -75,9 +99,7 @@ export const Stack: Context.ServiceClass<
|
|
|
75
99
|
};
|
|
76
100
|
<A, Req>(
|
|
77
101
|
stackName: string,
|
|
78
|
-
options:
|
|
79
|
-
providers: Layer.Layer<NoInfer<Req>, never, StackServices>;
|
|
80
|
-
},
|
|
102
|
+
options: StackProps<NoInfer<Req>>,
|
|
81
103
|
eff: Effect.Effect<A, never, Req | StackServices>,
|
|
82
104
|
): Effect.Effect<CompiledStack<A>>;
|
|
83
105
|
} = Object.assign(
|
|
@@ -87,23 +109,29 @@ export const Stack: Context.ServiceClass<
|
|
|
87
109
|
stackName: string,
|
|
88
110
|
options: {
|
|
89
111
|
providers: Layer.Layer<NoInfer<Req>, never, StackServices>;
|
|
112
|
+
state: Layer.Layer<State, never, StackServices>;
|
|
90
113
|
},
|
|
91
114
|
eff: Effect.Effect<A, never, Req>,
|
|
92
115
|
) =>
|
|
93
|
-
eff.pipe(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
get: (_, stage: string) =>
|
|
99
|
-
ref({
|
|
100
|
-
stack: stackName,
|
|
101
|
-
stage,
|
|
102
|
-
id: stackName,
|
|
103
|
-
}),
|
|
104
|
-
},
|
|
105
|
-
),
|
|
116
|
+
eff.pipe(
|
|
117
|
+
make({
|
|
118
|
+
name: stackName,
|
|
119
|
+
...options,
|
|
106
120
|
}),
|
|
121
|
+
(eff) =>
|
|
122
|
+
Object.assign(eff, {
|
|
123
|
+
stage: new Proxy(
|
|
124
|
+
{},
|
|
125
|
+
{
|
|
126
|
+
get: (_, stage: string) =>
|
|
127
|
+
ref({
|
|
128
|
+
stack: stackName,
|
|
129
|
+
stage,
|
|
130
|
+
id: stackName,
|
|
131
|
+
}),
|
|
132
|
+
},
|
|
133
|
+
),
|
|
134
|
+
}),
|
|
107
135
|
),
|
|
108
136
|
),
|
|
109
137
|
) as any;
|
|
@@ -130,38 +158,42 @@ export interface CompiledStack<
|
|
|
130
158
|
|
|
131
159
|
export const StackName = Stack.use((stack) => Effect.succeed(stack.name));
|
|
132
160
|
|
|
161
|
+
export interface MakeStackProps<ROut = never> {
|
|
162
|
+
name: string;
|
|
163
|
+
providers: Layer.Layer<ROut, never, StackServices>;
|
|
164
|
+
state: Layer.Layer<State, never, StackServices>;
|
|
165
|
+
/** @internal */
|
|
166
|
+
stack?: StackSpec;
|
|
167
|
+
}
|
|
168
|
+
|
|
133
169
|
export const make =
|
|
134
|
-
<ROut = never>(
|
|
135
|
-
name: string,
|
|
136
|
-
providers: Layer.Layer<ROut, never, StackServices>,
|
|
137
|
-
/** @internal */
|
|
138
|
-
stack?: StackSpec,
|
|
139
|
-
) =>
|
|
170
|
+
<ROut = never>(options: MakeStackProps<ROut>) =>
|
|
140
171
|
<A, Err = never, Req extends ROut | StackServices = never>(
|
|
141
172
|
effect: Effect.Effect<A, Err, Req>,
|
|
142
173
|
) =>
|
|
143
174
|
Effect.scope.pipe(
|
|
144
|
-
Effect.flatMap((scope) =>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
stage
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
175
|
+
Effect.flatMap((scope) =>
|
|
176
|
+
options.providers.pipe(
|
|
177
|
+
Layer.provideMerge(options.state),
|
|
178
|
+
Layer.provideMerge(
|
|
179
|
+
Layer.effect(
|
|
180
|
+
Stack,
|
|
181
|
+
Stage.asEffect().pipe(
|
|
182
|
+
Effect.map(
|
|
183
|
+
(stage) =>
|
|
184
|
+
options.stack ?? {
|
|
185
|
+
name: options.name,
|
|
186
|
+
stage,
|
|
187
|
+
resources: {},
|
|
188
|
+
bindings: {},
|
|
189
|
+
},
|
|
190
|
+
),
|
|
191
|
+
),
|
|
156
192
|
),
|
|
157
|
-
Effect.tap(Effect.logInfo),
|
|
158
193
|
),
|
|
159
|
-
);
|
|
160
|
-
return providers.pipe(
|
|
161
|
-
Layer.provideMerge(stackLayer),
|
|
162
194
|
Layer.buildWithScope(scope),
|
|
163
|
-
)
|
|
164
|
-
|
|
195
|
+
),
|
|
196
|
+
),
|
|
165
197
|
Effect.flatMap((context) =>
|
|
166
198
|
Effect.all([
|
|
167
199
|
effect,
|
|
@@ -175,7 +207,7 @@ export const make =
|
|
|
175
207
|
> => ({
|
|
176
208
|
...stack,
|
|
177
209
|
output,
|
|
178
|
-
services,
|
|
210
|
+
services: Context.merge(services, context),
|
|
179
211
|
}),
|
|
180
212
|
),
|
|
181
213
|
Effect.provideContext(context),
|
|
@@ -186,3 +218,46 @@ export const make =
|
|
|
186
218
|
export const CurrentStack = Effect.serviceOption(Stack)
|
|
187
219
|
.asEffect()
|
|
188
220
|
.pipe(Effect.map(Option.getOrUndefined));
|
|
221
|
+
|
|
222
|
+
const platform = Layer.mergeAll(
|
|
223
|
+
PlatformServices,
|
|
224
|
+
FetchHttpClient.layer,
|
|
225
|
+
Logger.layer([fileLogger("out")]),
|
|
226
|
+
);
|
|
227
|
+
// override alchemy state store, CLI/reporting, state, and Config
|
|
228
|
+
const alchemy = Layer.mergeAll(
|
|
229
|
+
// CLI.inkCLI(),
|
|
230
|
+
// optional
|
|
231
|
+
AlchemyContextLive,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
export const evalStack = <A, B, Err, Req>(
|
|
235
|
+
effect: StackEffect<CompiledStack<A>, Stage | AlchemyContext>,
|
|
236
|
+
fn: (stack: CompiledStack<A>) => Effect.Effect<B, Err, Req>,
|
|
237
|
+
options: {
|
|
238
|
+
stage: string;
|
|
239
|
+
},
|
|
240
|
+
) =>
|
|
241
|
+
Effect.gen(function* () {
|
|
242
|
+
const stack = yield* effect;
|
|
243
|
+
const configProvider = yield* loadConfigProvider(Option.none());
|
|
244
|
+
|
|
245
|
+
return yield* fn(stack).pipe(
|
|
246
|
+
provideFreshArtifactStore,
|
|
247
|
+
Effect.provide(stack.services),
|
|
248
|
+
Effect.provide(Layer.succeed(ConfigProvider, configProvider)),
|
|
249
|
+
);
|
|
250
|
+
}).pipe(
|
|
251
|
+
Effect.provide(
|
|
252
|
+
Layer.effect(
|
|
253
|
+
AuthProviders,
|
|
254
|
+
Effect.serviceOption(AuthProviders).pipe(
|
|
255
|
+
Effect.map(Option.getOrElse(() => ({}))),
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
),
|
|
259
|
+
Effect.provide(Layer.succeed(Stage, options.stage)),
|
|
260
|
+
Effect.provide(inkCLIAutoApprove()),
|
|
261
|
+
Effect.provide(Layer.provideMerge(alchemy, platform)),
|
|
262
|
+
Effect.scoped,
|
|
263
|
+
);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Redacted from "effect/Redacted";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as HttpApi from "effect/unstable/httpapi/HttpApi";
|
|
7
|
+
import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint";
|
|
8
|
+
import * as HttpApiError from "effect/unstable/httpapi/HttpApiError";
|
|
9
|
+
import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";
|
|
10
|
+
import * as HttpApiMiddleware from "effect/unstable/httpapi/HttpApiMiddleware";
|
|
11
|
+
import * as HttpApiSchema from "effect/unstable/httpapi/HttpApiSchema";
|
|
12
|
+
import * as HttpApiSecurity from "effect/unstable/httpapi/HttpApiSecurity";
|
|
13
|
+
|
|
14
|
+
export const ResourceStateSchema = Schema.Any;
|
|
15
|
+
|
|
16
|
+
export class BearerTokenValidator extends Context.Service<
|
|
17
|
+
BearerTokenValidator,
|
|
18
|
+
{
|
|
19
|
+
readonly validate: (
|
|
20
|
+
token: string,
|
|
21
|
+
) => Effect.Effect<void, HttpApiError.Unauthorized>;
|
|
22
|
+
}
|
|
23
|
+
>()("alchemy/State/BearerTokenValidator") {}
|
|
24
|
+
|
|
25
|
+
export class StateAuth extends HttpApiMiddleware.Service<
|
|
26
|
+
StateAuth,
|
|
27
|
+
{ requires: BearerTokenValidator }
|
|
28
|
+
>()("alchemy/State/StateAuth", {
|
|
29
|
+
security: {
|
|
30
|
+
bearer: HttpApiSecurity.bearer,
|
|
31
|
+
},
|
|
32
|
+
error: HttpApiError.UnauthorizedNoContent,
|
|
33
|
+
}) {}
|
|
34
|
+
|
|
35
|
+
export const StateAuthLive: Layer.Layer<
|
|
36
|
+
StateAuth,
|
|
37
|
+
never,
|
|
38
|
+
BearerTokenValidator
|
|
39
|
+
> = Layer.effect(
|
|
40
|
+
StateAuth,
|
|
41
|
+
Effect.gen(function* () {
|
|
42
|
+
const validator = yield* BearerTokenValidator;
|
|
43
|
+
return {
|
|
44
|
+
bearer: (httpEffect, { credential }) =>
|
|
45
|
+
validator
|
|
46
|
+
.validate(Redacted.value(credential))
|
|
47
|
+
.pipe(Effect.flatMap(() => httpEffect)),
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/** `stack` path segment for nested REST resources. */
|
|
53
|
+
const StackParams = Schema.Struct({
|
|
54
|
+
stack: Schema.String,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** Optional stage selector for stack deletion. */
|
|
58
|
+
const OptionalStageQuery = Schema.Struct({
|
|
59
|
+
stage: Schema.optional(Schema.String),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** `(stack, stage)` path segments shared by stage-scoped endpoints. */
|
|
63
|
+
const StackStage = Schema.Struct({
|
|
64
|
+
stack: Schema.String,
|
|
65
|
+
stage: Schema.String,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** `(stack, stage, fqn)` path segments for a single resource. */
|
|
69
|
+
const ResourceKey = Schema.Struct({
|
|
70
|
+
stack: Schema.String,
|
|
71
|
+
stage: Schema.String,
|
|
72
|
+
fqn: Schema.String,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const ListStacks = HttpApiEndpoint.get("listStacks", "/state/stacks", {
|
|
76
|
+
success: Schema.Array(Schema.String),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const ListStages = HttpApiEndpoint.get(
|
|
80
|
+
"listStages",
|
|
81
|
+
"/state/stacks/:stack/stages",
|
|
82
|
+
{
|
|
83
|
+
params: StackParams,
|
|
84
|
+
success: Schema.Array(Schema.String),
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
export const ListResources = HttpApiEndpoint.get(
|
|
89
|
+
"listResources",
|
|
90
|
+
"/state/stacks/:stack/stages/:stage/resources",
|
|
91
|
+
{
|
|
92
|
+
params: StackStage,
|
|
93
|
+
success: Schema.Array(Schema.String),
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
export const GetState = HttpApiEndpoint.get(
|
|
98
|
+
"getState",
|
|
99
|
+
"/state/stacks/:stack/stages/:stage/resources/:fqn",
|
|
100
|
+
{
|
|
101
|
+
params: ResourceKey,
|
|
102
|
+
success: Schema.UndefinedOr(ResourceStateSchema),
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export const SetState = HttpApiEndpoint.put(
|
|
107
|
+
"setState",
|
|
108
|
+
"/state/stacks/:stack/stages/:stage/resources/:fqn",
|
|
109
|
+
{
|
|
110
|
+
params: ResourceKey,
|
|
111
|
+
payload: ResourceStateSchema,
|
|
112
|
+
success: ResourceStateSchema,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
export const DeleteState = HttpApiEndpoint.delete(
|
|
117
|
+
"deleteState",
|
|
118
|
+
"/state/stacks/:stack/stages/:stage/resources/:fqn",
|
|
119
|
+
{
|
|
120
|
+
params: ResourceKey,
|
|
121
|
+
success: HttpApiSchema.NoContent,
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
export const DeleteStack = HttpApiEndpoint.delete(
|
|
126
|
+
"deleteStack",
|
|
127
|
+
"/state/stacks/:stack",
|
|
128
|
+
{
|
|
129
|
+
params: StackParams,
|
|
130
|
+
query: OptionalStageQuery,
|
|
131
|
+
success: HttpApiSchema.NoContent,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
export const GetReplacedResources = HttpApiEndpoint.get(
|
|
136
|
+
"getReplacedResources",
|
|
137
|
+
"/state/stacks/:stack/stages/:stage/replaced-resources",
|
|
138
|
+
{
|
|
139
|
+
params: StackStage,
|
|
140
|
+
success: Schema.Array(ResourceStateSchema),
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
export class StateGroup extends HttpApiGroup.make("state")
|
|
145
|
+
.add(ListStacks)
|
|
146
|
+
.add(ListStages)
|
|
147
|
+
.add(ListResources)
|
|
148
|
+
.add(GetState)
|
|
149
|
+
.add(SetState)
|
|
150
|
+
.add(DeleteState)
|
|
151
|
+
.add(GetReplacedResources)
|
|
152
|
+
.add(DeleteStack)
|
|
153
|
+
.middleware(StateAuth) {}
|
|
154
|
+
|
|
155
|
+
export class StateApi extends HttpApi.make("alchemy-state").add(StateGroup) {}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Schedule from "effect/Schedule";
|
|
3
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
4
|
+
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
5
|
+
import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
|
|
6
|
+
import { StateApi } from "./HttpStateApi.ts";
|
|
7
|
+
|
|
8
|
+
import type { ReplacedResourceState, ResourceState } from "./ResourceState.ts";
|
|
9
|
+
import { StateStoreError, type StateService } from "./State.ts";
|
|
10
|
+
import { encodeState, reviveStateRecursive } from "./StateEncoding.ts";
|
|
11
|
+
|
|
12
|
+
export interface HttpStateStoreProps {
|
|
13
|
+
url: string;
|
|
14
|
+
/** Bearer token used to authenticate every request. */
|
|
15
|
+
authToken: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const makeHttpStateStore = ({ url, authToken }: HttpStateStoreProps) =>
|
|
19
|
+
Effect.gen(function* () {
|
|
20
|
+
const apiClient = yield* HttpApiClient.make(StateApi, {
|
|
21
|
+
baseUrl: url,
|
|
22
|
+
transformClient: HttpClient.mapRequest(
|
|
23
|
+
HttpClientRequest.bearerToken(authToken),
|
|
24
|
+
),
|
|
25
|
+
});
|
|
26
|
+
const state = apiClient.state;
|
|
27
|
+
|
|
28
|
+
const service: StateService = {
|
|
29
|
+
listStacks: () =>
|
|
30
|
+
state.listStacks().pipe(
|
|
31
|
+
Effect.map((stacks) => [...stacks]),
|
|
32
|
+
mapStateStoreError,
|
|
33
|
+
),
|
|
34
|
+
listStages: (stack) =>
|
|
35
|
+
state.listStages({ params: { stack } }).pipe(mapStateStoreError),
|
|
36
|
+
list: (request) =>
|
|
37
|
+
state.listResources({ params: request }).pipe(mapStateStoreError),
|
|
38
|
+
get: (request) =>
|
|
39
|
+
state
|
|
40
|
+
.getState({
|
|
41
|
+
params: {
|
|
42
|
+
stack: request.stack,
|
|
43
|
+
stage: request.stage,
|
|
44
|
+
fqn: encodeURIComponent(request.fqn),
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
.pipe(
|
|
48
|
+
Effect.map((s) =>
|
|
49
|
+
s == null
|
|
50
|
+
? undefined
|
|
51
|
+
: (reviveStateRecursive(s) as ResourceState),
|
|
52
|
+
),
|
|
53
|
+
mapStateStoreError,
|
|
54
|
+
),
|
|
55
|
+
getReplacedResources: (request) =>
|
|
56
|
+
state.getReplacedResources({ params: request }).pipe(
|
|
57
|
+
Effect.map((resources) =>
|
|
58
|
+
resources.map(
|
|
59
|
+
(s) => reviveStateRecursive(s) as ReplacedResourceState,
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
mapStateStoreError,
|
|
63
|
+
),
|
|
64
|
+
set: <V extends ResourceState>(request: {
|
|
65
|
+
stack: string;
|
|
66
|
+
stage: string;
|
|
67
|
+
fqn: string;
|
|
68
|
+
value: V;
|
|
69
|
+
}) =>
|
|
70
|
+
state
|
|
71
|
+
.setState({
|
|
72
|
+
params: {
|
|
73
|
+
stack: request.stack,
|
|
74
|
+
stage: request.stage,
|
|
75
|
+
fqn: encodeURIComponent(request.fqn),
|
|
76
|
+
},
|
|
77
|
+
payload: encodeState(request.value),
|
|
78
|
+
})
|
|
79
|
+
.pipe(
|
|
80
|
+
// Server echoes the stored value, but the client already
|
|
81
|
+
// has the canonical object (including any Redacted<T>
|
|
82
|
+
// instances); returning the input avoids a lossy round-trip.
|
|
83
|
+
Effect.map(() => request.value),
|
|
84
|
+
mapStateStoreError,
|
|
85
|
+
),
|
|
86
|
+
delete: (request) =>
|
|
87
|
+
state
|
|
88
|
+
.deleteState({
|
|
89
|
+
params: {
|
|
90
|
+
stack: request.stack,
|
|
91
|
+
stage: request.stage,
|
|
92
|
+
fqn: encodeURIComponent(request.fqn),
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
.pipe(Effect.asVoid, mapStateStoreError),
|
|
96
|
+
deleteStack: (request) =>
|
|
97
|
+
state
|
|
98
|
+
.deleteStack({
|
|
99
|
+
params: { stack: request.stack },
|
|
100
|
+
query: request.stage === undefined ? {} : { stage: request.stage },
|
|
101
|
+
})
|
|
102
|
+
.pipe(Effect.asVoid, mapStateStoreError),
|
|
103
|
+
};
|
|
104
|
+
return service;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Predicate over an `HttpClientError`-shaped failure that returns `true`
|
|
109
|
+
* for failures we expect to clear up on their own.
|
|
110
|
+
*
|
|
111
|
+
* We retry:
|
|
112
|
+
* - all transport-level errors (no `response` — DNS, TCP reset, TLS,
|
|
113
|
+
* abort, etc.)
|
|
114
|
+
* - 408 (request timeout) and 429 (rate limit)
|
|
115
|
+
* - 404, which is normal in the seconds after a worker is first
|
|
116
|
+
* deployed and the route hasn't propagated yet
|
|
117
|
+
* - every 5xx
|
|
118
|
+
*
|
|
119
|
+
* Anything else (400/401/403/etc.) is a real client-side problem and
|
|
120
|
+
* shouldn't be hidden behind retries.
|
|
121
|
+
*/
|
|
122
|
+
const isTransient = (e: any): boolean => {
|
|
123
|
+
if (e?._tag !== "HttpClientError") return false;
|
|
124
|
+
const status: number | undefined = e.response?.status;
|
|
125
|
+
if (status == null) return true;
|
|
126
|
+
if (status === 404 || status === 408 || status === 429) return true;
|
|
127
|
+
return status >= 500 && status < 600;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const retryTransient = <A, Err, Req>(eff: Effect.Effect<A, Err, Req>) =>
|
|
131
|
+
Effect.retry(eff, {
|
|
132
|
+
while: isTransient,
|
|
133
|
+
// Exponential backoff capped at 2s, no attempt limit. The state
|
|
134
|
+
// store is on the deploy critical path; we'd rather block than
|
|
135
|
+
// surface a transient blip as a hard failure (which historically
|
|
136
|
+
// stranded `syncState` mid-flight, leaving the remote store
|
|
137
|
+
// partially populated).
|
|
138
|
+
schedule: Schedule.exponential(100).pipe(
|
|
139
|
+
Schedule.either(Schedule.spaced("2 seconds")),
|
|
140
|
+
),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/** Collapse any client failure into a {@link StateStoreError}. */
|
|
144
|
+
const mapStateStoreError = <A, E, R>(eff: Effect.Effect<A, E, R>) =>
|
|
145
|
+
eff.pipe(
|
|
146
|
+
retryTransient,
|
|
147
|
+
Effect.catch((e: E) =>
|
|
148
|
+
Effect.fail(
|
|
149
|
+
new StateStoreError({
|
|
150
|
+
message: e instanceof Error ? e.message : String(e),
|
|
151
|
+
cause: e instanceof Error ? e : undefined,
|
|
152
|
+
}),
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
) as Effect.Effect<A, StateStoreError, R>;
|
|
@@ -1,32 +1,23 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import * as Layer from "effect/Layer";
|
|
3
3
|
import type { ResourceState } from "./ResourceState.ts";
|
|
4
|
-
import { State
|
|
4
|
+
import { State } from "./State.ts";
|
|
5
5
|
|
|
6
6
|
type StackId = string;
|
|
7
7
|
type StageId = string;
|
|
8
8
|
type Fqn = string;
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const inMemoryState = (
|
|
11
11
|
initialState: Record<
|
|
12
12
|
StackId,
|
|
13
13
|
Record<StageId, Record<Fqn, ResourceState>>
|
|
14
14
|
> = {},
|
|
15
|
-
) =>
|
|
16
|
-
Layer.succeed(State, InMemoryService(initialState)) as Layer.Layer<
|
|
17
|
-
State,
|
|
18
|
-
never,
|
|
19
|
-
never
|
|
20
|
-
>;
|
|
15
|
+
) => Layer.succeed(State, InMemoryService(initialState));
|
|
21
16
|
|
|
22
17
|
export const InMemoryService = (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
> = {},
|
|
27
|
-
) => {
|
|
28
|
-
const state = initialState;
|
|
29
|
-
return {
|
|
18
|
+
state: Record<StackId, Record<StageId, Record<Fqn, ResourceState>>> = {},
|
|
19
|
+
) =>
|
|
20
|
+
State.of({
|
|
30
21
|
listStacks: () => Effect.succeed(Array.from(Object.keys(state))),
|
|
31
22
|
listStages: (stack: string) =>
|
|
32
23
|
Effect.succeed(
|
|
@@ -78,9 +69,16 @@ export const InMemoryService = (
|
|
|
78
69
|
stage: string;
|
|
79
70
|
fqn: string;
|
|
80
71
|
}) => Effect.succeed(delete state[stack]?.[stage]?.[fqn]),
|
|
72
|
+
deleteStack: ({ stack, stage }: { stack: string; stage?: string }) =>
|
|
73
|
+
Effect.sync(() => {
|
|
74
|
+
if (stage === undefined) {
|
|
75
|
+
delete state[stack];
|
|
76
|
+
} else {
|
|
77
|
+
delete state[stack]?.[stage];
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
81
80
|
list: ({ stack, stage }: { stack: string; stage: string }) =>
|
|
82
81
|
Effect.succeed(
|
|
83
82
|
Array.from(Object.keys(state[stack]?.[stage] ?? {}) ?? []),
|
|
84
83
|
),
|
|
85
|
-
}
|
|
86
|
-
};
|
|
84
|
+
});
|
package/src/State/LocalState.ts
CHANGED
|
@@ -3,53 +3,13 @@ import * as FileSystem from "effect/FileSystem";
|
|
|
3
3
|
import * as Layer from "effect/Layer";
|
|
4
4
|
import * as Path from "effect/Path";
|
|
5
5
|
import type { PlatformError } from "effect/PlatformError";
|
|
6
|
-
import * as Redacted from "effect/Redacted";
|
|
7
6
|
import { decodeFqn, encodeFqn } from "../FQN.ts";
|
|
8
|
-
import { isResource } from "../Resource.ts";
|
|
9
7
|
import { State, StateStoreError, type StateService } from "./State.ts";
|
|
8
|
+
import { encodeState, reviveState } from "./StateEncoding.ts";
|
|
10
9
|
|
|
11
|
-
const
|
|
10
|
+
export const localState = () => Layer.effect(State, makeLocalState());
|
|
12
11
|
|
|
13
|
-
const
|
|
14
|
-
if (value === null || value === undefined) return value;
|
|
15
|
-
if (Redacted.isRedacted(value)) {
|
|
16
|
-
return {
|
|
17
|
-
[REDACTED_MARKER]: encodeState(Redacted.value(value)),
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
if (isResource(value)) {
|
|
21
|
-
return {
|
|
22
|
-
id: value.LogicalId,
|
|
23
|
-
type: value.Type,
|
|
24
|
-
props: encodeState(value.Props),
|
|
25
|
-
attr: encodeState(value.Attributes),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
if (Array.isArray(value)) return value.map(encodeState);
|
|
29
|
-
if (typeof value === "object") {
|
|
30
|
-
const result: Record<string, unknown> = {};
|
|
31
|
-
for (const [k, v] of Object.entries(value)) {
|
|
32
|
-
result[k] = encodeState(v);
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const reviveState = (_key: string, value: unknown): unknown => {
|
|
40
|
-
if (
|
|
41
|
-
value !== null &&
|
|
42
|
-
typeof value === "object" &&
|
|
43
|
-
!Array.isArray(value) &&
|
|
44
|
-
REDACTED_MARKER in value
|
|
45
|
-
) {
|
|
46
|
-
return Redacted.make((value as Record<string, unknown>)[REDACTED_MARKER]);
|
|
47
|
-
}
|
|
48
|
-
return value;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export const LocalState = Layer.effect(
|
|
52
|
-
State,
|
|
12
|
+
export const makeLocalState = () =>
|
|
53
13
|
Effect.gen(function* () {
|
|
54
14
|
const fs = yield* FileSystem.FileSystem;
|
|
55
15
|
const path = yield* Path.Path;
|
|
@@ -132,6 +92,15 @@ export const LocalState = Layer.effect(
|
|
|
132
92
|
Effect.map(() => request.value),
|
|
133
93
|
),
|
|
134
94
|
delete: (request) => fs.remove(resource(request)).pipe(recover),
|
|
95
|
+
deleteStack: ({ stack, stage }) =>
|
|
96
|
+
fs
|
|
97
|
+
.remove(
|
|
98
|
+
stage === undefined
|
|
99
|
+
? path.join(stateDir, stack)
|
|
100
|
+
: stageDir({ stack, stage }),
|
|
101
|
+
{ recursive: true },
|
|
102
|
+
)
|
|
103
|
+
.pipe(recover),
|
|
135
104
|
list: (request) =>
|
|
136
105
|
fs.readDirectory(stageDir(request)).pipe(
|
|
137
106
|
recover,
|
|
@@ -143,5 +112,4 @@ export const LocalState = Layer.effect(
|
|
|
143
112
|
),
|
|
144
113
|
};
|
|
145
114
|
return state;
|
|
146
|
-
})
|
|
147
|
-
);
|
|
115
|
+
});
|