Unified · Split
| @@ -17,6 +17,8 @@ · 16 unmodified lines | ||
| 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", |
| @@ -8,6 +8,7 @@ import { afterEach, describe, expect, it } from "vitest"; · 7 unmodified lines | ||
| 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", () => { · 16 unmodified lines | ||
| 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", () => { · 25 unmodified lines | ||
| 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", () => { · 9 unmodified lines | ||
| 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", () => { · 13 unmodified lines | ||
| 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", () => { · 36 unmodified lines | ||
| 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", () => { · 6 unmodified lines | ||
| 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", () => { · 94 unmodified lines | ||
| 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", () => { · 2 unmodified lines | ||
| 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", () => { · 26 unmodified lines | ||
| 341 | 352 | readAll() { |
| 342 | 353 | return Effect.succeed(events); |
| 343 | 354 | }, |
| 344 | − close() {}, | |
| 345 | 355 | }; |
| 346 | 356 | |
| 347 | 357 | const engine = new OrchestrationEngine(flakyStore); |
| @@ -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 { · 15 unmodified lines | ||
| 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 { · 187 unmodified lines | ||
| 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 { · 49 unmodified lines | ||
| 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 { · 12 unmodified lines | ||
| 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 { |
| @@ -4,7 +4,7 @@ import path from "node:path"; · 3 unmodified lines | ||
| 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", () => { · 13 unmodified lines | ||
| 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", () => { · 4 unmodified lines | ||
| 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 | }); |
| @@ -3,23 +3,28 @@ import path from "node:path"; · 2 unmodified lines | ||
| 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( |
| @@ -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 | } |
| @@ -3,10 +3,11 @@ import os from "node:os"; · 2 unmodified lines | ||
| 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(() => { · 9 unmodified lines | ||
| 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", () => { · 6 unmodified lines | ||
| 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", () => { · 1 unmodified line | ||
| 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 |
| @@ -7,10 +7,13 @@ import type { SqlClient as SqlClientService } from "@effect/sql/SqlClient"; · 6 unmodified lines | ||
| 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({ · 25 unmodified lines | ||
| 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({ · 23 unmodified lines | ||
| 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({ · 9 unmodified lines | ||
| 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 | } |
| @@ -11,7 +11,6 @@ export interface OrchestrationEventRepositoryShape { · 10 unmodified lines | ||
| 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")< |
| @@ -36,7 +36,7 @@ import { · 35 unmodified lines | ||
| 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 { · 45 unmodified lines | ||
| 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) { · 13 unmodified lines | ||
| 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) { · 4 unmodified lines | ||
| 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) { · 196 unmodified lines | ||
| 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) { · 60 unmodified lines | ||
| 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) { · 237 unmodified lines | ||
| 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) { · 4 unmodified lines | ||
| 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(); |
| @@ -34,6 +34,8 @@ · 33 unmodified lines | ||
| 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 @@ · 108 unmodified lines | ||
| 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 @@ · 174 unmodified lines | ||
| 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 @@ · 578 unmodified lines | ||
| 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 @@ · 130 unmodified lines | ||
| 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 @@ · 280 unmodified lines | ||
| 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=="], |