| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; |
| 3 | import { recordStateStoreInit } from "../Telemetry/Metrics.ts"; |
| 4 | import { STATE_STORE_VERSION } from "./HttpStateApi.ts"; |
| 5 | import type { ResourceState } from "./ResourceState.ts"; |
| 6 | import { State, type PersistedState } from "./State.ts"; |
| 7 | |
| 8 | type StackId = string; |
| 9 | type StageId = string; |
| 10 | type Fqn = string; |
| 11 | |
| 12 | export const inMemoryState = ( |
| 13 | initialState: Record< |
| 14 | StackId, |
| 15 | Record<StageId, Record<Fqn, ResourceState>> |
| 16 | > = {}, |
| 17 | initialOutputs: Record<StackId, Record<StageId, unknown>> = {}, |
| 18 | ) => |
| 19 | Layer.effect( |
| 20 | State, |
| 21 | Effect.cached( |
| 22 | InMemoryService(initialState, initialOutputs).pipe(recordStateStoreInit), |
| 23 | ), |
| 24 | ); |
| 25 | |
| 26 | export const InMemoryService = ( |
| 27 | state: Record<StackId, Record<StageId, Record<Fqn, ResourceState>>> = {}, |
| 28 | outputs: Record<StackId, Record<StageId, unknown>> = {}, |
| 29 | ) => |
| 30 | State.of( |
| 31 | Effect.succeed({ |
| 32 | id: "inmemory", |
| 33 | getVersion: () => Effect.succeed(STATE_STORE_VERSION), |
| 34 | listStacks: () => Effect.succeed(Array.from(Object.keys(state))), |
| 35 | listStages: (stack: string) => |
| 36 | Effect.succeed( |
| 37 | Array.from(stack in state ? Object.keys(state[stack]) : []), |
| 38 | ), |
| 39 | get: ({ |
| 40 | stack, |
| 41 | stage, |
| 42 | fqn, |
| 43 | }: { |
| 44 | stack: string; |
| 45 | stage: string; |
| 46 | fqn: string; |
| 47 | }) => Effect.succeed(state[stack]?.[stage]?.[fqn]), |
| 48 | getReplacedResources: ({ |
| 49 | stack, |
| 50 | stage, |
| 51 | }: { |
| 52 | stack: string; |
| 53 | stage: string; |
| 54 | }) => |
| 55 | Effect.succeed( |
| 56 | Array.from(Object.values(state[stack]?.[stage] ?? {}) ?? []).filter( |
| 57 | (s) => s.status === "replaced", |
| 58 | ), |
| 59 | ), |
| 60 | set: <V extends PersistedState>({ |
| 61 | stack, |
| 62 | stage, |
| 63 | fqn, |
| 64 | value, |
| 65 | }: { |
| 66 | stack: string; |
| 67 | stage: string; |
| 68 | fqn: string; |
| 69 | value: V; |
| 70 | }) => |
| 71 | Effect.sync(() => { |
| 72 | const stackState = (state[stack] ??= {}); |
| 73 | const stageState = (stackState[stage] ??= {}); |
| 74 | stageState[fqn] = value as ResourceState; |
| 75 | return value; |
| 76 | }), |
| 77 | delete: ({ |
| 78 | stack, |
| 79 | stage, |
| 80 | fqn, |
| 81 | }: { |
| 82 | stack: string; |
| 83 | stage: string; |
| 84 | fqn: string; |
| 85 | }) => Effect.sync(() => delete state[stack]?.[stage]?.[fqn]), |
| 86 | deleteStack: ({ stack, stage }: { stack: string; stage?: string }) => |
| 87 | Effect.sync(() => { |
| 88 | if (stage === undefined) { |
| 89 | delete state[stack]; |
| 90 | delete outputs[stack]; |
| 91 | } else { |
| 92 | delete state[stack]?.[stage]; |
| 93 | delete outputs[stack]?.[stage]; |
| 94 | } |
| 95 | }), |
| 96 | list: ({ stack, stage }: { stack: string; stage: string }) => |
| 97 | Effect.succeed( |
| 98 | Array.from(Object.keys(state[stack]?.[stage] ?? {}) ?? []), |
| 99 | ), |
| 100 | getOutput: ({ stack, stage }: { stack: string; stage: string }) => |
| 101 | Effect.succeed(outputs[stack]?.[stage]), |
| 102 | setOutput: ({ |
| 103 | stack, |
| 104 | stage, |
| 105 | value, |
| 106 | }: { |
| 107 | stack: string; |
| 108 | stage: string; |
| 109 | value: unknown; |
| 110 | }) => |
| 111 | Effect.sync(() => { |
| 112 | const stackOutputs = (outputs[stack] ??= {}); |
| 113 | stackOutputs[stage] = value; |
| 114 | return value; |
| 115 | }), |
| 116 | }), |
| 117 | ); |