authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-21 20:22:55-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-26 13:20:36-08:00
logc71e71b5acacb74e201b3f4692ef08ed1dce3a28
tree41607073b3380187d120c668cf8ef977105940cf
parentbbf4e6bd3cf4dc1f73f3a0735092cf2f5e88c347

more effect: scoped layers


11 files changed, 307 insertions(+), 210 deletions(-)

apps/server/package.json+2
...@@ -17,6 +17,8 @@...@@ -17,6 +17,8 @@
17 "test": "vitest run"17 "test": "vitest run"
18 },18 },
19 "dependencies": {19 "dependencies": {
20 "@effect/platform": "^0.94.5",
21 "@effect/platform-node": "^0.104.1",
20 "@effect/sql": "^0.49.0",22 "@effect/sql": "^0.49.0",
21 "@effect/sql-sqlite-node": "^0.50.1",23 "@effect/sql-sqlite-node": "^0.50.1",
22 "effect": "^3.19.19",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,6 +8,7 @@ import { afterEach, describe, expect, it } from "vitest";
88
9import type { OrchestrationEventRepositoryShape } from "../persistence/Services/OrchestrationEvents";9import type { OrchestrationEventRepositoryShape } from "../persistence/Services/OrchestrationEvents";
10import { OrchestrationEngine } from "./engine";10import { OrchestrationEngine } from "./engine";
11import { createOrchestrationSystem } from "./runtime";
1112
12const tempDirs: string[] = [];13const tempDirs: string[] = [];
1314
...@@ -30,8 +31,8 @@ describe("OrchestrationEngine", () => {...@@ -30,8 +31,8 @@ describe("OrchestrationEngine", () => {
30 const projectId = "project-1";31 const projectId = "project-1";
31 const threadId = "thread-1";32 const threadId = "thread-1";
3233
33 const engineA = new OrchestrationEngine(stateDir);34 const firstSystem = await createOrchestrationSystem(stateDir);
34 await engineA.start();35 const engineA = firstSystem.engine;
35 await engineA.dispatch({36 await engineA.dispatch({
36 type: "project.create",37 type: "project.create",
37 commandId: "cmd-1",38 commandId: "cmd-1",
...@@ -63,19 +64,19 @@ describe("OrchestrationEngine", () => {...@@ -63,19 +64,19 @@ describe("OrchestrationEngine", () => {
63 createdAt,64 createdAt,
64 });65 });
65 const snapshotA = engineA.getSnapshot();66 const snapshotA = engineA.getSnapshot();
66 await engineA.stop();67 await firstSystem.dispose();
6768
68 const engineB = new OrchestrationEngine(stateDir);69 const secondSystem = await createOrchestrationSystem(stateDir);
69 await engineB.start();70 const engineB = secondSystem.engine;
70 const snapshotB = engineB.getSnapshot();71 const snapshotB = engineB.getSnapshot();
71 expect(snapshotB).toEqual(snapshotA);72 expect(snapshotB).toEqual(snapshotA);
72 await engineB.stop();73 await secondSystem.dispose();
73 });74 });
7475
75 it("fans out read-model updates to subscribers", async () => {76 it("fans out read-model updates to subscribers", async () => {
76 const stateDir = makeTempDir("t3code-orchestration-fanout-");77 const stateDir = makeTempDir("t3code-orchestration-fanout-");
77 const engine = new OrchestrationEngine(stateDir);78 const system = await createOrchestrationSystem(stateDir);
78 await engine.start();79 const engine = system.engine;
79 const updates: number[] = [];80 const updates: number[] = [];
80 const unsubscribe = engine.subscribeToReadModel((snapshot) => {81 const unsubscribe = engine.subscribeToReadModel((snapshot) => {
81 updates.push(snapshot.sequence);82 updates.push(snapshot.sequence);
...@@ -91,13 +92,13 @@ describe("OrchestrationEngine", () => {...@@ -91,13 +92,13 @@ describe("OrchestrationEngine", () => {
91 });92 });
92 unsubscribe();93 unsubscribe();
93 expect(updates.length).toBeGreaterThan(0);94 expect(updates.length).toBeGreaterThan(0);
94 await engine.stop();95 await system.dispose();
95 });96 });
9697
97 it("replays append-only events from sequence", async () => {98 it("replays append-only events from sequence", async () => {
98 const stateDir = makeTempDir("t3code-orchestration-replay-");99 const stateDir = makeTempDir("t3code-orchestration-replay-");
99 const engine = new OrchestrationEngine(stateDir);100 const system = await createOrchestrationSystem(stateDir);
100 await engine.start();101 const engine = system.engine;
101 await engine.dispatch({102 await engine.dispatch({
102 type: "project.create",103 type: "project.create",
103 commandId: "cmd-a",104 commandId: "cmd-a",
...@@ -117,14 +118,14 @@ describe("OrchestrationEngine", () => {...@@ -117,14 +118,14 @@ describe("OrchestrationEngine", () => {
117 expect(events.length).toBe(2);118 expect(events.length).toBe(2);
118 expect(events[0]?.type).toBe("project.created");119 expect(events[0]?.type).toBe("project.created");
119 expect(events[1]?.type).toBe("project.deleted");120 expect(events[1]?.type).toBe("project.deleted");
120 await engine.stop();121 await system.dispose();
121 });122 });
122123
123 it("stores completed turn summaries even when no files changed", async () => {124 it("stores completed turn summaries even when no files changed", async () => {
124 const stateDir = makeTempDir("t3code-orchestration-turn-diff-");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 const completedAt = new Date().toISOString();128 const completedAt = new Date().toISOString();
127 await engine.start();
128 await engine.dispatch({129 await engine.dispatch({
129 type: "project.create",130 type: "project.create",
130 commandId: "cmd-project-turn-diff",131 commandId: "cmd-project-turn-diff",
...@@ -167,10 +168,10 @@ describe("OrchestrationEngine", () => {...@@ -167,10 +168,10 @@ describe("OrchestrationEngine", () => {
167 checkpointTurnCount: 1,168 checkpointTurnCount: 1,
168 },169 },
169 ]);170 ]);
170 await engine.stop();171 await firstSystem.dispose();
171172
172 const restartedEngine = new OrchestrationEngine(stateDir);173 const secondSystem = await createOrchestrationSystem(stateDir);
173 await restartedEngine.start();174 const restartedEngine = secondSystem.engine;
174 const restartedThread = restartedEngine175 const restartedThread = restartedEngine
175 .getSnapshot()176 .getSnapshot()
176 .threads.find((thread) => thread.id === "thread-turn-diff");177 .threads.find((thread) => thread.id === "thread-turn-diff");
...@@ -183,14 +184,14 @@ describe("OrchestrationEngine", () => {...@@ -183,14 +184,14 @@ describe("OrchestrationEngine", () => {
183 checkpointTurnCount: 1,184 checkpointTurnCount: 1,
184 },185 },
185 ]);186 ]);
186 await restartedEngine.stop();187 await secondSystem.dispose();
187 });188 });
188189
189 it("reverts thread messages and turn summaries to a checkpoint", async () => {190 it("reverts thread messages and turn summaries to a checkpoint", async () => {
190 const stateDir = makeTempDir("t3code-orchestration-revert-");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 const createdAt = new Date().toISOString();194 const createdAt = new Date().toISOString();
193 await engine.start();
194 await engine.dispatch({195 await engine.dispatch({
195 type: "project.create",196 type: "project.create",
196 commandId: "cmd-project-revert",197 commandId: "cmd-project-revert",
...@@ -291,10 +292,10 @@ describe("OrchestrationEngine", () => {...@@ -291,10 +292,10 @@ describe("OrchestrationEngine", () => {
291 expect(thread?.messages.map((message) => message.id)).toEqual(["user-1", "assistant:turn-1"]);292 expect(thread?.messages.map((message) => message.id)).toEqual(["user-1", "assistant:turn-1"]);
292 expect(thread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]);293 expect(thread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]);
293 expect(thread?.latestTurnId).toBe("turn-1");294 expect(thread?.latestTurnId).toBe("turn-1");
294 await engine.stop();295 await firstSystem.dispose();
295296
296 const restarted = new OrchestrationEngine(stateDir);297 const secondSystem = await createOrchestrationSystem(stateDir);
297 await restarted.start();298 const restarted = secondSystem.engine;
298 const restartedThread = restarted299 const restartedThread = restarted
299 .getSnapshot()300 .getSnapshot()
300 .threads.find((entry) => entry.id === "thread-revert");301 .threads.find((entry) => entry.id === "thread-revert");
...@@ -303,12 +304,22 @@ describe("OrchestrationEngine", () => {...@@ -303,12 +304,22 @@ describe("OrchestrationEngine", () => {
303 "assistant:turn-1",304 "assistant:turn-1",
304 ]);305 ]);
305 expect(restartedThread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]);306 expect(restartedThread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]);
306 await restarted.stop();307 await secondSystem.dispose();
307 });308 });
308309
309 it("allows stop to be called multiple times", async () => {310 it("allows stop to be called multiple times", async () => {
310 const stateDir = makeTempDir("t3code-orchestration-stop-");311 const inMemoryStore: OrchestrationEventRepositoryShape = {
311 const engine = new OrchestrationEngine(stateDir);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 await engine.start();323 await engine.start();
313 await engine.stop();324 await engine.stop();
314 await expect(engine.stop()).resolves.toBeUndefined();325 await expect(engine.stop()).resolves.toBeUndefined();
...@@ -341,7 +352,6 @@ describe("OrchestrationEngine", () => {...@@ -341,7 +352,6 @@ describe("OrchestrationEngine", () => {
341 readAll() {352 readAll() {
342 return Effect.succeed(events);353 return Effect.succeed(events);
343 },354 },
344 close() {},
345 };355 };
346356
347 const engine = new OrchestrationEngine(flakyStore);357 const engine = new OrchestrationEngine(flakyStore);
apps/server/src/orchestration/engine.ts+6-13
...@@ -1,5 +1,3 @@...@@ -1,5 +1,3 @@
1import path from "node:path";
2
3import type {1import type {
4 OrchestrationCommand,2 OrchestrationCommand,
5 OrchestrationEvent,3 OrchestrationEvent,
...@@ -21,7 +19,6 @@ import {...@@ -21,7 +19,6 @@ import {
2119
22import { createLogger } from "../logger";20import { createLogger } from "../logger";
23import type { OrchestrationEventRepositoryShape } from "../persistence/Services/OrchestrationEvents";21import type { OrchestrationEventRepositoryShape } from "../persistence/Services/OrchestrationEvents";
24import { makeSqliteOrchestrationEventRepository } from "../persistence/Layers/OrchestrationEvents";
25import { createEmptyReadModel, reduceEvent } from "./reducer";22import { createEmptyReadModel, reduceEvent } from "./reducer";
26import { UI_ENTITY_CONTRACTS } from "./uiContractInventory";23import { UI_ENTITY_CONTRACTS } from "./uiContractInventory";
2724
...@@ -215,15 +212,8 @@ export class OrchestrationEngine {...@@ -215,15 +212,8 @@ export class OrchestrationEngine {
215 private readonly readModelListeners = new Set<(snapshot: OrchestrationReadModel) => void>();212 private readonly readModelListeners = new Set<(snapshot: OrchestrationReadModel) => void>();
216 private readonly domainEventListeners = new Set<(event: OrchestrationEvent) => void>();213 private readonly domainEventListeners = new Set<(event: OrchestrationEvent) => void>();
217214
218 constructor(stateDir: string);215 constructor(eventStore: OrchestrationEventRepositoryShape) {
219 constructor(eventStore: OrchestrationEventRepositoryShape);216 this.eventStore = eventStore;
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 }
227 this.readModel = createEmptyReadModel(new Date().toISOString());217 this.readModel = createEmptyReadModel(new Date().toISOString());
228 this.commandQueue = Runtime.runSync(this.runtime)(Queue.unbounded<CommandEnvelope>());218 this.commandQueue = Runtime.runSync(this.runtime)(Queue.unbounded<CommandEnvelope>());
229 this.readModelPubSub = Runtime.runSync(this.runtime)(219 this.readModelPubSub = Runtime.runSync(this.runtime)(
...@@ -279,6 +269,10 @@ export class OrchestrationEngine {...@@ -279,6 +269,10 @@ export class OrchestrationEngine {
279 }269 }
280270
281 async start(): Promise<void> {271 async start(): Promise<void> {
272 if (this.workerFiber) {
273 return;
274 }
275
282 await Runtime.runPromise(this.runtime)(this.bootstrapReadModel());276 await Runtime.runPromise(this.runtime)(this.bootstrapReadModel());
283277
284 const worker = Stream.fromQueue(this.commandQueue).pipe(278 const worker = Stream.fromQueue(this.commandQueue).pipe(
...@@ -297,7 +291,6 @@ export class OrchestrationEngine {...@@ -297,7 +291,6 @@ export class OrchestrationEngine {
297 await Runtime.runPromise(this.runtime)(Fiber.interrupt(this.workerFiber));291 await Runtime.runPromise(this.runtime)(Fiber.interrupt(this.workerFiber));
298 this.workerFiber = null;292 this.workerFiber = null;
299 }293 }
300 this.eventStore.close();
301 }294 }
302295
303 getSnapshot(): OrchestrationReadModel {296 getSnapshot(): OrchestrationReadModel {
apps/server/src/orchestration/layers.test.ts+7-7
...@@ -4,7 +4,7 @@ import path from "node:path";...@@ -4,7 +4,7 @@ import path from "node:path";
44
5import { afterEach, describe, expect, it } from "vitest";5import { afterEach, describe, expect, it } from "vitest";
66
7import { createOrchestrationEngine } from "./runtime";7import { createOrchestrationSystem } from "./runtime";
88
9const tempDirs: string[] = [];9const tempDirs: string[] = [];
1010
...@@ -24,9 +24,9 @@ describe("orchestration layers", () => {...@@ -24,9 +24,9 @@ describe("orchestration layers", () => {
24 it("creates an engine through Context.Tag wiring", async () => {24 it("creates an engine through Context.Tag wiring", async () => {
25 const stateDir = makeTempDir("t3code-orchestration-layer-");25 const stateDir = makeTempDir("t3code-orchestration-layer-");
26 const createdAt = new Date().toISOString();26 const createdAt = new Date().toISOString();
27 const engine = createOrchestrationEngine(stateDir);27 const firstSystem = await createOrchestrationSystem(stateDir);
28 const { engine } = firstSystem;
2829
29 await engine.start();
30 await engine.dispatch({30 await engine.dispatch({
31 type: "project.create",31 type: "project.create",
32 commandId: "cmd-layer-1",32 commandId: "cmd-layer-1",
...@@ -37,11 +37,11 @@ describe("orchestration layers", () => {...@@ -37,11 +37,11 @@ describe("orchestration layers", () => {
37 createdAt,37 createdAt,
38 });38 });
39 const firstSnapshot = engine.getSnapshot();39 const firstSnapshot = engine.getSnapshot();
40 await engine.stop();40 await firstSystem.dispose();
4141
42 const restarted = createOrchestrationEngine(stateDir);42 const secondSystem = await createOrchestrationSystem(stateDir);
43 await restarted.start();43 const restarted = secondSystem.engine;
44 expect(restarted.getSnapshot()).toEqual(firstSnapshot);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,23 +3,28 @@ import path from "node:path";
3import { Effect, Layer } from "effect";3import { Effect, Layer } from "effect";
44
5import { OrchestrationEventRepository } from "../persistence/Services/OrchestrationEvents";5import { OrchestrationEventRepository } from "../persistence/Services/OrchestrationEvents";
6import { makeSqliteOrchestrationEventRepository } from "../persistence/Layers/OrchestrationEvents";6import { makeSqliteOrchestrationEventRepositoryLive } from "../persistence/Layers/OrchestrationEvents";
7import { OrchestrationEngine } from "./engine";7import { OrchestrationEngine } from "./engine";
8import { OrchestrationConfig, OrchestrationEngineService } from "./services";8import { OrchestrationConfig, OrchestrationEngineService } from "./services";
99
10export const OrchestrationEventRepositoryLive = Layer.effect(10export const OrchestrationEventRepositoryLive = Layer.unwrapEffect(
11 OrchestrationEventRepository,
12 Effect.map(OrchestrationConfig, ({ stateDir }) => {11 Effect.map(OrchestrationConfig, ({ stateDir }) => {
13 const dbPath = path.join(stateDir, "orchestration.sqlite");12 const dbPath = path.join(stateDir, "orchestration.sqlite");
14 return makeSqliteOrchestrationEventRepository(dbPath);13 return makeSqliteOrchestrationEventRepositoryLive(dbPath);
15 }),14 }),
16);15);
1716
18export const OrchestrationEngineLive = Layer.effect(17export const OrchestrationEngineLive = Layer.scoped(
19 OrchestrationEngineService,18 OrchestrationEngineService,
20 Effect.map(OrchestrationEventRepository, (eventRepository) => {19 Effect.acquireRelease(
21 return new OrchestrationEngine(eventRepository);20 Effect.gen(function* () {
22 }),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);
2429
25export const OrchestrationLive = OrchestrationEngineLive.pipe(30export const OrchestrationLive = OrchestrationEngineLive.pipe(
apps/server/src/orchestration/runtime.ts+13-5
...@@ -1,14 +1,22 @@...@@ -1,14 +1,22 @@
1import { Effect, Layer, Runtime } from "effect";1import { Layer, ManagedRuntime } from "effect";
22
3import type { OrchestrationEngine } from "./engine";3import type { OrchestrationEngine } from "./engine";
4import { OrchestrationLive } from "./layers";4import { OrchestrationLive } from "./layers";
5import { OrchestrationConfig, OrchestrationEngineService } from "./services";5import { OrchestrationConfig, OrchestrationEngineService } from "./services";
66
7export function createOrchestrationEngine(stateDir: string): OrchestrationEngine {7export interface OrchestrationSystem {
8 readonly engine: OrchestrationEngine;
9 readonly dispose: () => Promise<void>;
10}
11
12export async function createOrchestrationSystem(stateDir: string): Promise<OrchestrationSystem> {
8 const orchestrationLayer = OrchestrationLive.pipe(13 const orchestrationLayer = OrchestrationLive.pipe(
9 Layer.provide(Layer.succeed(OrchestrationConfig, { stateDir })),14 Layer.provide(Layer.succeed(OrchestrationConfig, { stateDir })),
10 );15 );
11 return Runtime.runSync(Runtime.defaultRuntime)(16 const runtime = ManagedRuntime.make(orchestrationLayer);
12 Effect.provide(OrchestrationEngineService, orchestrationLayer),17 const engine = await runtime.runPromise(OrchestrationEngineService);
13 );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,10 +3,11 @@ import os from "node:os";
3import path from "node:path";3import path from "node:path";
4import { DatabaseSync } from "node:sqlite";4import { DatabaseSync } from "node:sqlite";
55
6import { Runtime } from "effect";6import { Runtime, ManagedRuntime } from "effect";
7import { afterEach, describe, expect, it } from "vitest";7import { afterEach, describe, expect, it } from "vitest";
88
9import { makeSqliteOrchestrationEventRepository } from "./OrchestrationEvents";9import { OrchestrationEventRepository } from "../Services/OrchestrationEvents";
10import { makeSqliteOrchestrationEventRepositoryLive } from "./OrchestrationEvents";
1011
11const tempDirs: string[] = [];12const tempDirs: string[] = [];
1213
...@@ -22,14 +23,15 @@ afterEach(() => {...@@ -22,14 +23,15 @@ afterEach(() => {
22 }23 }
23});24});
2425
25describe("SqliteEventStore", () => {26describe("OrchestrationEventRepository", () => {
26 it("persists and replays events across restarts", async () => {27 it("persists and replays events across restarts", async () => {
27 const stateDir = makeTempDir("t3code-event-store-");28 const stateDir = makeTempDir("t3code-event-store-");
28 const dbPath = path.join(stateDir, "orchestration.sqlite");29 const dbPath = path.join(stateDir, "orchestration.sqlite");
29 const runtime = Runtime.defaultRuntime;30 const runtime = Runtime.defaultRuntime;
30 const createdAt = new Date().toISOString();31 const createdAt = new Date().toISOString();
3132
32 const first = makeSqliteOrchestrationEventRepository(dbPath);33 const firstRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath));
34 const first = await firstRuntime.runPromise(OrchestrationEventRepository);
33 const saved = await Runtime.runPromise(runtime)(35 const saved = await Runtime.runPromise(runtime)(
34 first.append({36 first.append({
35 eventId: "event-1",37 eventId: "event-1",
...@@ -42,12 +44,13 @@ describe("SqliteEventStore", () => {...@@ -42,12 +44,13 @@ describe("SqliteEventStore", () => {
42 }),44 }),
43 );45 );
44 expect(saved.sequence).toBe(1);46 expect(saved.sequence).toBe(1);
45 first.close();47 await firstRuntime.dispose();
4648
47 const second = makeSqliteOrchestrationEventRepository(dbPath);49 const secondRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath));
50 const second = await secondRuntime.runPromise(OrchestrationEventRepository);
48 const replayed = await Runtime.runPromise(runtime)(second.readFromSequence(0));51 const replayed = await Runtime.runPromise(runtime)(second.readFromSequence(0));
49 expect(replayed).toEqual([saved]);52 expect(replayed).toEqual([saved]);
50 second.close();53 await secondRuntime.dispose();
51 });54 });
5255
53 it("creates and reuses the migrator tracking table", async () => {56 it("creates and reuses the migrator tracking table", async () => {
...@@ -55,13 +58,15 @@ describe("SqliteEventStore", () => {...@@ -55,13 +58,15 @@ describe("SqliteEventStore", () => {
55 const dbPath = path.join(stateDir, "orchestration.sqlite");58 const dbPath = path.join(stateDir, "orchestration.sqlite");
56 const runtime = Runtime.defaultRuntime;59 const runtime = Runtime.defaultRuntime;
5760
58 const first = makeSqliteOrchestrationEventRepository(dbPath);61 const firstRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath));
62 const first = await firstRuntime.runPromise(OrchestrationEventRepository);
59 await Runtime.runPromise(runtime)(first.readAll());63 await Runtime.runPromise(runtime)(first.readAll());
60 first.close();64 await firstRuntime.dispose();
6165
62 const second = makeSqliteOrchestrationEventRepository(dbPath);66 const secondRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath));
67 const second = await secondRuntime.runPromise(OrchestrationEventRepository);
63 await Runtime.runPromise(runtime)(second.readAll());68 await Runtime.runPromise(runtime)(second.readAll());
64 second.close();69 await secondRuntime.dispose();
6570
66 const db = new DatabaseSync(dbPath, { readOnly: true });71 const db = new DatabaseSync(dbPath, { readOnly: true });
67 const tableRow = db72 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,10 +7,13 @@ import type { SqlClient as SqlClientService } from "@effect/sql/SqlClient";
7import * as SqlClient from "@effect/sql/SqlClient";7import * as SqlClient from "@effect/sql/SqlClient";
8import * as SqlSchema from "@effect/sql/SqlSchema";8import * as SqlSchema from "@effect/sql/SqlSchema";
9import * as SqliteClient from "@effect/sql-sqlite-node/SqliteClient";9import * as SqliteClient from "@effect/sql-sqlite-node/SqliteClient";
10import { Effect, ManagedRuntime, Schema } from "effect";10import { Context, Effect, Layer, Schema } from "effect";
1111
12import type { OrchestrationEventRepositoryShape } from "../Services/OrchestrationEvents";
13import { runMigrations } from "../Migrations";12import { runMigrations } from "../Migrations";
13import {
14 OrchestrationEventRepository,
15 type OrchestrationEventRepositoryShape,
16} from "../Services/OrchestrationEvents";
1417
15const decodeEvent = Schema.decodeUnknownSync(OrchestrationEventSchema);18const decodeEvent = Schema.decodeUnknownSync(OrchestrationEventSchema);
1619
...@@ -42,11 +45,31 @@ const ReadFromSequenceRequestSchema = Schema.Struct({...@@ -42,11 +45,31 @@ const ReadFromSequenceRequestSchema = Schema.Struct({
42 limit: Schema.Number,45 limit: Schema.Number,
43});46});
4447
45const appendEventRow = SqlSchema.single({48class OrchestrationSql extends Context.Tag("persistence/OrchestrationSql")<
46 Request: AppendEventRequestSchema,49 OrchestrationSql,
47 Result: EventRowSchema,50 SqlClientService
48 execute: (request) =>51>() {}
49 Effect.flatMap(SqlClient.SqlClient, (sql) =>52
53function 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
66const 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 sql`73 sql`
51 INSERT INTO orchestration_events (74 INSERT INTO orchestration_events (
52 event_id,75 event_id,
...@@ -76,14 +99,12 @@ const appendEventRow = SqlSchema.single({...@@ -76,14 +99,12 @@ const appendEventRow = SqlSchema.single({
76 command_id AS "commandId",99 command_id AS "commandId",
77 payload_json AS "payloadJson"100 payload_json AS "payloadJson"
78 `,101 `,
79 ),102 });
80});
81103
82const readEventRowsFromSequence = SqlSchema.findAll({104 const readEventRowsFromSequence = SqlSchema.findAll({
83 Request: ReadFromSequenceRequestSchema,105 Request: ReadFromSequenceRequestSchema,
84 Result: EventRowSchema,106 Result: EventRowSchema,
85 execute: (request) =>107 execute: (request) =>
86 Effect.flatMap(SqlClient.SqlClient, (sql) =>
87 sql`108 sql`
88 SELECT109 SELECT
89 sequence,110 sequence,
...@@ -99,92 +120,59 @@ const readEventRowsFromSequence = SqlSchema.findAll({...@@ -99,92 +120,59 @@ const readEventRowsFromSequence = SqlSchema.findAll({
99 ORDER BY sequence ASC120 ORDER BY sequence ASC
100 LIMIT ${request.limit}121 LIMIT ${request.limit}
101 `,122 `,
102 ),
103});
104
105function 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
118export 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);
143124
144 const append: OrchestrationEventRepositoryShape["append"] = (event) =>125 const append: OrchestrationEventRepositoryShape["append"] = (event) =>
145 Effect.gen(function* () {126 appendEventRow({
146 yield* ensureInitialized;127 eventId: event.eventId,
147 const row = yield* provideSql(128 type: event.type,
148 appendEventRow({129 aggregateType: event.aggregateType,
149 eventId: event.eventId,130 aggregateId: event.aggregateId,
150 type: event.type,131 occurredAt: event.occurredAt,
151 aggregateType: event.aggregateType,132 commandId: event.commandId,
152 aggregateId: event.aggregateId,133 payloadJson: JSON.stringify(event.payload),
153 occurredAt: event.occurredAt,134 }).pipe(Effect.map(eventRowToOrchestrationEvent), Effect.orDie);
154 commandId: event.commandId,
155 payloadJson: JSON.stringify(event.payload),
156 }),
157 );
158 return eventRowToOrchestrationEvent(row);
159 }).pipe(Effect.orDie);
160135
161 const readFromSequence: OrchestrationEventRepositoryShape["readFromSequence"] = (136 const readFromSequence: OrchestrationEventRepositoryShape["readFromSequence"] = (
162 sequenceExclusive,137 sequenceExclusive,
163 limit = 1_000,138 limit = 1_000,
164 ) =>139 ) =>
165 Effect.gen(function* () {140 readEventRowsFromSequence({ sequenceExclusive, limit }).pipe(
166 yield* ensureInitialized;141 Effect.map((rows) => rows.map(eventRowToOrchestrationEvent)),
167 const rows = yield* provideSql(142 Effect.orDie,
168 readEventRowsFromSequence({143 );
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 };
183144
184 return {145 return {
185 append,146 append,
186 readFromSequence,147 readFromSequence,
187 readAll: () => readFromSequence(0, Number.MAX_SAFE_INTEGER),148 readAll: () => readFromSequence(0, Number.MAX_SAFE_INTEGER),
188 close,149 } satisfies OrchestrationEventRepositoryShape;
189 };150});
151
152export const OrchestrationEventRepositoryLive = Layer.effect(
153 OrchestrationEventRepository,
154 makeRepository,
155);
156
157export 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,7 +11,6 @@ export interface OrchestrationEventRepositoryShape {
11 limit?: number,11 limit?: number,
12 ) => Effect.Effect<OrchestrationEvent[]>;12 ) => Effect.Effect<OrchestrationEvent[]>;
13 readonly readAll: () => Effect.Effect<OrchestrationEvent[]>;13 readonly readAll: () => Effect.Effect<OrchestrationEvent[]>;
14 readonly close: () => void;
15}14}
1615
17export class OrchestrationEventRepository extends Context.Tag("orchestration/EventRepository")<16export class OrchestrationEventRepository extends Context.Tag("orchestration/EventRepository")<
apps/server/src/wsServer.ts+90-49
...@@ -36,7 +36,7 @@ import {...@@ -36,7 +36,7 @@ import {
36import { TerminalManager } from "./terminalManager";36import { TerminalManager } from "./terminalManager";
37import { loadResolvedKeybindingsConfig, upsertKeybindingRule } from "./keybindings";37import { loadResolvedKeybindingsConfig, upsertKeybindingRule } from "./keybindings";
38import { searchWorkspaceEntries } from "./workspaceEntries";38import { searchWorkspaceEntries } from "./workspaceEntries";
39import { createOrchestrationEngine } from "./orchestration/runtime";39import { createOrchestrationSystem, type OrchestrationSystem } from "./orchestration/runtime";
4040
41const MIME_TYPES: Record<string, string> = {41const MIME_TYPES: Record<string, string> = {
42 ".html": "text/html; charset=utf-8",42 ".html": "text/html; charset=utf-8",
...@@ -88,6 +88,8 @@ function asNumber(value: unknown): number | null {...@@ -88,6 +88,8 @@ function asNumber(value: unknown): number | null {
88 return typeof value === "number" && Number.isFinite(value) ? value : null;88 return typeof value === "number" && Number.isFinite(value) ? value : null;
89}89}
9090
91const noop = () => {};
92
91export function createServer(options: ServerOptions) {93export function createServer(options: ServerOptions) {
92 const {94 const {
93 port,95 port,
...@@ -107,14 +109,15 @@ export function createServer(options: ServerOptions) {...@@ -107,14 +109,15 @@ export function createServer(options: ServerOptions) {
107 const projectRegistry =109 const projectRegistry =
108 providedRegistry ?? new ProjectRegistry(path.join(os.homedir(), ".t3", "userdata"));110 providedRegistry ?? new ProjectRegistry(path.join(os.homedir(), ".t3", "userdata"));
109 const gitManager = providedGitManager ?? new GitManager();111 const gitManager = providedGitManager ?? new GitManager();
110 const orchestrationEngine = createOrchestrationEngine(112 const resolvedStateDir = stateDir ?? path.join(os.homedir(), ".t3", "userdata");
111 stateDir ?? path.join(os.homedir(), ".t3", "userdata"),113 let orchestrationSystem: OrchestrationSystem | null = null;
112 );
113 const clients = new Set<WebSocket>();114 const clients = new Set<WebSocket>();
114 const logger = createLogger("ws");115 const logger = createLogger("ws");
115 const logWebSocketEvents =116 const logWebSocketEvents =
116 explicitLogWsEvents ?? parseBooleanEnv(process.env.T3CODE_LOG_WS_EVENTS) ?? Boolean(devUrl);117 explicitLogWsEvents ?? parseBooleanEnv(process.env.T3CODE_LOG_WS_EVENTS) ?? Boolean(devUrl);
117 let keybindingsConfig = loadResolvedKeybindingsConfig(logger);118 let keybindingsConfig = loadResolvedKeybindingsConfig(logger);
119 let unsubscribeReadModel = noop;
120 let unsubscribeDomainEvents = noop;
118121
119 function logOutgoingPush(push: WsPush, recipients: number) {122 function logOutgoingPush(push: WsPush, recipients: number) {
120 if (!logWebSocketEvents) return;123 if (!logWebSocketEvents) return;
...@@ -125,46 +128,62 @@ export function createServer(options: ServerOptions) {...@@ -125,46 +128,62 @@ export function createServer(options: ServerOptions) {
125 });128 });
126 }129 }
127130
128 const unsubscribeReadModel = orchestrationEngine.subscribeToReadModel((snapshot) => {131 function requireOrchestrationEngine() {
129 const push: WsPush = {132 if (!orchestrationSystem) {
130 type: "push",133 throw new Error("Orchestration engine is not started");
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 }
144 }134 }
145 logOutgoingPush(push, recipients);135 return orchestrationSystem.engine;
146 });136 }
147137
148 const unsubscribeDomainEvents = orchestrationEngine.subscribeToDomainEvents((event) => {138 function attachOrchestrationSubscriptions() {
149 const push: WsPush = {139 const orchestrationEngine = requireOrchestrationEngine();
150 type: "push",140
151 channel: ORCHESTRATION_WS_CHANNELS.domainEvent,141 unsubscribeReadModel = orchestrationEngine.subscribeToReadModel((snapshot) => {
152 data: event,142 const push: WsPush = {
153 };143 type: "push",
154 const message = JSON.stringify(push);144 channel: ORCHESTRATION_WS_CHANNELS.readModel,
155 let recipients = 0;145 data: {
156 for (const client of clients) {146 sequence: snapshot.sequence,
157 if (client.readyState === client.OPEN) {147 snapshot,
158 client.send(message);148 } satisfies OrchestrationReadModelPush,
159 recipients += 1;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 }158 logOutgoingPush(push, recipients);
162 logOutgoingPush(push, recipients);159 });
163 });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 }
164178
165 // Forward provider events to all connected WebSocket clients179 // Forward provider events to all connected WebSocket clients
166 providerManager.on("event", (event) => {180 providerManager.on("event", (event) => {
167 void (async () => {181 void (async () => {
182 const liveOrchestrationSystem = orchestrationSystem;
183 if (!liveOrchestrationSystem) {
184 return;
185 }
186 const orchestrationEngine = liveOrchestrationSystem.engine;
168 const snapshot = orchestrationEngine.getSnapshot();187 const snapshot = orchestrationEngine.getSnapshot();
169 const thread = snapshot.threads.find((entry) => entry.session?.sessionId === event.sessionId);188 const thread = snapshot.threads.find((entry) => entry.session?.sessionId === event.sessionId);
170 if (!thread) return;189 if (!thread) return;
...@@ -367,6 +386,7 @@ export function createServer(options: ServerOptions) {...@@ -367,6 +386,7 @@ export function createServer(options: ServerOptions) {
367386
368 wss.on("connection", (ws) => {387 wss.on("connection", (ws) => {
369 clients.add(ws);388 clients.add(ws);
389 const orchestrationEngine = requireOrchestrationEngine();
370390
371 // Send welcome message with project info391 // Send welcome message with project info
372 const segments = cwd.split(/[/\\]/).filter(Boolean);392 const segments = cwd.split(/[/\\]/).filter(Boolean);
...@@ -433,6 +453,8 @@ export function createServer(options: ServerOptions) {...@@ -433,6 +453,8 @@ export function createServer(options: ServerOptions) {
433 }453 }
434454
435 async function routeRequest(request: WsRequest): Promise<unknown> {455 async function routeRequest(request: WsRequest): Promise<unknown> {
456 const orchestrationEngine = requireOrchestrationEngine();
457
436 switch (request.method) {458 switch (request.method) {
437 case WS_METHODS.providersStartSession:459 case WS_METHODS.providersStartSession:
438 return providerManager.startSession(request.params as never);460 return providerManager.startSession(request.params as never);
...@@ -676,21 +698,42 @@ export function createServer(options: ServerOptions) {...@@ -676,21 +698,42 @@ export function createServer(options: ServerOptions) {
676 }698 }
677 }699 }
678700
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 return new Promise<void>((resolve, reject) => {728 return new Promise<void>((resolve, reject) => {
681 const onError = (error: Error) => {729 const onError = (error: Error) => {
682 httpServer.off("error", onError);730 httpServer.off("error", onError);
683 reject(error);731 void disposeOrchestrationSystem().finally(() => reject(error));
684 };732 };
685 httpServer.once("error", onError);733 httpServer.once("error", onError);
686 const onListening = async () => {734 const onListening = () => {
687 httpServer.off("error", onError);735 httpServer.off("error", onError);
688 try {736 resolve();
689 await orchestrationEngine.start();
690 resolve();
691 } catch (error) {
692 reject(error instanceof Error ? error : new Error("Failed to start orchestration engine"));
693 }
694 };737 };
695 if (host) {738 if (host) {
696 httpServer.listen(port, host, onListening);739 httpServer.listen(port, host, onListening);
...@@ -701,9 +744,7 @@ export function createServer(options: ServerOptions) {...@@ -701,9 +744,7 @@ export function createServer(options: ServerOptions) {
701 }744 }
702745
703 async function stop(): Promise<void> {746 async function stop(): Promise<void> {
704 unsubscribeReadModel();747 await disposeOrchestrationSystem();
705 unsubscribeDomainEvents();
706 await orchestrationEngine.stop();
707 terminalManager.off("event", onTerminalEvent);748 terminalManager.off("event", onTerminalEvent);
708 providerManager.stopAll();749 providerManager.stopAll();
709 providerManager.dispose();750 providerManager.dispose();
bun.lock+46
...@@ -34,6 +34,8 @@...@@ -34,6 +34,8 @@
34 "t3": "./dist/index.mjs",34 "t3": "./dist/index.mjs",
35 },35 },
36 "dependencies": {36 "dependencies": {
37 "@effect/platform": "^0.94.5",
38 "@effect/platform-node": "^0.104.1",
37 "@effect/sql": "^0.49.0",39 "@effect/sql": "^0.49.0",
38 "@effect/sql-sqlite-node": "^0.50.1",40 "@effect/sql-sqlite-node": "^0.50.1",
39 "effect": "^3.19.19",41 "effect": "^3.19.19",
...@@ -148,14 +150,24 @@...@@ -148,14 +150,24 @@
148150
149 "@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=="],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=="],
150152
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 "@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=="],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=="],
152156
153 "@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=="],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=="],
154158
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 "@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=="],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=="],
156166
157 "@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=="],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=="],
158168
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 "@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=="],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=="],
160172
161 "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],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,6 +348,34 @@
336348
337 "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.46.0", "", { "os": "win32", "cpu": "x64" }, "sha512-DJbQsSJUr4KSi9uU0QqOgI7PX2C+fKGZX+YDprt3vM2sC0dWZsgVTLoN2vtkNyEWJSY2mnvRFUshWXT3bmo0Ug=="],349 "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.46.0", "", { "os": "win32", "cpu": "x64" }, "sha512-DJbQsSJUr4KSi9uU0QqOgI7PX2C+fKGZX+YDprt3vM2sC0dWZsgVTLoN2vtkNyEWJSY2mnvRFUshWXT3bmo0Ug=="],
338350
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 "@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=="],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=="],
340380
341 "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],381 "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
...@@ -920,6 +960,8 @@...@@ -920,6 +960,8 @@
920960
921 "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],961 "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
922962
963 "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
964
923 "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=="],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=="],
924966
925 "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],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,6 +1098,8 @@
10561098
1057 "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],1099 "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
10581100
1101 "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
1102
1059 "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],1103 "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
10601104
1061 "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],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,6 +1386,8 @@
13421386
1343 "unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="],1387 "unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="],
13441388
1389 "undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="],
1390
1345 "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],1391 "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
13461392
1347 "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=="],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=="],