| author | |
| committer | |
| log | c71e71b5acacb74e201b3f4692ef08ed1dce3a28 |
| tree | 41607073b3380187d120c668cf8ef977105940cf |
| parent | bbf4e6bd3cf4dc1f73f3a0735092cf2f5e88c347 |
11 files changed, 307 insertions(+), 210 deletions(-)
apps/server/package.json+2| ... | ... | @@ -17,6 +17,8 @@ |
| 17 | 17 | "test": "vitest run" |
| 18 | 18 | }, |
| 19 | 19 | "dependencies": { |
| 20 | "@effect/platform": "^0.94.5", | |
| 21 | "@effect/platform-node": "^0.104.1", | |
| 20 | 22 | "@effect/sql": "^0.49.0", |
| 21 | 23 | "@effect/sql-sqlite-node": "^0.50.1", |
| 22 | 24 | "effect": "^3.19.19", |
apps/server/src/orchestration/engine.test.ts+37-27| ... | ... | @@ -8,6 +8,7 @@ import { afterEach, describe, expect, it } from "vitest"; |
| 8 | 8 | |
| 9 | 9 | import type { OrchestrationEventRepositoryShape } from "../persistence/Services/OrchestrationEvents"; |
| 10 | 10 | import { OrchestrationEngine } from "./engine"; |
| 11 | import { createOrchestrationSystem } from "./runtime"; | |
| 11 | 12 | |
| 12 | 13 | const tempDirs: string[] = []; |
| 13 | 14 | |
| ... | ... | @@ -30,8 +31,8 @@ describe("OrchestrationEngine", () => { |
| 30 | 31 | const projectId = "project-1"; |
| 31 | 32 | const threadId = "thread-1"; |
| 32 | 33 | |
| 33 | const engineA = new OrchestrationEngine(stateDir); | |
| 34 | await engineA.start(); | |
| 34 | const firstSystem = await createOrchestrationSystem(stateDir); | |
| 35 | const engineA = firstSystem.engine; | |
| 35 | 36 | await engineA.dispatch({ |
| 36 | 37 | type: "project.create", |
| 37 | 38 | commandId: "cmd-1", |
| ... | ... | @@ -63,19 +64,19 @@ describe("OrchestrationEngine", () => { |
| 63 | 64 | createdAt, |
| 64 | 65 | }); |
| 65 | 66 | const snapshotA = engineA.getSnapshot(); |
| 66 | await engineA.stop(); | |
| 67 | await firstSystem.dispose(); | |
| 67 | 68 | |
| 68 | const engineB = new OrchestrationEngine(stateDir); | |
| 69 | await engineB.start(); | |
| 69 | const secondSystem = await createOrchestrationSystem(stateDir); | |
| 70 | const engineB = secondSystem.engine; | |
| 70 | 71 | const snapshotB = engineB.getSnapshot(); |
| 71 | 72 | expect(snapshotB).toEqual(snapshotA); |
| 72 | await engineB.stop(); | |
| 73 | await secondSystem.dispose(); | |
| 73 | 74 | }); |
| 74 | 75 | |
| 75 | 76 | it("fans out read-model updates to subscribers", async () => { |
| 76 | 77 | const stateDir = makeTempDir("t3code-orchestration-fanout-"); |
| 77 | const engine = new OrchestrationEngine(stateDir); | |
| 78 | await engine.start(); | |
| 78 | const system = await createOrchestrationSystem(stateDir); | |
| 79 | const engine = system.engine; | |
| 79 | 80 | const updates: number[] = []; |
| 80 | 81 | const unsubscribe = engine.subscribeToReadModel((snapshot) => { |
| 81 | 82 | updates.push(snapshot.sequence); |
| ... | ... | @@ -91,13 +92,13 @@ describe("OrchestrationEngine", () => { |
| 91 | 92 | }); |
| 92 | 93 | unsubscribe(); |
| 93 | 94 | expect(updates.length).toBeGreaterThan(0); |
| 94 | await engine.stop(); | |
| 95 | await system.dispose(); | |
| 95 | 96 | }); |
| 96 | 97 | |
| 97 | 98 | it("replays append-only events from sequence", async () => { |
| 98 | 99 | const stateDir = makeTempDir("t3code-orchestration-replay-"); |
| 99 | const engine = new OrchestrationEngine(stateDir); | |
| 100 | await engine.start(); | |
| 100 | const system = await createOrchestrationSystem(stateDir); | |
| 101 | const engine = system.engine; | |
| 101 | 102 | await engine.dispatch({ |
| 102 | 103 | type: "project.create", |
| 103 | 104 | commandId: "cmd-a", |
| ... | ... | @@ -117,14 +118,14 @@ describe("OrchestrationEngine", () => { |
| 117 | 118 | expect(events.length).toBe(2); |
| 118 | 119 | expect(events[0]?.type).toBe("project.created"); |
| 119 | 120 | expect(events[1]?.type).toBe("project.deleted"); |
| 120 | await engine.stop(); | |
| 121 | await system.dispose(); | |
| 121 | 122 | }); |
| 122 | 123 | |
| 123 | 124 | it("stores completed turn summaries even when no files changed", async () => { |
| 124 | 125 | const stateDir = makeTempDir("t3code-orchestration-turn-diff-"); |
| 125 | const engine = new OrchestrationEngine(stateDir); | |
| 126 | const firstSystem = await createOrchestrationSystem(stateDir); | |
| 127 | const engine = firstSystem.engine; | |
| 126 | 128 | const completedAt = new Date().toISOString(); |
| 127 | await engine.start(); | |
| 128 | 129 | await engine.dispatch({ |
| 129 | 130 | type: "project.create", |
| 130 | 131 | commandId: "cmd-project-turn-diff", |
| ... | ... | @@ -167,10 +168,10 @@ describe("OrchestrationEngine", () => { |
| 167 | 168 | checkpointTurnCount: 1, |
| 168 | 169 | }, |
| 169 | 170 | ]); |
| 170 | await engine.stop(); | |
| 171 | await firstSystem.dispose(); | |
| 171 | 172 | |
| 172 | const restartedEngine = new OrchestrationEngine(stateDir); | |
| 173 | await restartedEngine.start(); | |
| 173 | const secondSystem = await createOrchestrationSystem(stateDir); | |
| 174 | const restartedEngine = secondSystem.engine; | |
| 174 | 175 | const restartedThread = restartedEngine |
| 175 | 176 | .getSnapshot() |
| 176 | 177 | .threads.find((thread) => thread.id === "thread-turn-diff"); |
| ... | ... | @@ -183,14 +184,14 @@ describe("OrchestrationEngine", () => { |
| 183 | 184 | checkpointTurnCount: 1, |
| 184 | 185 | }, |
| 185 | 186 | ]); |
| 186 | await restartedEngine.stop(); | |
| 187 | await secondSystem.dispose(); | |
| 187 | 188 | }); |
| 188 | 189 | |
| 189 | 190 | it("reverts thread messages and turn summaries to a checkpoint", async () => { |
| 190 | 191 | const stateDir = makeTempDir("t3code-orchestration-revert-"); |
| 191 | const engine = new OrchestrationEngine(stateDir); | |
| 192 | const firstSystem = await createOrchestrationSystem(stateDir); | |
| 193 | const engine = firstSystem.engine; | |
| 192 | 194 | const createdAt = new Date().toISOString(); |
| 193 | await engine.start(); | |
| 194 | 195 | await engine.dispatch({ |
| 195 | 196 | type: "project.create", |
| 196 | 197 | commandId: "cmd-project-revert", |
| ... | ... | @@ -291,10 +292,10 @@ describe("OrchestrationEngine", () => { |
| 291 | 292 | expect(thread?.messages.map((message) => message.id)).toEqual(["user-1", "assistant:turn-1"]); |
| 292 | 293 | expect(thread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]); |
| 293 | 294 | expect(thread?.latestTurnId).toBe("turn-1"); |
| 294 | await engine.stop(); | |
| 295 | await firstSystem.dispose(); | |
| 295 | 296 | |
| 296 | const restarted = new OrchestrationEngine(stateDir); | |
| 297 | await restarted.start(); | |
| 297 | const secondSystem = await createOrchestrationSystem(stateDir); | |
| 298 | const restarted = secondSystem.engine; | |
| 298 | 299 | const restartedThread = restarted |
| 299 | 300 | .getSnapshot() |
| 300 | 301 | .threads.find((entry) => entry.id === "thread-revert"); |
| ... | ... | @@ -303,12 +304,22 @@ describe("OrchestrationEngine", () => { |
| 303 | 304 | "assistant:turn-1", |
| 304 | 305 | ]); |
| 305 | 306 | expect(restartedThread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]); |
| 306 | await restarted.stop(); | |
| 307 | await secondSystem.dispose(); | |
| 307 | 308 | }); |
| 308 | 309 | |
| 309 | 310 | it("allows stop to be called multiple times", async () => { |
| 310 | const stateDir = makeTempDir("t3code-orchestration-stop-"); | |
| 311 | const engine = new OrchestrationEngine(stateDir); | |
| 311 | const inMemoryStore: OrchestrationEventRepositoryShape = { | |
| 312 | append(event) { | |
| 313 | return Effect.succeed({ ...event, sequence: 1 }); | |
| 314 | }, | |
| 315 | readFromSequence() { | |
| 316 | return Effect.succeed([]); | |
| 317 | }, | |
| 318 | readAll() { | |
| 319 | return Effect.succeed([]); | |
| 320 | }, | |
| 321 | }; | |
| 322 | const engine = new OrchestrationEngine(inMemoryStore); | |
| 312 | 323 | await engine.start(); |
| 313 | 324 | await engine.stop(); |
| 314 | 325 | await expect(engine.stop()).resolves.toBeUndefined(); |
| ... | ... | @@ -341,7 +352,6 @@ describe("OrchestrationEngine", () => { |
| 341 | 352 | readAll() { |
| 342 | 353 | return Effect.succeed(events); |
| 343 | 354 | }, |
| 344 | close() {}, | |
| 345 | 355 | }; |
| 346 | 356 | |
| 347 | 357 | const engine = new OrchestrationEngine(flakyStore); |
apps/server/src/orchestration/engine.ts+6-13| ... | ... | @@ -1,5 +1,3 @@ |
| 1 | import path from "node:path"; | |
| 2 | ||
| 3 | 1 | import type { |
| 4 | 2 | OrchestrationCommand, |
| 5 | 3 | OrchestrationEvent, |
| ... | ... | @@ -21,7 +19,6 @@ import { |
| 21 | 19 | |
| 22 | 20 | import { createLogger } from "../logger"; |
| 23 | 21 | import type { OrchestrationEventRepositoryShape } from "../persistence/Services/OrchestrationEvents"; |
| 24 | import { makeSqliteOrchestrationEventRepository } from "../persistence/Layers/OrchestrationEvents"; | |
| 25 | 22 | import { createEmptyReadModel, reduceEvent } from "./reducer"; |
| 26 | 23 | import { UI_ENTITY_CONTRACTS } from "./uiContractInventory"; |
| 27 | 24 | |
| ... | ... | @@ -215,15 +212,8 @@ export class OrchestrationEngine { |
| 215 | 212 | private readonly readModelListeners = new Set<(snapshot: OrchestrationReadModel) => void>(); |
| 216 | 213 | private readonly domainEventListeners = new Set<(event: OrchestrationEvent) => void>(); |
| 217 | 214 | |
| 218 | constructor(stateDir: string); | |
| 219 | constructor(eventStore: OrchestrationEventRepositoryShape); | |
| 220 | constructor(stateDirOrEventStore: string | OrchestrationEventRepositoryShape) { | |
| 221 | if (typeof stateDirOrEventStore === "string") { | |
| 222 | const dbPath = path.join(stateDirOrEventStore, "orchestration.sqlite"); | |
| 223 | this.eventStore = makeSqliteOrchestrationEventRepository(dbPath); | |
| 224 | } else { | |
| 225 | this.eventStore = stateDirOrEventStore; | |
| 226 | } | |
| 215 | constructor(eventStore: OrchestrationEventRepositoryShape) { | |
| 216 | this.eventStore = eventStore; | |
| 227 | 217 | this.readModel = createEmptyReadModel(new Date().toISOString()); |
| 228 | 218 | this.commandQueue = Runtime.runSync(this.runtime)(Queue.unbounded<CommandEnvelope>()); |
| 229 | 219 | this.readModelPubSub = Runtime.runSync(this.runtime)( |
| ... | ... | @@ -279,6 +269,10 @@ export class OrchestrationEngine { |
| 279 | 269 | } |
| 280 | 270 | |
| 281 | 271 | async start(): Promise<void> { |
| 272 | if (this.workerFiber) { | |
| 273 | return; | |
| 274 | } | |
| 275 | ||
| 282 | 276 | await Runtime.runPromise(this.runtime)(this.bootstrapReadModel()); |
| 283 | 277 | |
| 284 | 278 | const worker = Stream.fromQueue(this.commandQueue).pipe( |
| ... | ... | @@ -297,7 +291,6 @@ export class OrchestrationEngine { |
| 297 | 291 | await Runtime.runPromise(this.runtime)(Fiber.interrupt(this.workerFiber)); |
| 298 | 292 | this.workerFiber = null; |
| 299 | 293 | } |
| 300 | this.eventStore.close(); | |
| 301 | 294 | } |
| 302 | 295 | |
| 303 | 296 | getSnapshot(): OrchestrationReadModel { |
apps/server/src/orchestration/layers.test.ts+7-7| ... | ... | @@ -4,7 +4,7 @@ import path from "node:path"; |
| 4 | 4 | |
| 5 | 5 | import { afterEach, describe, expect, it } from "vitest"; |
| 6 | 6 | |
| 7 | import { createOrchestrationEngine } from "./runtime"; | |
| 7 | import { createOrchestrationSystem } from "./runtime"; | |
| 8 | 8 | |
| 9 | 9 | const tempDirs: string[] = []; |
| 10 | 10 | |
| ... | ... | @@ -24,9 +24,9 @@ describe("orchestration layers", () => { |
| 24 | 24 | it("creates an engine through Context.Tag wiring", async () => { |
| 25 | 25 | const stateDir = makeTempDir("t3code-orchestration-layer-"); |
| 26 | 26 | const createdAt = new Date().toISOString(); |
| 27 | const engine = createOrchestrationEngine(stateDir); | |
| 27 | const firstSystem = await createOrchestrationSystem(stateDir); | |
| 28 | const { engine } = firstSystem; | |
| 28 | 29 | |
| 29 | await engine.start(); | |
| 30 | 30 | await engine.dispatch({ |
| 31 | 31 | type: "project.create", |
| 32 | 32 | commandId: "cmd-layer-1", |
| ... | ... | @@ -37,11 +37,11 @@ describe("orchestration layers", () => { |
| 37 | 37 | createdAt, |
| 38 | 38 | }); |
| 39 | 39 | const firstSnapshot = engine.getSnapshot(); |
| 40 | await engine.stop(); | |
| 40 | await firstSystem.dispose(); | |
| 41 | 41 | |
| 42 | const restarted = createOrchestrationEngine(stateDir); | |
| 43 | await restarted.start(); | |
| 42 | const secondSystem = await createOrchestrationSystem(stateDir); | |
| 43 | const restarted = secondSystem.engine; | |
| 44 | 44 | expect(restarted.getSnapshot()).toEqual(firstSnapshot); |
| 45 | await restarted.stop(); | |
| 45 | await secondSystem.dispose(); | |
| 46 | 46 | }); |
| 47 | 47 | }); |
apps/server/src/orchestration/layers.ts+13-8| ... | ... | @@ -3,23 +3,28 @@ import path from "node:path"; |
| 3 | 3 | import { Effect, Layer } from "effect"; |
| 4 | 4 | |
| 5 | 5 | import { OrchestrationEventRepository } from "../persistence/Services/OrchestrationEvents"; |
| 6 | import { makeSqliteOrchestrationEventRepository } from "../persistence/Layers/OrchestrationEvents"; | |
| 6 | import { makeSqliteOrchestrationEventRepositoryLive } from "../persistence/Layers/OrchestrationEvents"; | |
| 7 | 7 | import { OrchestrationEngine } from "./engine"; |
| 8 | 8 | import { OrchestrationConfig, OrchestrationEngineService } from "./services"; |
| 9 | 9 | |
| 10 | export const OrchestrationEventRepositoryLive = Layer.effect( | |
| 11 | OrchestrationEventRepository, | |
| 10 | export const OrchestrationEventRepositoryLive = Layer.unwrapEffect( | |
| 12 | 11 | Effect.map(OrchestrationConfig, ({ stateDir }) => { |
| 13 | 12 | const dbPath = path.join(stateDir, "orchestration.sqlite"); |
| 14 | return makeSqliteOrchestrationEventRepository(dbPath); | |
| 13 | return makeSqliteOrchestrationEventRepositoryLive(dbPath); | |
| 15 | 14 | }), |
| 16 | 15 | ); |
| 17 | 16 | |
| 18 | export const OrchestrationEngineLive = Layer.effect( | |
| 17 | export const OrchestrationEngineLive = Layer.scoped( | |
| 19 | 18 | OrchestrationEngineService, |
| 20 | Effect.map(OrchestrationEventRepository, (eventRepository) => { | |
| 21 | return new OrchestrationEngine(eventRepository); | |
| 22 | }), | |
| 19 | Effect.acquireRelease( | |
| 20 | Effect.gen(function* () { | |
| 21 | const eventRepository = yield* OrchestrationEventRepository; | |
| 22 | const engine = new OrchestrationEngine(eventRepository); | |
| 23 | yield* Effect.promise(() => engine.start()); | |
| 24 | return engine; | |
| 25 | }), | |
| 26 | (engine) => Effect.promise(() => engine.stop()), | |
| 27 | ), | |
| 23 | 28 | ); |
| 24 | 29 | |
| 25 | 30 | export const OrchestrationLive = OrchestrationEngineLive.pipe( |
apps/server/src/orchestration/runtime.ts+13-5| ... | ... | @@ -1,14 +1,22 @@ |
| 1 | import { Effect, Layer, Runtime } from "effect"; | |
| 1 | import { Layer, ManagedRuntime } from "effect"; | |
| 2 | 2 | |
| 3 | 3 | import type { OrchestrationEngine } from "./engine"; |
| 4 | 4 | import { OrchestrationLive } from "./layers"; |
| 5 | 5 | import { OrchestrationConfig, OrchestrationEngineService } from "./services"; |
| 6 | 6 | |
| 7 | export function createOrchestrationEngine(stateDir: string): OrchestrationEngine { | |
| 7 | export interface OrchestrationSystem { | |
| 8 | readonly engine: OrchestrationEngine; | |
| 9 | readonly dispose: () => Promise<void>; | |
| 10 | } | |
| 11 | ||
| 12 | export async function createOrchestrationSystem(stateDir: string): Promise<OrchestrationSystem> { | |
| 8 | 13 | const orchestrationLayer = OrchestrationLive.pipe( |
| 9 | 14 | Layer.provide(Layer.succeed(OrchestrationConfig, { stateDir })), |
| 10 | 15 | ); |
| 11 | return Runtime.runSync(Runtime.defaultRuntime)( | |
| 12 | Effect.provide(OrchestrationEngineService, orchestrationLayer), | |
| 13 | ); | |
| 16 | const runtime = ManagedRuntime.make(orchestrationLayer); | |
| 17 | const engine = await runtime.runPromise(OrchestrationEngineService); | |
| 18 | return { | |
| 19 | engine, | |
| 20 | dispose: () => runtime.dispose(), | |
| 21 | }; | |
| 14 | 22 | } |
apps/server/src/persistence/Layers/OrchestrationEvents.test.ts+16-11| ... | ... | @@ -3,10 +3,11 @@ import os from "node:os"; |
| 3 | 3 | import path from "node:path"; |
| 4 | 4 | import { DatabaseSync } from "node:sqlite"; |
| 5 | 5 | |
| 6 | import { Runtime } from "effect"; | |
| 6 | import { Runtime, ManagedRuntime } from "effect"; | |
| 7 | 7 | import { afterEach, describe, expect, it } from "vitest"; |
| 8 | 8 | |
| 9 | import { makeSqliteOrchestrationEventRepository } from "./OrchestrationEvents"; | |
| 9 | import { OrchestrationEventRepository } from "../Services/OrchestrationEvents"; | |
| 10 | import { makeSqliteOrchestrationEventRepositoryLive } from "./OrchestrationEvents"; | |
| 10 | 11 | |
| 11 | 12 | const tempDirs: string[] = []; |
| 12 | 13 | |
| ... | ... | @@ -22,14 +23,15 @@ afterEach(() => { |
| 22 | 23 | } |
| 23 | 24 | }); |
| 24 | 25 | |
| 25 | describe("SqliteEventStore", () => { | |
| 26 | describe("OrchestrationEventRepository", () => { | |
| 26 | 27 | it("persists and replays events across restarts", async () => { |
| 27 | 28 | const stateDir = makeTempDir("t3code-event-store-"); |
| 28 | 29 | const dbPath = path.join(stateDir, "orchestration.sqlite"); |
| 29 | 30 | const runtime = Runtime.defaultRuntime; |
| 30 | 31 | const createdAt = new Date().toISOString(); |
| 31 | 32 | |
| 32 | const first = makeSqliteOrchestrationEventRepository(dbPath); | |
| 33 | const firstRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath)); | |
| 34 | const first = await firstRuntime.runPromise(OrchestrationEventRepository); | |
| 33 | 35 | const saved = await Runtime.runPromise(runtime)( |
| 34 | 36 | first.append({ |
| 35 | 37 | eventId: "event-1", |
| ... | ... | @@ -42,12 +44,13 @@ describe("SqliteEventStore", () => { |
| 42 | 44 | }), |
| 43 | 45 | ); |
| 44 | 46 | expect(saved.sequence).toBe(1); |
| 45 | first.close(); | |
| 47 | await firstRuntime.dispose(); | |
| 46 | 48 | |
| 47 | const second = makeSqliteOrchestrationEventRepository(dbPath); | |
| 49 | const secondRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath)); | |
| 50 | const second = await secondRuntime.runPromise(OrchestrationEventRepository); | |
| 48 | 51 | const replayed = await Runtime.runPromise(runtime)(second.readFromSequence(0)); |
| 49 | 52 | expect(replayed).toEqual([saved]); |
| 50 | second.close(); | |
| 53 | await secondRuntime.dispose(); | |
| 51 | 54 | }); |
| 52 | 55 | |
| 53 | 56 | it("creates and reuses the migrator tracking table", async () => { |
| ... | ... | @@ -55,13 +58,15 @@ describe("SqliteEventStore", () => { |
| 55 | 58 | const dbPath = path.join(stateDir, "orchestration.sqlite"); |
| 56 | 59 | const runtime = Runtime.defaultRuntime; |
| 57 | 60 | |
| 58 | const first = makeSqliteOrchestrationEventRepository(dbPath); | |
| 61 | const firstRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath)); | |
| 62 | const first = await firstRuntime.runPromise(OrchestrationEventRepository); | |
| 59 | 63 | await Runtime.runPromise(runtime)(first.readAll()); |
| 60 | first.close(); | |
| 64 | await firstRuntime.dispose(); | |
| 61 | 65 | |
| 62 | const second = makeSqliteOrchestrationEventRepository(dbPath); | |
| 66 | const secondRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath)); | |
| 67 | const second = await secondRuntime.runPromise(OrchestrationEventRepository); | |
| 63 | 68 | await Runtime.runPromise(runtime)(second.readAll()); |
| 64 | second.close(); | |
| 69 | await secondRuntime.dispose(); | |
| 65 | 70 | |
| 66 | 71 | const db = new DatabaseSync(dbPath, { readOnly: true }); |
| 67 | 72 | const tableRow = db |
apps/server/src/persistence/Layers/OrchestrationEvents.ts+77-89| ... | ... | @@ -7,10 +7,13 @@ import type { SqlClient as SqlClientService } from "@effect/sql/SqlClient"; |
| 7 | 7 | import * as SqlClient from "@effect/sql/SqlClient"; |
| 8 | 8 | import * as SqlSchema from "@effect/sql/SqlSchema"; |
| 9 | 9 | import * as SqliteClient from "@effect/sql-sqlite-node/SqliteClient"; |
| 10 | import { Effect, ManagedRuntime, Schema } from "effect"; | |
| 10 | import { Context, Effect, Layer, Schema } from "effect"; | |
| 11 | 11 | |
| 12 | import type { OrchestrationEventRepositoryShape } from "../Services/OrchestrationEvents"; | |
| 13 | 12 | import { runMigrations } from "../Migrations"; |
| 13 | import { | |
| 14 | OrchestrationEventRepository, | |
| 15 | type OrchestrationEventRepositoryShape, | |
| 16 | } from "../Services/OrchestrationEvents"; | |
| 14 | 17 | |
| 15 | 18 | const decodeEvent = Schema.decodeUnknownSync(OrchestrationEventSchema); |
| 16 | 19 | |
| ... | ... | @@ -42,11 +45,31 @@ const ReadFromSequenceRequestSchema = Schema.Struct({ |
| 42 | 45 | limit: Schema.Number, |
| 43 | 46 | }); |
| 44 | 47 | |
| 45 | const appendEventRow = SqlSchema.single({ | |
| 46 | Request: AppendEventRequestSchema, | |
| 47 | Result: EventRowSchema, | |
| 48 | execute: (request) => | |
| 49 | Effect.flatMap(SqlClient.SqlClient, (sql) => | |
| 48 | class OrchestrationSql extends Context.Tag("persistence/OrchestrationSql")< | |
| 49 | OrchestrationSql, | |
| 50 | SqlClientService | |
| 51 | >() {} | |
| 52 | ||
| 53 | function eventRowToOrchestrationEvent(row: EventRow): OrchestrationEvent { | |
| 54 | return decodeEvent({ | |
| 55 | sequence: row.sequence, | |
| 56 | eventId: row.eventId, | |
| 57 | type: row.type, | |
| 58 | aggregateType: row.aggregateType, | |
| 59 | aggregateId: row.aggregateId, | |
| 60 | occurredAt: row.occurredAt, | |
| 61 | commandId: row.commandId, | |
| 62 | payload: JSON.parse(row.payloadJson) as unknown, | |
| 63 | }); | |
| 64 | } | |
| 65 | ||
| 66 | const makeRepository = Effect.gen(function* () { | |
| 67 | const sql = yield* OrchestrationSql; | |
| 68 | ||
| 69 | const appendEventRow = SqlSchema.single({ | |
| 70 | Request: AppendEventRequestSchema, | |
| 71 | Result: EventRowSchema, | |
| 72 | execute: (request) => | |
| 50 | 73 | sql` |
| 51 | 74 | INSERT INTO orchestration_events ( |
| 52 | 75 | event_id, |
| ... | ... | @@ -76,14 +99,12 @@ const appendEventRow = SqlSchema.single({ |
| 76 | 99 | command_id AS "commandId", |
| 77 | 100 | payload_json AS "payloadJson" |
| 78 | 101 | `, |
| 79 | ), | |
| 80 | }); | |
| 102 | }); | |
| 81 | 103 | |
| 82 | const readEventRowsFromSequence = SqlSchema.findAll({ | |
| 83 | Request: ReadFromSequenceRequestSchema, | |
| 84 | Result: EventRowSchema, | |
| 85 | execute: (request) => | |
| 86 | Effect.flatMap(SqlClient.SqlClient, (sql) => | |
| 104 | const readEventRowsFromSequence = SqlSchema.findAll({ | |
| 105 | Request: ReadFromSequenceRequestSchema, | |
| 106 | Result: EventRowSchema, | |
| 107 | execute: (request) => | |
| 87 | 108 | sql` |
| 88 | 109 | SELECT |
| 89 | 110 | sequence, |
| ... | ... | @@ -99,92 +120,59 @@ const readEventRowsFromSequence = SqlSchema.findAll({ |
| 99 | 120 | ORDER BY sequence ASC |
| 100 | 121 | LIMIT ${request.limit} |
| 101 | 122 | `, |
| 102 | ), | |
| 103 | }); | |
| 104 | ||
| 105 | function eventRowToOrchestrationEvent(row: EventRow): OrchestrationEvent { | |
| 106 | return decodeEvent({ | |
| 107 | sequence: row.sequence, | |
| 108 | eventId: row.eventId, | |
| 109 | type: row.type, | |
| 110 | aggregateType: row.aggregateType, | |
| 111 | aggregateId: row.aggregateId, | |
| 112 | occurredAt: row.occurredAt, | |
| 113 | commandId: row.commandId, | |
| 114 | payload: JSON.parse(row.payloadJson) as unknown, | |
| 115 | 123 | }); |
| 116 | } | |
| 117 | ||
| 118 | export function makeSqliteOrchestrationEventRepository( | |
| 119 | dbPath: string, | |
| 120 | ): OrchestrationEventRepositoryShape { | |
| 121 | fs.mkdirSync(path.dirname(dbPath), { recursive: true }); | |
| 122 | ||
| 123 | const runtime: ManagedRuntime.ManagedRuntime<SqlClientService, unknown> = ManagedRuntime.make( | |
| 124 | SqliteClient.layer({ | |
| 125 | filename: dbPath, | |
| 126 | }), | |
| 127 | ); | |
| 128 | let closed = false; | |
| 129 | ||
| 130 | const provideSql = <A, E>( | |
| 131 | effect: Effect.Effect<A, E, SqlClientService>, | |
| 132 | ): Effect.Effect<A, E | unknown> => Effect.provide(effect, runtime); | |
| 133 | ||
| 134 | const initialize = Effect.gen(function* () { | |
| 135 | const sql = yield* SqlClient.SqlClient; | |
| 136 | yield* sql`PRAGMA journal_mode = WAL;`; | |
| 137 | yield* sql`PRAGMA foreign_keys = ON;`; | |
| 138 | yield* runMigrations; | |
| 139 | }); | |
| 140 | ||
| 141 | const initialized = runtime.runPromise(initialize).then(() => undefined); | |
| 142 | const ensureInitialized = Effect.promise(() => initialized); | |
| 143 | 124 | |
| 144 | 125 | const append: OrchestrationEventRepositoryShape["append"] = (event) => |
| 145 | Effect.gen(function* () { | |
| 146 | yield* ensureInitialized; | |
| 147 | const row = yield* provideSql( | |
| 148 | appendEventRow({ | |
| 149 | eventId: event.eventId, | |
| 150 | type: event.type, | |
| 151 | aggregateType: event.aggregateType, | |
| 152 | aggregateId: event.aggregateId, | |
| 153 | occurredAt: event.occurredAt, | |
| 154 | commandId: event.commandId, | |
| 155 | payloadJson: JSON.stringify(event.payload), | |
| 156 | }), | |
| 157 | ); | |
| 158 | return eventRowToOrchestrationEvent(row); | |
| 159 | }).pipe(Effect.orDie); | |
| 126 | appendEventRow({ | |
| 127 | eventId: event.eventId, | |
| 128 | type: event.type, | |
| 129 | aggregateType: event.aggregateType, | |
| 130 | aggregateId: event.aggregateId, | |
| 131 | occurredAt: event.occurredAt, | |
| 132 | commandId: event.commandId, | |
| 133 | payloadJson: JSON.stringify(event.payload), | |
| 134 | }).pipe(Effect.map(eventRowToOrchestrationEvent), Effect.orDie); | |
| 160 | 135 | |
| 161 | 136 | const readFromSequence: OrchestrationEventRepositoryShape["readFromSequence"] = ( |
| 162 | 137 | sequenceExclusive, |
| 163 | 138 | limit = 1_000, |
| 164 | 139 | ) => |
| 165 | Effect.gen(function* () { | |
| 166 | yield* ensureInitialized; | |
| 167 | const rows = yield* provideSql( | |
| 168 | readEventRowsFromSequence({ | |
| 169 | sequenceExclusive, | |
| 170 | limit, | |
| 171 | }), | |
| 172 | ); | |
| 173 | return rows.map(eventRowToOrchestrationEvent); | |
| 174 | }).pipe(Effect.orDie); | |
| 175 | ||
| 176 | const close = (): void => { | |
| 177 | if (closed) { | |
| 178 | return; | |
| 179 | } | |
| 180 | closed = true; | |
| 181 | void runtime.dispose(); | |
| 182 | }; | |
| 140 | readEventRowsFromSequence({ sequenceExclusive, limit }).pipe( | |
| 141 | Effect.map((rows) => rows.map(eventRowToOrchestrationEvent)), | |
| 142 | Effect.orDie, | |
| 143 | ); | |
| 183 | 144 | |
| 184 | 145 | return { |
| 185 | 146 | append, |
| 186 | 147 | readFromSequence, |
| 187 | 148 | readAll: () => readFromSequence(0, Number.MAX_SAFE_INTEGER), |
| 188 | close, | |
| 189 | }; | |
| 149 | } satisfies OrchestrationEventRepositoryShape; | |
| 150 | }); | |
| 151 | ||
| 152 | export const OrchestrationEventRepositoryLive = Layer.effect( | |
| 153 | OrchestrationEventRepository, | |
| 154 | makeRepository, | |
| 155 | ); | |
| 156 | ||
| 157 | export function makeSqliteOrchestrationEventRepositoryLive(dbPath: string) { | |
| 158 | const SqliteClientLive = Effect.gen(function* () { | |
| 159 | yield* Effect.try({ | |
| 160 | try: () => fs.mkdirSync(path.dirname(dbPath), { recursive: true }), | |
| 161 | catch: (error) => Effect.die(error), | |
| 162 | }) | |
| 163 | return SqliteClient.layer({ filename: dbPath }); | |
| 164 | }).pipe(Layer.unwrapEffect) | |
| 165 | ||
| 166 | const OrchestrationSqlLive = Layer.scoped( | |
| 167 | OrchestrationSql, | |
| 168 | Effect.gen(function* () { | |
| 169 | const sql = yield* SqlClient.SqlClient; | |
| 170 | yield* sql`PRAGMA journal_mode = WAL;`; | |
| 171 | yield* sql`PRAGMA foreign_keys = ON;`; | |
| 172 | yield* runMigrations; | |
| 173 | return sql; | |
| 174 | }), | |
| 175 | ).pipe(Layer.provide(SqliteClientLive)); | |
| 176 | ||
| 177 | return OrchestrationEventRepositoryLive.pipe(Layer.provide(OrchestrationSqlLive)); | |
| 190 | 178 | } |
apps/server/src/persistence/Services/OrchestrationEvents.ts-1| ... | ... | @@ -11,7 +11,6 @@ export interface OrchestrationEventRepositoryShape { |
| 11 | 11 | limit?: number, |
| 12 | 12 | ) => Effect.Effect<OrchestrationEvent[]>; |
| 13 | 13 | readonly readAll: () => Effect.Effect<OrchestrationEvent[]>; |
| 14 | readonly close: () => void; | |
| 15 | 14 | } |
| 16 | 15 | |
| 17 | 16 | export class OrchestrationEventRepository extends Context.Tag("orchestration/EventRepository")< |
apps/server/src/wsServer.ts+90-49| ... | ... | @@ -36,7 +36,7 @@ import { |
| 36 | 36 | import { TerminalManager } from "./terminalManager"; |
| 37 | 37 | import { loadResolvedKeybindingsConfig, upsertKeybindingRule } from "./keybindings"; |
| 38 | 38 | import { searchWorkspaceEntries } from "./workspaceEntries"; |
| 39 | import { createOrchestrationEngine } from "./orchestration/runtime"; | |
| 39 | import { createOrchestrationSystem, type OrchestrationSystem } from "./orchestration/runtime"; | |
| 40 | 40 | |
| 41 | 41 | const MIME_TYPES: Record<string, string> = { |
| 42 | 42 | ".html": "text/html; charset=utf-8", |
| ... | ... | @@ -88,6 +88,8 @@ function asNumber(value: unknown): number | null { |
| 88 | 88 | return typeof value === "number" && Number.isFinite(value) ? value : null; |
| 89 | 89 | } |
| 90 | 90 | |
| 91 | const noop = () => {}; | |
| 92 | ||
| 91 | 93 | export function createServer(options: ServerOptions) { |
| 92 | 94 | const { |
| 93 | 95 | port, |
| ... | ... | @@ -107,14 +109,15 @@ export function createServer(options: ServerOptions) { |
| 107 | 109 | const projectRegistry = |
| 108 | 110 | providedRegistry ?? new ProjectRegistry(path.join(os.homedir(), ".t3", "userdata")); |
| 109 | 111 | const gitManager = providedGitManager ?? new GitManager(); |
| 110 | const orchestrationEngine = createOrchestrationEngine( | |
| 111 | stateDir ?? path.join(os.homedir(), ".t3", "userdata"), | |
| 112 | ); | |
| 112 | const resolvedStateDir = stateDir ?? path.join(os.homedir(), ".t3", "userdata"); | |
| 113 | let orchestrationSystem: OrchestrationSystem | null = null; | |
| 113 | 114 | const clients = new Set<WebSocket>(); |
| 114 | 115 | const logger = createLogger("ws"); |
| 115 | 116 | const logWebSocketEvents = |
| 116 | 117 | explicitLogWsEvents ?? parseBooleanEnv(process.env.T3CODE_LOG_WS_EVENTS) ?? Boolean(devUrl); |
| 117 | 118 | let keybindingsConfig = loadResolvedKeybindingsConfig(logger); |
| 119 | let unsubscribeReadModel = noop; | |
| 120 | let unsubscribeDomainEvents = noop; | |
| 118 | 121 | |
| 119 | 122 | function logOutgoingPush(push: WsPush, recipients: number) { |
| 120 | 123 | if (!logWebSocketEvents) return; |
| ... | ... | @@ -125,46 +128,62 @@ export function createServer(options: ServerOptions) { |
| 125 | 128 | }); |
| 126 | 129 | } |
| 127 | 130 | |
| 128 | const unsubscribeReadModel = orchestrationEngine.subscribeToReadModel((snapshot) => { | |
| 129 | const push: WsPush = { | |
| 130 | type: "push", | |
| 131 | channel: ORCHESTRATION_WS_CHANNELS.readModel, | |
| 132 | data: { | |
| 133 | sequence: snapshot.sequence, | |
| 134 | snapshot, | |
| 135 | } satisfies OrchestrationReadModelPush, | |
| 136 | }; | |
| 137 | const message = JSON.stringify(push); | |
| 138 | let recipients = 0; | |
| 139 | for (const client of clients) { | |
| 140 | if (client.readyState === client.OPEN) { | |
| 141 | client.send(message); | |
| 142 | recipients += 1; | |
| 143 | } | |
| 131 | function requireOrchestrationEngine() { | |
| 132 | if (!orchestrationSystem) { | |
| 133 | throw new Error("Orchestration engine is not started"); | |
| 144 | 134 | } |
| 145 | logOutgoingPush(push, recipients); | |
| 146 | }); | |
| 135 | return orchestrationSystem.engine; | |
| 136 | } | |
| 147 | 137 | |
| 148 | const unsubscribeDomainEvents = orchestrationEngine.subscribeToDomainEvents((event) => { | |
| 149 | const push: WsPush = { | |
| 150 | type: "push", | |
| 151 | channel: ORCHESTRATION_WS_CHANNELS.domainEvent, | |
| 152 | data: event, | |
| 153 | }; | |
| 154 | const message = JSON.stringify(push); | |
| 155 | let recipients = 0; | |
| 156 | for (const client of clients) { | |
| 157 | if (client.readyState === client.OPEN) { | |
| 158 | client.send(message); | |
| 159 | recipients += 1; | |
| 138 | function attachOrchestrationSubscriptions() { | |
| 139 | const orchestrationEngine = requireOrchestrationEngine(); | |
| 140 | ||
| 141 | unsubscribeReadModel = orchestrationEngine.subscribeToReadModel((snapshot) => { | |
| 142 | const push: WsPush = { | |
| 143 | type: "push", | |
| 144 | channel: ORCHESTRATION_WS_CHANNELS.readModel, | |
| 145 | data: { | |
| 146 | sequence: snapshot.sequence, | |
| 147 | snapshot, | |
| 148 | } satisfies OrchestrationReadModelPush, | |
| 149 | }; | |
| 150 | const message = JSON.stringify(push); | |
| 151 | let recipients = 0; | |
| 152 | for (const client of clients) { | |
| 153 | if (client.readyState === client.OPEN) { | |
| 154 | client.send(message); | |
| 155 | recipients += 1; | |
| 156 | } | |
| 160 | 157 | } |
| 161 | } | |
| 162 | logOutgoingPush(push, recipients); | |
| 163 | }); | |
| 158 | logOutgoingPush(push, recipients); | |
| 159 | }); | |
| 160 | ||
| 161 | unsubscribeDomainEvents = orchestrationEngine.subscribeToDomainEvents((event) => { | |
| 162 | const push: WsPush = { | |
| 163 | type: "push", | |
| 164 | channel: ORCHESTRATION_WS_CHANNELS.domainEvent, | |
| 165 | data: event, | |
| 166 | }; | |
| 167 | const message = JSON.stringify(push); | |
| 168 | let recipients = 0; | |
| 169 | for (const client of clients) { | |
| 170 | if (client.readyState === client.OPEN) { | |
| 171 | client.send(message); | |
| 172 | recipients += 1; | |
| 173 | } | |
| 174 | } | |
| 175 | logOutgoingPush(push, recipients); | |
| 176 | }); | |
| 177 | } | |
| 164 | 178 | |
| 165 | 179 | // Forward provider events to all connected WebSocket clients |
| 166 | 180 | providerManager.on("event", (event) => { |
| 167 | 181 | void (async () => { |
| 182 | const liveOrchestrationSystem = orchestrationSystem; | |
| 183 | if (!liveOrchestrationSystem) { | |
| 184 | return; | |
| 185 | } | |
| 186 | const orchestrationEngine = liveOrchestrationSystem.engine; | |
| 168 | 187 | const snapshot = orchestrationEngine.getSnapshot(); |
| 169 | 188 | const thread = snapshot.threads.find((entry) => entry.session?.sessionId === event.sessionId); |
| 170 | 189 | if (!thread) return; |
| ... | ... | @@ -367,6 +386,7 @@ export function createServer(options: ServerOptions) { |
| 367 | 386 | |
| 368 | 387 | wss.on("connection", (ws) => { |
| 369 | 388 | clients.add(ws); |
| 389 | const orchestrationEngine = requireOrchestrationEngine(); | |
| 370 | 390 | |
| 371 | 391 | // Send welcome message with project info |
| 372 | 392 | const segments = cwd.split(/[/\\]/).filter(Boolean); |
| ... | ... | @@ -433,6 +453,8 @@ export function createServer(options: ServerOptions) { |
| 433 | 453 | } |
| 434 | 454 | |
| 435 | 455 | async function routeRequest(request: WsRequest): Promise<unknown> { |
| 456 | const orchestrationEngine = requireOrchestrationEngine(); | |
| 457 | ||
| 436 | 458 | switch (request.method) { |
| 437 | 459 | case WS_METHODS.providersStartSession: |
| 438 | 460 | return providerManager.startSession(request.params as never); |
| ... | ... | @@ -676,21 +698,42 @@ export function createServer(options: ServerOptions) { |
| 676 | 698 | } |
| 677 | 699 | } |
| 678 | 700 | |
| 679 | function start() { | |
| 701 | async function ensureOrchestrationSystemStarted() { | |
| 702 | if (orchestrationSystem) { | |
| 703 | return; | |
| 704 | } | |
| 705 | ||
| 706 | orchestrationSystem = await createOrchestrationSystem(resolvedStateDir); | |
| 707 | attachOrchestrationSubscriptions(); | |
| 708 | } | |
| 709 | ||
| 710 | async function disposeOrchestrationSystem() { | |
| 711 | unsubscribeReadModel(); | |
| 712 | unsubscribeDomainEvents(); | |
| 713 | unsubscribeReadModel = noop; | |
| 714 | unsubscribeDomainEvents = noop; | |
| 715 | ||
| 716 | if (!orchestrationSystem) { | |
| 717 | return; | |
| 718 | } | |
| 719 | ||
| 720 | const system = orchestrationSystem; | |
| 721 | orchestrationSystem = null; | |
| 722 | await system.dispose(); | |
| 723 | } | |
| 724 | ||
| 725 | async function start() { | |
| 726 | await ensureOrchestrationSystemStarted(); | |
| 727 | ||
| 680 | 728 | return new Promise<void>((resolve, reject) => { |
| 681 | 729 | const onError = (error: Error) => { |
| 682 | 730 | httpServer.off("error", onError); |
| 683 | reject(error); | |
| 731 | void disposeOrchestrationSystem().finally(() => reject(error)); | |
| 684 | 732 | }; |
| 685 | 733 | httpServer.once("error", onError); |
| 686 | const onListening = async () => { | |
| 734 | const onListening = () => { | |
| 687 | 735 | httpServer.off("error", onError); |
| 688 | try { | |
| 689 | await orchestrationEngine.start(); | |
| 690 | resolve(); | |
| 691 | } catch (error) { | |
| 692 | reject(error instanceof Error ? error : new Error("Failed to start orchestration engine")); | |
| 693 | } | |
| 736 | resolve(); | |
| 694 | 737 | }; |
| 695 | 738 | if (host) { |
| 696 | 739 | httpServer.listen(port, host, onListening); |
| ... | ... | @@ -701,9 +744,7 @@ export function createServer(options: ServerOptions) { |
| 701 | 744 | } |
| 702 | 745 | |
| 703 | 746 | async function stop(): Promise<void> { |
| 704 | unsubscribeReadModel(); | |
| 705 | unsubscribeDomainEvents(); | |
| 706 | await orchestrationEngine.stop(); | |
| 747 | await disposeOrchestrationSystem(); | |
| 707 | 748 | terminalManager.off("event", onTerminalEvent); |
| 708 | 749 | providerManager.stopAll(); |
| 709 | 750 | providerManager.dispose(); |
bun.lock+46| ... | ... | @@ -34,6 +34,8 @@ |
| 34 | 34 | "t3": "./dist/index.mjs", |
| 35 | 35 | }, |
| 36 | 36 | "dependencies": { |
| 37 | "@effect/platform": "^0.94.5", | |
| 38 | "@effect/platform-node": "^0.104.1", | |
| 37 | 39 | "@effect/sql": "^0.49.0", |
| 38 | 40 | "@effect/sql-sqlite-node": "^0.50.1", |
| 39 | 41 | "effect": "^3.19.19", |
| ... | ... | @@ -148,14 +150,24 @@ |
| 148 | 150 | |
| 149 | 151 | "@base-ui/utils": ["@base-ui/utils@0.2.5", "", { "dependencies": { "@babel/runtime": "^7.28.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-oYC7w0gp76RI5MxprlGLV0wze0SErZaRl3AAkeP3OnNB/UBMb6RqNf6ZSIlxOc9Qp68Ab3C2VOcJQyRs7Xc7Vw=="], |
| 150 | 152 | |
| 153 | "@effect/cluster": ["@effect/cluster@0.56.4", "", { "dependencies": { "kubernetes-types": "^1.30.0" }, "peerDependencies": { "@effect/platform": "^0.94.5", "@effect/rpc": "^0.73.1", "@effect/sql": "^0.49.0", "@effect/workflow": "^0.16.0", "effect": "^3.19.17" } }, "sha512-7Je5/JlbZOlsSxsbKjr97dJed2cNGWsb+TLNgMcr5mRDbcWlFOTUGvsrisEJV6waosYLIg+2omPdvnvRoYKdhA=="], | |
| 154 | ||
| 151 | 155 | "@effect/experimental": ["@effect/experimental@0.58.0", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.94.0", "effect": "^3.19.13", "ioredis": "^5", "lmdb": "^3" }, "optionalPeers": ["ioredis", "lmdb"] }, "sha512-IEP9sapjF6rFy5TkoqDPc86st/fnqUfjT7Xa3pWJrFGr1hzaMXHo+mWsYOZS9LAOVKnpHuVziDK97EP5qsCHVA=="], |
| 152 | 156 | |
| 153 | 157 | "@effect/platform": ["@effect/platform@0.94.5", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.17" } }, "sha512-z05APUiDDPbodhTkH/RJqOLoCU11bU2IZLfcwLFrld03+ob1VeqRnELQlmueLIYm6NZifHAtjl32V+GRt34y4A=="], |
| 154 | 158 | |
| 159 | "@effect/platform-node": ["@effect/platform-node@0.104.1", "", { "dependencies": { "@effect/platform-node-shared": "^0.57.1", "mime": "^3.0.0", "undici": "^7.10.0", "ws": "^8.18.2" }, "peerDependencies": { "@effect/cluster": "^0.56.1", "@effect/platform": "^0.94.2", "@effect/rpc": "^0.73.0", "@effect/sql": "^0.49.0", "effect": "^3.19.15" } }, "sha512-jT1a/z98niK6fnEU8pWHPPCdJMVDRCIdB65lolcOjse5rsTwVbczMjvKkhVQpF63mNWoOnol7OTRNkw5L54llg=="], | |
| 160 | ||
| 161 | "@effect/platform-node-shared": ["@effect/platform-node-shared@0.57.1", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "multipasta": "^0.2.7", "ws": "^8.18.2" }, "peerDependencies": { "@effect/cluster": "^0.56.1", "@effect/platform": "^0.94.2", "@effect/rpc": "^0.73.0", "@effect/sql": "^0.49.0", "effect": "^3.19.15" } }, "sha512-oX/bApMdoKsyrDiNdJxo7U9Rz1RXsjRv+ecfAPp1qGlSdGIo32wVRvJ2XCHqYj0sqaYJS0pU0/GCulRfVGuJag=="], | |
| 162 | ||
| 163 | "@effect/rpc": ["@effect/rpc@0.73.2", "", { "dependencies": { "msgpackr": "^1.11.4" }, "peerDependencies": { "@effect/platform": "^0.94.5", "effect": "^3.19.18" } }, "sha512-td7LHDgBOYKg+VgGWEelD8rSAmvjXz7am17vfxZROX5qIYuvH7drL/z4p5xQFadhHZ7DYdlFpqdO9ggc77OCIw=="], | |
| 164 | ||
| 155 | 165 | "@effect/sql": ["@effect/sql@0.49.0", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/experimental": "^0.58.0", "@effect/platform": "^0.94.0", "effect": "^3.19.13" } }, "sha512-9UEKR+z+MrI/qMAmSvb/RiD9KlgIazjZUCDSpwNgm0lEK9/Q6ExEyfziiYFVCPiptp52cBw8uBHRic8hHnwqXA=="], |
| 156 | 166 | |
| 157 | 167 | "@effect/sql-sqlite-node": ["@effect/sql-sqlite-node@0.50.1", "", { "dependencies": { "better-sqlite3": "^12.6.2" }, "peerDependencies": { "@effect/experimental": "^0.58.0", "@effect/platform": "^0.94.4", "@effect/sql": "^0.49.0", "effect": "^3.19.16" } }, "sha512-L7DlHcOVl9/9V5stttFyv9wjvA9PBbaXwsp9pU+KN8KqokWgAVBNBEDf/dVfboUrynbDpDWwMU9SJmRJ1LXASQ=="], |
| 158 | 168 | |
| 169 | "@effect/workflow": ["@effect/workflow@0.16.0", "", { "peerDependencies": { "@effect/experimental": "^0.58.0", "@effect/platform": "^0.94.0", "@effect/rpc": "^0.73.0", "effect": "^3.19.13" } }, "sha512-MiAdlxx3TixkgHdbw+Yf1Z3tHAAE0rOQga12kIydJqj05Fnod+W/I+kQGRMY/XWRg+QUsVxhmh1qTr7Ype6lrw=="], | |
| 170 | ||
| 159 | 171 | "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], |
| 160 | 172 | |
| 161 | 173 | "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], |
| ... | ... | @@ -336,6 +348,34 @@ |
| 336 | 348 | |
| 337 | 349 | "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.46.0", "", { "os": "win32", "cpu": "x64" }, "sha512-DJbQsSJUr4KSi9uU0QqOgI7PX2C+fKGZX+YDprt3vM2sC0dWZsgVTLoN2vtkNyEWJSY2mnvRFUshWXT3bmo0Ug=="], |
| 338 | 350 | |
| 351 | "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], | |
| 352 | ||
| 353 | "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="], | |
| 354 | ||
| 355 | "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA=="], | |
| 356 | ||
| 357 | "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg=="], | |
| 358 | ||
| 359 | "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng=="], | |
| 360 | ||
| 361 | "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ=="], | |
| 362 | ||
| 363 | "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg=="], | |
| 364 | ||
| 365 | "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA=="], | |
| 366 | ||
| 367 | "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA=="], | |
| 368 | ||
| 369 | "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ=="], | |
| 370 | ||
| 371 | "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg=="], | |
| 372 | ||
| 373 | "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q=="], | |
| 374 | ||
| 375 | "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g=="], | |
| 376 | ||
| 377 | "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="], | |
| 378 | ||
| 339 | 379 | "@pierre/diffs": ["@pierre/diffs@1.1.0-beta.16", "", { "dependencies": { "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-McjTuEPuacSIcXdoI2O9W6VSHIOs9ApEHnEUwONKZnKqIo2GGv1vNg9Pr8tgBOL7lgBWNEHX5ROJ5z1X74sENQ=="], |
| 340 | 380 | |
| 341 | 381 | "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], |
| ... | ... | @@ -920,6 +960,8 @@ |
| 920 | 960 | |
| 921 | 961 | "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], |
| 922 | 962 | |
| 963 | "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], | |
| 964 | ||
| 923 | 965 | "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], |
| 924 | 966 | |
| 925 | 967 | "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="], |
| ... | ... | @@ -1056,6 +1098,8 @@ |
| 1056 | 1098 | |
| 1057 | 1099 | "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], |
| 1058 | 1100 | |
| 1101 | "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], | |
| 1102 | ||
| 1059 | 1103 | "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], |
| 1060 | 1104 | |
| 1061 | 1105 | "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], |
| ... | ... | @@ -1342,6 +1386,8 @@ |
| 1342 | 1386 | |
| 1343 | 1387 | "unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="], |
| 1344 | 1388 | |
| 1389 | "undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="], | |
| 1390 | ||
| 1345 | 1391 | "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], |
| 1346 | 1392 | |
| 1347 | 1393 | "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], |