History

more effect: scoped layers

Julius Marminge committed 8 months ago

Commit 10bd27d1 parent a23fb5e11 files · +307 −210Browse files

Unified · Split

11 files
  1. apps/server/package.json +2 −0
  2. apps/server/src/orchestration/engine.test.ts +37 −27
  3. apps/server/src/orchestration/engine.ts +6 −13
  4. apps/server/src/orchestration/layers.test.ts +7 −7
  5. apps/server/src/orchestration/layers.ts +13 −8
  6. apps/server/src/orchestration/runtime.ts +13 −5
  7. apps/server/src/persistence/Layers/OrchestrationEvents.test.ts +16 −11
  8. apps/server/src/persistence/Layers/OrchestrationEvents.ts +77 −89
  9. apps/server/src/persistence/Services/OrchestrationEvents.ts +0 −1
  10. apps/server/src/wsServer.ts +90 −49
  11. bun.lock +46 −0
apps/server/package.json+2−0
@@ -17,6 +17,8 @@ · 16 unmodified lines
1717 "test": "vitest run"
1818 },
1919 "dependencies": {
20+ "@effect/platform": "^0.94.5",
21+ "@effect/platform-node": "^0.104.1",
2022 "@effect/sql": "^0.49.0",
2123 "@effect/sql-sqlite-node": "^0.50.1",
2224 "effect": "^3.19.19",
apps/server/src/orchestration/engine.test.ts+37−27
@@ -8,6 +8,7 @@ import { afterEach, describe, expect, it } from "vitest"; · 7 unmodified lines
88
99 import type { OrchestrationEventRepositoryShape } from "../persistence/Services/OrchestrationEvents";
1010 import { OrchestrationEngine } from "./engine";
11+import { createOrchestrationSystem } from "./runtime";
1112
1213 const tempDirs: string[] = [];
1314
@@ -30,8 +31,8 @@ describe("OrchestrationEngine", () => { · 16 unmodified lines
3031 const projectId = "project-1";
3132 const threadId = "thread-1";
3233
33− const engineA = new OrchestrationEngine(stateDir);
34− await engineA.start();
34+ const firstSystem = await createOrchestrationSystem(stateDir);
35+ const engineA = firstSystem.engine;
3536 await engineA.dispatch({
3637 type: "project.create",
3738 commandId: "cmd-1",
@@ -63,19 +64,19 @@ describe("OrchestrationEngine", () => { · 25 unmodified lines
6364 createdAt,
6465 });
6566 const snapshotA = engineA.getSnapshot();
66− await engineA.stop();
67+ await firstSystem.dispose();
6768
68− const engineB = new OrchestrationEngine(stateDir);
69− await engineB.start();
69+ const secondSystem = await createOrchestrationSystem(stateDir);
70+ const engineB = secondSystem.engine;
7071 const snapshotB = engineB.getSnapshot();
7172 expect(snapshotB).toEqual(snapshotA);
72− await engineB.stop();
73+ await secondSystem.dispose();
7374 });
7475
7576 it("fans out read-model updates to subscribers", async () => {
7677 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;
7980 const updates: number[] = [];
8081 const unsubscribe = engine.subscribeToReadModel((snapshot) => {
8182 updates.push(snapshot.sequence);
@@ -91,13 +92,13 @@ describe("OrchestrationEngine", () => { · 9 unmodified lines
9192 });
9293 unsubscribe();
9394 expect(updates.length).toBeGreaterThan(0);
94− await engine.stop();
95+ await system.dispose();
9596 });
9697
9798 it("replays append-only events from sequence", async () => {
9899 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;
101102 await engine.dispatch({
102103 type: "project.create",
103104 commandId: "cmd-a",
@@ -117,14 +118,14 @@ describe("OrchestrationEngine", () => { · 13 unmodified lines
117118 expect(events.length).toBe(2);
118119 expect(events[0]?.type).toBe("project.created");
119120 expect(events[1]?.type).toBe("project.deleted");
120− await engine.stop();
121+ await system.dispose();
121122 });
122123
123124 it("stores completed turn summaries even when no files changed", async () => {
124125 const stateDir = makeTempDir("t3code-orchestration-turn-diff-");
125− const engine = new OrchestrationEngine(stateDir);
126+ const firstSystem = await createOrchestrationSystem(stateDir);
127+ const engine = firstSystem.engine;
126128 const completedAt = new Date().toISOString();
127− await engine.start();
128129 await engine.dispatch({
129130 type: "project.create",
130131 commandId: "cmd-project-turn-diff",
@@ -167,10 +168,10 @@ describe("OrchestrationEngine", () => { · 36 unmodified lines
167168 checkpointTurnCount: 1,
168169 },
169170 ]);
170− await engine.stop();
171+ await firstSystem.dispose();
171172
172− const restartedEngine = new OrchestrationEngine(stateDir);
173− await restartedEngine.start();
173+ const secondSystem = await createOrchestrationSystem(stateDir);
174+ const restartedEngine = secondSystem.engine;
174175 const restartedThread = restartedEngine
175176 .getSnapshot()
176177 .threads.find((thread) => thread.id === "thread-turn-diff");
@@ -183,14 +184,14 @@ describe("OrchestrationEngine", () => { · 6 unmodified lines
183184 checkpointTurnCount: 1,
184185 },
185186 ]);
186− await restartedEngine.stop();
187+ await secondSystem.dispose();
187188 });
188189
189190 it("reverts thread messages and turn summaries to a checkpoint", async () => {
190191 const stateDir = makeTempDir("t3code-orchestration-revert-");
191− const engine = new OrchestrationEngine(stateDir);
192+ const firstSystem = await createOrchestrationSystem(stateDir);
193+ const engine = firstSystem.engine;
192194 const createdAt = new Date().toISOString();
193− await engine.start();
194195 await engine.dispatch({
195196 type: "project.create",
196197 commandId: "cmd-project-revert",
@@ -291,10 +292,10 @@ describe("OrchestrationEngine", () => { · 94 unmodified lines
291292 expect(thread?.messages.map((message) => message.id)).toEqual(["user-1", "assistant:turn-1"]);
292293 expect(thread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]);
293294 expect(thread?.latestTurnId).toBe("turn-1");
294− await engine.stop();
295+ await firstSystem.dispose();
295296
296− const restarted = new OrchestrationEngine(stateDir);
297− await restarted.start();
297+ const secondSystem = await createOrchestrationSystem(stateDir);
298+ const restarted = secondSystem.engine;
298299 const restartedThread = restarted
299300 .getSnapshot()
300301 .threads.find((entry) => entry.id === "thread-revert");
@@ -303,12 +304,22 @@ describe("OrchestrationEngine", () => { · 2 unmodified lines
303304 "assistant:turn-1",
304305 ]);
305306 expect(restartedThread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]);
306− await restarted.stop();
307+ await secondSystem.dispose();
307308 });
308309
309310 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);
312323 await engine.start();
313324 await engine.stop();
314325 await expect(engine.stop()).resolves.toBeUndefined();
@@ -341,7 +352,6 @@ describe("OrchestrationEngine", () => { · 26 unmodified lines
341352 readAll() {
342353 return Effect.succeed(events);
343354 },
344− close() {},
345355 };
346356
347357 const engine = new OrchestrationEngine(flakyStore);
apps/server/src/orchestration/engine.ts+6−13
@@ -1,5 +1,3 @@
1−import path from "node:path";
2−
31 import type {
42 OrchestrationCommand,
53 OrchestrationEvent,
@@ -21,7 +19,6 @@ import { · 15 unmodified lines
2119
2220 import { createLogger } from "../logger";
2321 import type { OrchestrationEventRepositoryShape } from "../persistence/Services/OrchestrationEvents";
24−import { makeSqliteOrchestrationEventRepository } from "../persistence/Layers/OrchestrationEvents";
2522 import { createEmptyReadModel, reduceEvent } from "./reducer";
2623 import { UI_ENTITY_CONTRACTS } from "./uiContractInventory";
2724
@@ -215,15 +212,8 @@ export class OrchestrationEngine { · 187 unmodified lines
215212 private readonly readModelListeners = new Set<(snapshot: OrchestrationReadModel) => void>();
216213 private readonly domainEventListeners = new Set<(event: OrchestrationEvent) => void>();
217214
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;
227217 this.readModel = createEmptyReadModel(new Date().toISOString());
228218 this.commandQueue = Runtime.runSync(this.runtime)(Queue.unbounded<CommandEnvelope>());
229219 this.readModelPubSub = Runtime.runSync(this.runtime)(
@@ -279,6 +269,10 @@ export class OrchestrationEngine { · 49 unmodified lines
279269 }
280270
281271 async start(): Promise<void> {
272+ if (this.workerFiber) {
273+ return;
274+ }
275+
282276 await Runtime.runPromise(this.runtime)(this.bootstrapReadModel());
283277
284278 const worker = Stream.fromQueue(this.commandQueue).pipe(
@@ -297,7 +291,6 @@ export class OrchestrationEngine { · 12 unmodified lines
297291 await Runtime.runPromise(this.runtime)(Fiber.interrupt(this.workerFiber));
298292 this.workerFiber = null;
299293 }
300− this.eventStore.close();
301294 }
302295
303296 getSnapshot(): OrchestrationReadModel {
apps/server/src/orchestration/layers.test.ts+7−7
@@ -4,7 +4,7 @@ import path from "node:path"; · 3 unmodified lines
44
55 import { afterEach, describe, expect, it } from "vitest";
66
7−import { createOrchestrationEngine } from "./runtime";
7+import { createOrchestrationSystem } from "./runtime";
88
99 const tempDirs: string[] = [];
1010
@@ -24,9 +24,9 @@ describe("orchestration layers", () => { · 13 unmodified lines
2424 it("creates an engine through Context.Tag wiring", async () => {
2525 const stateDir = makeTempDir("t3code-orchestration-layer-");
2626 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();
3030 await engine.dispatch({
3131 type: "project.create",
3232 commandId: "cmd-layer-1",
@@ -37,11 +37,11 @@ describe("orchestration layers", () => { · 4 unmodified lines
3737 createdAt,
3838 });
3939 const firstSnapshot = engine.getSnapshot();
40− await engine.stop();
40+ await firstSystem.dispose();
4141
42− const restarted = createOrchestrationEngine(stateDir);
43− await restarted.start();
42+ const secondSystem = await createOrchestrationSystem(stateDir);
43+ const restarted = secondSystem.engine;
4444 expect(restarted.getSnapshot()).toEqual(firstSnapshot);
45− await restarted.stop();
45+ await secondSystem.dispose();
4646 });
4747 });
apps/server/src/orchestration/layers.ts+13−8
@@ -3,23 +3,28 @@ import path from "node:path"; · 2 unmodified lines
33 import { Effect, Layer } from "effect";
44
55 import { OrchestrationEventRepository } from "../persistence/Services/OrchestrationEvents";
6−import { makeSqliteOrchestrationEventRepository } from "../persistence/Layers/OrchestrationEvents";
6+import { makeSqliteOrchestrationEventRepositoryLive } from "../persistence/Layers/OrchestrationEvents";
77 import { OrchestrationEngine } from "./engine";
88 import { OrchestrationConfig, OrchestrationEngineService } from "./services";
99
10−export const OrchestrationEventRepositoryLive = Layer.effect(
11− OrchestrationEventRepository,
10+export const OrchestrationEventRepositoryLive = Layer.unwrapEffect(
1211 Effect.map(OrchestrationConfig, ({ stateDir }) => {
1312 const dbPath = path.join(stateDir, "orchestration.sqlite");
14− return makeSqliteOrchestrationEventRepository(dbPath);
13+ return makeSqliteOrchestrationEventRepositoryLive(dbPath);
1514 }),
1615 );
1716
18−export const OrchestrationEngineLive = Layer.effect(
17+export const OrchestrationEngineLive = Layer.scoped(
1918 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+ ),
2328 );
2429
2530 export const OrchestrationLive = OrchestrationEngineLive.pipe(
apps/server/src/orchestration/runtime.ts+13−5
@@ -1,14 +1,22 @@
1−import { Effect, Layer, Runtime } from "effect";
1+import { Layer, ManagedRuntime } from "effect";
22
33 import type { OrchestrationEngine } from "./engine";
44 import { OrchestrationLive } from "./layers";
55 import { OrchestrationConfig, OrchestrationEngineService } from "./services";
66
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> {
813 const orchestrationLayer = OrchestrationLive.pipe(
914 Layer.provide(Layer.succeed(OrchestrationConfig, { stateDir })),
1015 );
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+ };
1422 }
apps/server/src/persistence/Layers/OrchestrationEvents.test.ts+16−11
@@ -3,10 +3,11 @@ import os from "node:os"; · 2 unmodified lines
33 import path from "node:path";
44 import { DatabaseSync } from "node:sqlite";
55
6−import { Runtime } from "effect";
6+import { Runtime, ManagedRuntime } from "effect";
77 import { afterEach, describe, expect, it } from "vitest";
88
9−import { makeSqliteOrchestrationEventRepository } from "./OrchestrationEvents";
9+import { OrchestrationEventRepository } from "../Services/OrchestrationEvents";
10+import { makeSqliteOrchestrationEventRepositoryLive } from "./OrchestrationEvents";
1011
1112 const tempDirs: string[] = [];
1213
@@ -22,14 +23,15 @@ afterEach(() => { · 9 unmodified lines
2223 }
2324 });
2425
25−describe("SqliteEventStore", () => {
26+describe("OrchestrationEventRepository", () => {
2627 it("persists and replays events across restarts", async () => {
2728 const stateDir = makeTempDir("t3code-event-store-");
2829 const dbPath = path.join(stateDir, "orchestration.sqlite");
2930 const runtime = Runtime.defaultRuntime;
3031 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);
3335 const saved = await Runtime.runPromise(runtime)(
3436 first.append({
3537 eventId: "event-1",
@@ -42,12 +44,13 @@ describe("SqliteEventStore", () => { · 6 unmodified lines
4244 }),
4345 );
4446 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);
4851 const replayed = await Runtime.runPromise(runtime)(second.readFromSequence(0));
4952 expect(replayed).toEqual([saved]);
50− second.close();
53+ await secondRuntime.dispose();
5154 });
5255
5356 it("creates and reuses the migrator tracking table", async () => {
@@ -55,13 +58,15 @@ describe("SqliteEventStore", () => { · 1 unmodified line
5558 const dbPath = path.join(stateDir, "orchestration.sqlite");
5659 const runtime = Runtime.defaultRuntime;
5760
58− const first = makeSqliteOrchestrationEventRepository(dbPath);
61+ const firstRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath));
62+ const first = await firstRuntime.runPromise(OrchestrationEventRepository);
5963 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);
6368 await Runtime.runPromise(runtime)(second.readAll());
64− second.close();
69+ await secondRuntime.dispose();
6570
6671 const db = new DatabaseSync(dbPath, { readOnly: true });
6772 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"; · 6 unmodified lines
77 import * as SqlClient from "@effect/sql/SqlClient";
88 import * as SqlSchema from "@effect/sql/SqlSchema";
99 import * as SqliteClient from "@effect/sql-sqlite-node/SqliteClient";
10−import { Effect, ManagedRuntime, Schema } from "effect";
10+import { Context, Effect, Layer, Schema } from "effect";
1111
12−import type { OrchestrationEventRepositoryShape } from "../Services/OrchestrationEvents";
1312 import { runMigrations } from "../Migrations";
13+import {
14+ OrchestrationEventRepository,
15+ type OrchestrationEventRepositoryShape,
16+} from "../Services/OrchestrationEvents";
1417
1518 const decodeEvent = Schema.decodeUnknownSync(OrchestrationEventSchema);
1619
@@ -42,11 +45,31 @@ const ReadFromSequenceRequestSchema = Schema.Struct({ · 25 unmodified lines
4245 limit: Schema.Number,
4346 });
4447
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) =>
5073 sql`
5174 INSERT INTO orchestration_events (
5275 event_id,
@@ -76,14 +99,12 @@ const appendEventRow = SqlSchema.single({ · 23 unmodified lines
7699 command_id AS "commandId",
77100 payload_json AS "payloadJson"
78101 `,
79− ),
80−});
102+ });
81103
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) =>
87108 sql`
88109 SELECT
89110 sequence,
@@ -99,92 +120,59 @@ const readEventRowsFromSequence = SqlSchema.findAll({ · 9 unmodified lines
99120 ORDER BY sequence ASC
100121 LIMIT ${request.limit}
101122 `,
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,
115123 });
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);
143124
144125 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);
160135
161136 const readFromSequence: OrchestrationEventRepositoryShape["readFromSequence"] = (
162137 sequenceExclusive,
163138 limit = 1_000,
164139 ) =>
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+ );
183144
184145 return {
185146 append,
186147 readFromSequence,
187148 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));
190178 }
apps/server/src/persistence/Services/OrchestrationEvents.ts+0−1
@@ -11,7 +11,6 @@ export interface OrchestrationEventRepositoryShape { · 10 unmodified lines
1111 limit?: number,
1212 ) => Effect.Effect<OrchestrationEvent[]>;
1313 readonly readAll: () => Effect.Effect<OrchestrationEvent[]>;
14− readonly close: () => void;
1514 }
1615
1716 export class OrchestrationEventRepository extends Context.Tag("orchestration/EventRepository")<
apps/server/src/wsServer.ts+90−49
@@ -36,7 +36,7 @@ import { · 35 unmodified lines
3636 import { TerminalManager } from "./terminalManager";
3737 import { loadResolvedKeybindingsConfig, upsertKeybindingRule } from "./keybindings";
3838 import { searchWorkspaceEntries } from "./workspaceEntries";
39−import { createOrchestrationEngine } from "./orchestration/runtime";
39+import { createOrchestrationSystem, type OrchestrationSystem } from "./orchestration/runtime";
4040
4141 const MIME_TYPES: Record<string, string> = {
4242 ".html": "text/html; charset=utf-8",
@@ -88,6 +88,8 @@ function asNumber(value: unknown): number | null { · 45 unmodified lines
8888 return typeof value === "number" && Number.isFinite(value) ? value : null;
8989 }
9090
91+const noop = () => {};
92+
9193 export function createServer(options: ServerOptions) {
9294 const {
9395 port,
@@ -107,14 +109,15 @@ export function createServer(options: ServerOptions) { · 13 unmodified lines
107109 const projectRegistry =
108110 providedRegistry ?? new ProjectRegistry(path.join(os.homedir(), ".t3", "userdata"));
109111 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;
113114 const clients = new Set<WebSocket>();
114115 const logger = createLogger("ws");
115116 const logWebSocketEvents =
116117 explicitLogWsEvents ?? parseBooleanEnv(process.env.T3CODE_LOG_WS_EVENTS) ?? Boolean(devUrl);
117118 let keybindingsConfig = loadResolvedKeybindingsConfig(logger);
119+ let unsubscribeReadModel = noop;
120+ let unsubscribeDomainEvents = noop;
118121
119122 function logOutgoingPush(push: WsPush, recipients: number) {
120123 if (!logWebSocketEvents) return;
@@ -125,46 +128,62 @@ export function createServer(options: ServerOptions) { · 4 unmodified lines
125128 });
126129 }
127130
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");
144134 }
145− logOutgoingPush(push, recipients);
146− });
135+ return orchestrationSystem.engine;
136+ }
147137
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+ }
160157 }
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+ }
164178
165179 // Forward provider events to all connected WebSocket clients
166180 providerManager.on("event", (event) => {
167181 void (async () => {
182+ const liveOrchestrationSystem = orchestrationSystem;
183+ if (!liveOrchestrationSystem) {
184+ return;
185+ }
186+ const orchestrationEngine = liveOrchestrationSystem.engine;
168187 const snapshot = orchestrationEngine.getSnapshot();
169188 const thread = snapshot.threads.find((entry) => entry.session?.sessionId === event.sessionId);
170189 if (!thread) return;
@@ -367,6 +386,7 @@ export function createServer(options: ServerOptions) { · 196 unmodified lines
367386
368387 wss.on("connection", (ws) => {
369388 clients.add(ws);
389+ const orchestrationEngine = requireOrchestrationEngine();
370390
371391 // Send welcome message with project info
372392 const segments = cwd.split(/[/\\]/).filter(Boolean);
@@ -433,6 +453,8 @@ export function createServer(options: ServerOptions) { · 60 unmodified lines
433453 }
434454
435455 async function routeRequest(request: WsRequest): Promise<unknown> {
456+ const orchestrationEngine = requireOrchestrationEngine();
457+
436458 switch (request.method) {
437459 case WS_METHODS.providersStartSession:
438460 return providerManager.startSession(request.params as never);
@@ -676,21 +698,42 @@ export function createServer(options: ServerOptions) { · 237 unmodified lines
676698 }
677699 }
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+
680728 return new Promise<void>((resolve, reject) => {
681729 const onError = (error: Error) => {
682730 httpServer.off("error", onError);
683− reject(error);
731+ void disposeOrchestrationSystem().finally(() => reject(error));
684732 };
685733 httpServer.once("error", onError);
686− const onListening = async () => {
734+ const onListening = () => {
687735 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();
694737 };
695738 if (host) {
696739 httpServer.listen(port, host, onListening);
@@ -701,9 +744,7 @@ export function createServer(options: ServerOptions) { · 4 unmodified lines
701744 }
702745
703746 async function stop(): Promise<void> {
704− unsubscribeReadModel();
705− unsubscribeDomainEvents();
706− await orchestrationEngine.stop();
747+ await disposeOrchestrationSystem();
707748 terminalManager.off("event", onTerminalEvent);
708749 providerManager.stopAll();
709750 providerManager.dispose();
bun.lock+46−0
@@ -34,6 +34,8 @@ · 33 unmodified lines
3434 "t3": "./dist/index.mjs",
3535 },
3636 "dependencies": {
37+ "@effect/platform": "^0.94.5",
38+ "@effect/platform-node": "^0.104.1",
3739 "@effect/sql": "^0.49.0",
3840 "@effect/sql-sqlite-node": "^0.50.1",
3941 "effect": "^3.19.19",
@@ -148,14 +150,24 @@ · 108 unmodified lines
148150
149151 "@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+
151155 "@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
153157 "@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+
155165 "@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
157167 "@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+
159171 "@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
161173 "@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
336348
337349 "@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+
339379 "@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
341381 "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="],
@@ -920,6 +960,8 @@ · 578 unmodified lines
920960
921961 "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+
923965 "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
925967 "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
10561098
10571099 "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+
10591103 "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
10601104
10611105 "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
13421386
13431387 "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+
13451391 "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
13461392
13471393 "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=="],

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t