@wopr-network/defcon 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/src/execution/cli.js +42 -5
  2. package/dist/src/litestream/cli-integration.test.d.ts +1 -0
  3. package/dist/src/litestream/cli-integration.test.js +35 -0
  4. package/dist/src/litestream/manager.d.ts +23 -0
  5. package/dist/src/litestream/manager.js +123 -0
  6. package/dist/src/litestream/manager.test.d.ts +1 -0
  7. package/dist/src/litestream/manager.test.js +46 -0
  8. package/dist/src/repositories/drizzle/domain-event.repo.d.ts +1 -0
  9. package/dist/src/repositories/drizzle/domain-event.repo.js +4 -1
  10. package/dist/src/repositories/drizzle/entity-snapshot.repo.d.ts +14 -0
  11. package/dist/src/repositories/drizzle/entity-snapshot.repo.js +68 -0
  12. package/dist/src/repositories/drizzle/index.d.ts +1 -0
  13. package/dist/src/repositories/drizzle/index.js +1 -0
  14. package/dist/src/repositories/drizzle/schema.d.ts +329 -0
  15. package/dist/src/repositories/drizzle/schema.js +23 -0
  16. package/dist/src/repositories/event-sourced/entity.repo.d.ts +29 -0
  17. package/dist/src/repositories/event-sourced/entity.repo.js +84 -0
  18. package/dist/src/repositories/event-sourced/index.d.ts +3 -0
  19. package/dist/src/repositories/event-sourced/index.js +3 -0
  20. package/dist/src/repositories/event-sourced/invocation.repo.d.ts +20 -0
  21. package/dist/src/repositories/event-sourced/invocation.repo.js +61 -0
  22. package/dist/src/repositories/event-sourced/replay.d.ts +11 -0
  23. package/dist/src/repositories/event-sourced/replay.js +135 -0
  24. package/dist/src/repositories/interfaces.d.ts +11 -0
  25. package/drizzle/0015_clean_redwing.sql +11 -0
  26. package/drizzle/0016_hesitant_bill_hollister.sql +22 -0
  27. package/drizzle/0017_hesitant_bill_hollister.sql +22 -0
  28. package/drizzle/0018_volatile_reavers.sql +1 -0
  29. package/drizzle/meta/0016_snapshot.json +1316 -0
  30. package/drizzle/meta/0018_snapshot.json +1351 -0
  31. package/drizzle/meta/_journal.json +15 -1
  32. package/package.json +1 -1
@@ -15,9 +15,11 @@ import { resolveCorsOrigin } from "../cors.js";
15
15
  import { DomainEventPersistAdapter } from "../engine/domain-event-adapter.js";
16
16
  import { Engine } from "../engine/engine.js";
17
17
  import { EventEmitter } from "../engine/event-emitter.js";
18
+ import { buildConfigFromEnv, isLitestreamEnabled, LitestreamManager } from "../litestream/manager.js";
18
19
  import { withTransaction } from "../main.js";
19
20
  import { DrizzleDomainEventRepository } from "../repositories/drizzle/domain-event.repo.js";
20
21
  import { DrizzleEntityRepository } from "../repositories/drizzle/entity.repo.js";
22
+ import { DrizzleEntitySnapshotRepository } from "../repositories/drizzle/entity-snapshot.repo.js";
21
23
  import { DrizzleEventRepository } from "../repositories/drizzle/event.repo.js";
22
24
  import { DrizzleFlowRepository } from "../repositories/drizzle/flow.repo.js";
23
25
  import { DrizzleGateRepository } from "../repositories/drizzle/gate.repo.js";
@@ -25,6 +27,8 @@ import { DrizzleInvocationRepository } from "../repositories/drizzle/invocation.
25
27
  import * as schema from "../repositories/drizzle/schema.js";
26
28
  import { entities, entityHistory, flowDefinitions, flowVersions, gateDefinitions, gateResults, invocations, stateDefinitions, transitionRules, } from "../repositories/drizzle/schema.js";
27
29
  import { DrizzleTransitionLogRepository } from "../repositories/drizzle/transition-log.repo.js";
30
+ import { EventSourcedEntityRepository } from "../repositories/event-sourced/entity.repo.js";
31
+ import { EventSourcedInvocationRepository } from "../repositories/event-sourced/invocation.repo.js";
28
32
  import { UiSseAdapter } from "../ui/sse.js";
29
33
  import { WebSocketBroadcaster } from "../ws/broadcast.js";
30
34
  import { createMcpServer, startStdioServer } from "./mcp-server.js";
@@ -158,13 +162,38 @@ program
158
162
  .option("--http-host <address>", "Host for HTTP REST API", "127.0.0.1")
159
163
  .option("--ui", "Enable built-in web UI at /ui")
160
164
  .action(async (opts) => {
165
+ // Litestream: restore from replica if DB missing and replication configured
166
+ let litestreamMgr;
167
+ if (isLitestreamEnabled()) {
168
+ const lsConfig = buildConfigFromEnv(opts.db);
169
+ litestreamMgr = new LitestreamManager(lsConfig);
170
+ litestreamMgr.restore();
171
+ }
161
172
  const { db, sqlite } = openDb(opts.db);
162
- const entityRepo = new DrizzleEntityRepository(db);
173
+ // Litestream: start continuous replication
174
+ if (litestreamMgr) {
175
+ litestreamMgr.start();
176
+ }
177
+ const mutableEntityRepo = new DrizzleEntityRepository(db);
163
178
  const flowRepo = new DrizzleFlowRepository(db);
164
- const invocationRepo = new DrizzleInvocationRepository(db);
179
+ const mutableInvocationRepo = new DrizzleInvocationRepository(db);
165
180
  const gateRepo = new DrizzleGateRepository(db);
166
181
  const transitionLogRepo = new DrizzleTransitionLogRepository(db);
167
182
  const domainEventRepo = new DrizzleDomainEventRepository(db);
183
+ const useEventSourced = process.env.DEFCON_EVENT_SOURCED === "true";
184
+ const snapshotInterval = parseInt(process.env.DEFCON_SNAPSHOT_INTERVAL ?? "10", 10);
185
+ let entityRepo;
186
+ let invocationRepo;
187
+ if (useEventSourced) {
188
+ const snapshotRepo = new DrizzleEntitySnapshotRepository(db);
189
+ entityRepo = new EventSourcedEntityRepository(mutableEntityRepo, domainEventRepo, snapshotRepo, snapshotInterval);
190
+ invocationRepo = new EventSourcedInvocationRepository(mutableInvocationRepo, domainEventRepo);
191
+ process.stderr.write("[defcon] Event-sourced repositories enabled\n");
192
+ }
193
+ else {
194
+ entityRepo = mutableEntityRepo;
195
+ invocationRepo = mutableInvocationRepo;
196
+ }
168
197
  const eventEmitter = new EventEmitter();
169
198
  eventEmitter.register({
170
199
  emit: async (event) => {
@@ -359,7 +388,12 @@ program
359
388
  });
360
389
  const shutdown = makeShutdownHandler({
361
390
  stopReaper,
362
- closeables: [{ close: () => restHttpServer?.close() }, httpServer, sqlite],
391
+ closeables: [
392
+ ...(litestreamMgr ? [litestreamMgr] : []),
393
+ { close: () => restHttpServer?.close() },
394
+ httpServer,
395
+ sqlite,
396
+ ],
363
397
  });
364
398
  process.once("SIGINT", shutdown);
365
399
  process.once("SIGTERM", shutdown);
@@ -367,7 +401,10 @@ program
367
401
  else if (startMcp) {
368
402
  // stdio (default)
369
403
  console.error("Starting MCP server on stdio...");
370
- const cleanup = makeShutdownHandler({ stopReaper, closeables: [sqlite] });
404
+ const cleanup = makeShutdownHandler({
405
+ stopReaper,
406
+ closeables: [...(litestreamMgr ? [litestreamMgr] : []), sqlite],
407
+ });
371
408
  process.once("SIGINT", cleanup);
372
409
  process.once("SIGTERM", cleanup);
373
410
  const mcpOpts = { adminToken, workerToken, stdioTrusted: true };
@@ -377,7 +414,7 @@ program
377
414
  // HTTP-only mode — keep process alive
378
415
  const cleanup = makeShutdownHandler({
379
416
  stopReaper,
380
- closeables: [{ close: () => restHttpServer?.close() }, sqlite],
417
+ closeables: [...(litestreamMgr ? [litestreamMgr] : []), { close: () => restHttpServer?.close() }, sqlite],
381
418
  });
382
419
  process.once("SIGINT", cleanup);
383
420
  process.once("SIGTERM", cleanup);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { buildConfigFromEnv } from "./manager.js";
3
+ describe("buildConfigFromEnv", () => {
4
+ const originalEnv = process.env;
5
+ beforeEach(() => {
6
+ process.env = { ...originalEnv };
7
+ });
8
+ afterEach(() => {
9
+ process.env = originalEnv;
10
+ });
11
+ it("builds config from env vars with defaults", () => {
12
+ process.env.DEFCON_LITESTREAM_REPLICA_URL = "s3://bucket/db";
13
+ process.env.DEFCON_LITESTREAM_ACCESS_KEY_ID = "AKIA";
14
+ process.env.DEFCON_LITESTREAM_SECRET_ACCESS_KEY = "secret";
15
+ const config = buildConfigFromEnv("./defcon.db");
16
+ expect(config.replicaUrl).toBe("s3://bucket/db");
17
+ expect(config.region).toBe("us-east-1");
18
+ expect(config.retention).toBe("24h");
19
+ expect(config.syncInterval).toBe("1s");
20
+ expect(config.endpoint).toBeUndefined();
21
+ });
22
+ it("throws if access keys missing", () => {
23
+ process.env.DEFCON_LITESTREAM_REPLICA_URL = "s3://bucket/db";
24
+ delete process.env.DEFCON_LITESTREAM_ACCESS_KEY_ID;
25
+ expect(() => buildConfigFromEnv("./defcon.db")).toThrow("DEFCON_LITESTREAM_ACCESS_KEY_ID");
26
+ });
27
+ it("includes custom endpoint", () => {
28
+ process.env.DEFCON_LITESTREAM_REPLICA_URL = "s3://bucket/db";
29
+ process.env.DEFCON_LITESTREAM_ACCESS_KEY_ID = "AKIA";
30
+ process.env.DEFCON_LITESTREAM_SECRET_ACCESS_KEY = "secret";
31
+ process.env.DEFCON_LITESTREAM_ENDPOINT = "https://r2.example.com";
32
+ const config = buildConfigFromEnv("./defcon.db");
33
+ expect(config.endpoint).toBe("https://r2.example.com");
34
+ });
35
+ });
@@ -0,0 +1,23 @@
1
+ export interface LitestreamConfig {
2
+ dbPath: string;
3
+ replicaUrl: string;
4
+ accessKeyId: string;
5
+ secretAccessKey: string;
6
+ endpoint?: string;
7
+ region: string;
8
+ retention: string;
9
+ syncInterval: string;
10
+ }
11
+ export declare function isLitestreamEnabled(): boolean;
12
+ export declare function buildConfigFromEnv(dbPath: string): LitestreamConfig;
13
+ export declare class LitestreamManager {
14
+ private config;
15
+ private configPath;
16
+ private child;
17
+ constructor(config: LitestreamConfig);
18
+ generateConfig(): string;
19
+ restore(): void;
20
+ start(): void;
21
+ close(): Promise<void>;
22
+ private writeConfig;
23
+ }
@@ -0,0 +1,123 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ export function isLitestreamEnabled() {
6
+ return !!process.env.DEFCON_LITESTREAM_REPLICA_URL?.trim();
7
+ }
8
+ export function buildConfigFromEnv(dbPath) {
9
+ const replicaUrl = process.env.DEFCON_LITESTREAM_REPLICA_URL ?? "";
10
+ const accessKeyId = process.env.DEFCON_LITESTREAM_ACCESS_KEY_ID;
11
+ const secretAccessKey = process.env.DEFCON_LITESTREAM_SECRET_ACCESS_KEY;
12
+ if (!accessKeyId || !secretAccessKey) {
13
+ throw new Error("DEFCON_LITESTREAM_ACCESS_KEY_ID and DEFCON_LITESTREAM_SECRET_ACCESS_KEY must be set when DEFCON_LITESTREAM_REPLICA_URL is configured");
14
+ }
15
+ return {
16
+ dbPath: resolve(dbPath),
17
+ replicaUrl,
18
+ accessKeyId,
19
+ secretAccessKey,
20
+ endpoint: process.env.DEFCON_LITESTREAM_ENDPOINT || undefined,
21
+ region: process.env.DEFCON_LITESTREAM_REGION || "us-east-1",
22
+ retention: process.env.DEFCON_LITESTREAM_RETENTION || "24h",
23
+ syncInterval: process.env.DEFCON_LITESTREAM_SYNC_INTERVAL || "1s",
24
+ };
25
+ }
26
+ export class LitestreamManager {
27
+ config;
28
+ configPath;
29
+ child = null;
30
+ constructor(config) {
31
+ this.config = config;
32
+ this.configPath = join(tmpdir(), `litestream-${process.pid}.yml`);
33
+ }
34
+ generateConfig() {
35
+ const q = (v) => `'${v.replace(/'/g, "''")}'`;
36
+ const endpoint = this.config.endpoint ? ` endpoint: ${q(this.config.endpoint)}\n` : "";
37
+ return `dbs:
38
+ - path: ${q(this.config.dbPath)}
39
+ replicas:
40
+ - type: s3
41
+ url: ${q(this.config.replicaUrl)}
42
+ ${endpoint} region: ${q(this.config.region)}
43
+ retention: ${q(this.config.retention)}
44
+ sync-interval: ${q(this.config.syncInterval)}
45
+ `;
46
+ }
47
+ restore() {
48
+ if (existsSync(this.config.dbPath)) {
49
+ process.stderr.write(`[litestream] DB exists at ${this.config.dbPath}, skipping restore\n`);
50
+ return;
51
+ }
52
+ const dir = dirname(this.config.dbPath);
53
+ if (!existsSync(dir)) {
54
+ mkdirSync(dir, { recursive: true });
55
+ }
56
+ this.writeConfig();
57
+ process.stderr.write(`[litestream] Restoring from ${this.config.replicaUrl}...\n`);
58
+ const result = spawnSync("litestream", ["restore", "-config", this.configPath, this.config.dbPath], {
59
+ stdio: ["ignore", "pipe", "pipe"],
60
+ timeout: 120_000,
61
+ env: {
62
+ ...process.env,
63
+ LITESTREAM_ACCESS_KEY_ID: this.config.accessKeyId,
64
+ LITESTREAM_SECRET_ACCESS_KEY: this.config.secretAccessKey,
65
+ },
66
+ });
67
+ if (result.status !== 0) {
68
+ const stderr = result.stderr?.toString() ?? "";
69
+ if (stderr.includes("no generations found")) {
70
+ process.stderr.write(`[litestream] No replica found, starting fresh\n`);
71
+ }
72
+ else {
73
+ throw new Error(`[litestream] Restore failed: ${stderr}`);
74
+ }
75
+ }
76
+ else {
77
+ process.stderr.write(`[litestream] Restore complete\n`);
78
+ }
79
+ }
80
+ start() {
81
+ this.writeConfig();
82
+ process.stderr.write(`[litestream] Starting replication to ${this.config.replicaUrl}\n`);
83
+ this.child = spawn("litestream", ["replicate", "-config", this.configPath], {
84
+ stdio: ["ignore", "pipe", "pipe"],
85
+ env: {
86
+ ...process.env,
87
+ LITESTREAM_ACCESS_KEY_ID: this.config.accessKeyId,
88
+ LITESTREAM_SECRET_ACCESS_KEY: this.config.secretAccessKey,
89
+ },
90
+ });
91
+ this.child.stdout?.on("data", (chunk) => {
92
+ process.stderr.write(`[litestream] ${chunk.toString()}`);
93
+ });
94
+ this.child.stderr?.on("data", (chunk) => {
95
+ process.stderr.write(`[litestream] ${chunk.toString()}`);
96
+ });
97
+ this.child.on("exit", (code) => {
98
+ process.stderr.write(`[litestream] Process exited with code ${code}\n`);
99
+ this.child = null;
100
+ });
101
+ this.child.on("error", (err) => {
102
+ process.stderr.write(`[litestream] Process error: ${err.message}\n`);
103
+ this.child = null;
104
+ });
105
+ }
106
+ close() {
107
+ if (!this.child) {
108
+ return Promise.resolve();
109
+ }
110
+ const child = this.child;
111
+ return new Promise((resolve) => {
112
+ process.stderr.write(`[litestream] Stopping replication\n`);
113
+ child.once("exit", () => {
114
+ this.child = null;
115
+ resolve();
116
+ });
117
+ child.kill("SIGTERM");
118
+ });
119
+ }
120
+ writeConfig() {
121
+ writeFileSync(this.configPath, this.generateConfig());
122
+ }
123
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { isLitestreamEnabled, LitestreamManager } from "./manager.js";
3
+ describe("isLitestreamEnabled", () => {
4
+ it("returns false when DEFCON_LITESTREAM_REPLICA_URL is not set", () => {
5
+ delete process.env.DEFCON_LITESTREAM_REPLICA_URL;
6
+ expect(isLitestreamEnabled()).toBe(false);
7
+ });
8
+ it("returns true when DEFCON_LITESTREAM_REPLICA_URL is set", () => {
9
+ process.env.DEFCON_LITESTREAM_REPLICA_URL = "s3://bucket/defcon.db";
10
+ expect(isLitestreamEnabled()).toBe(true);
11
+ delete process.env.DEFCON_LITESTREAM_REPLICA_URL;
12
+ });
13
+ });
14
+ describe("LitestreamManager", () => {
15
+ it("generates correct YAML config", () => {
16
+ const mgr = new LitestreamManager({
17
+ dbPath: "/data/defcon.db",
18
+ replicaUrl: "s3://bucket/defcon.db",
19
+ accessKeyId: "AKIA...",
20
+ secretAccessKey: "secret",
21
+ region: "us-east-1",
22
+ retention: "24h",
23
+ syncInterval: "1s",
24
+ });
25
+ const yaml = mgr.generateConfig();
26
+ expect(yaml).toContain("'/data/defcon.db'");
27
+ expect(yaml).toContain("'s3://bucket/defcon.db'");
28
+ expect(yaml).not.toContain("AKIA...");
29
+ expect(yaml).toContain("retention: '24h'");
30
+ expect(yaml).toContain("sync-interval: '1s'");
31
+ });
32
+ it("generates config with custom endpoint for R2", () => {
33
+ const mgr = new LitestreamManager({
34
+ dbPath: "/data/defcon.db",
35
+ replicaUrl: "s3://bucket/defcon.db",
36
+ accessKeyId: "key",
37
+ secretAccessKey: "secret",
38
+ endpoint: "https://account.r2.cloudflarestorage.com",
39
+ region: "auto",
40
+ retention: "24h",
41
+ syncInterval: "1s",
42
+ });
43
+ const yaml = mgr.generateConfig();
44
+ expect(yaml).toContain("endpoint: 'https://account.r2.cloudflarestorage.com'");
45
+ });
46
+ });
@@ -11,6 +11,7 @@ export declare class DrizzleDomainEventRepository implements IDomainEventReposit
11
11
  list(entityId: string, opts?: {
12
12
  type?: string;
13
13
  limit?: number;
14
+ minSequence?: number;
14
15
  }): Promise<DomainEvent[]>;
15
16
  }
16
17
  export {};
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { and, eq, sql } from "drizzle-orm";
2
+ import { and, eq, gt, sql } from "drizzle-orm";
3
3
  import { domainEvents } from "./schema.js";
4
4
  export class DrizzleDomainEventRepository {
5
5
  db;
@@ -62,6 +62,9 @@ export class DrizzleDomainEventRepository {
62
62
  if (opts?.type) {
63
63
  conditions.push(eq(domainEvents.type, opts.type));
64
64
  }
65
+ if (opts?.minSequence !== undefined) {
66
+ conditions.push(gt(domainEvents.sequence, opts.minSequence));
67
+ }
65
68
  const rows = this.db
66
69
  .select()
67
70
  .from(domainEvents)
@@ -0,0 +1,14 @@
1
+ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
2
+ import type { Entity, IEntitySnapshotRepository } from "../interfaces.js";
3
+ import type * as schema from "./schema.js";
4
+ type Db = BetterSQLite3Database<typeof schema>;
5
+ export declare class DrizzleEntitySnapshotRepository implements IEntitySnapshotRepository {
6
+ private readonly db;
7
+ constructor(db: Db);
8
+ save(entityId: string, sequence: number, state: Entity): Promise<void>;
9
+ loadLatest(entityId: string): Promise<{
10
+ sequence: number;
11
+ state: Entity;
12
+ } | null>;
13
+ }
14
+ export {};
@@ -0,0 +1,68 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { desc, eq } from "drizzle-orm";
3
+ import { entitySnapshots } from "./schema.js";
4
+ export class DrizzleEntitySnapshotRepository {
5
+ db;
6
+ constructor(db) {
7
+ this.db = db;
8
+ }
9
+ async save(entityId, sequence, state) {
10
+ const id = randomUUID();
11
+ this.db
12
+ .insert(entitySnapshots)
13
+ .values({
14
+ id,
15
+ entityId,
16
+ sequence,
17
+ state: state.state,
18
+ flowId: state.flowId,
19
+ refs: state.refs,
20
+ artifacts: state.artifacts,
21
+ claimedBy: state.claimedBy,
22
+ claimedAt: state.claimedAt?.getTime() ?? null,
23
+ flowVersion: state.flowVersion,
24
+ priority: state.priority,
25
+ affinityWorkerId: state.affinityWorkerId,
26
+ affinityRole: state.affinityRole,
27
+ affinityExpiresAt: state.affinityExpiresAt?.getTime() ?? null,
28
+ createdAt: state.createdAt.getTime(),
29
+ updatedAt: state.updatedAt.getTime(),
30
+ snapshotAt: Date.now(),
31
+ parentEntityId: state.parentEntityId,
32
+ })
33
+ .onConflictDoNothing()
34
+ .run();
35
+ }
36
+ async loadLatest(entityId) {
37
+ const rows = this.db
38
+ .select()
39
+ .from(entitySnapshots)
40
+ .where(eq(entitySnapshots.entityId, entityId))
41
+ .orderBy(desc(entitySnapshots.sequence))
42
+ .limit(1)
43
+ .all();
44
+ if (rows.length === 0)
45
+ return null;
46
+ const row = rows[0];
47
+ return {
48
+ sequence: row.sequence,
49
+ state: {
50
+ id: entityId,
51
+ flowId: row.flowId,
52
+ state: row.state,
53
+ refs: row.refs,
54
+ artifacts: row.artifacts,
55
+ claimedBy: row.claimedBy,
56
+ claimedAt: row.claimedAt ? new Date(row.claimedAt) : null,
57
+ flowVersion: row.flowVersion ?? 1,
58
+ priority: row.priority ?? 0,
59
+ createdAt: new Date(row.createdAt ?? 0),
60
+ updatedAt: new Date(row.updatedAt ?? 0),
61
+ affinityWorkerId: row.affinityWorkerId ?? null,
62
+ affinityRole: row.affinityRole ?? null,
63
+ affinityExpiresAt: row.affinityExpiresAt ? new Date(row.affinityExpiresAt) : null,
64
+ parentEntityId: row.parentEntityId ?? null,
65
+ },
66
+ };
67
+ }
68
+ }
@@ -1,5 +1,6 @@
1
1
  export { DrizzleDomainEventRepository } from "./domain-event.repo.js";
2
2
  export { DrizzleEntityRepository } from "./entity.repo.js";
3
+ export { DrizzleEntitySnapshotRepository } from "./entity-snapshot.repo.js";
3
4
  export { DrizzleEventRepository } from "./event.repo.js";
4
5
  export { DrizzleFlowRepository } from "./flow.repo.js";
5
6
  export { DrizzleGateRepository } from "./gate.repo.js";
@@ -1,6 +1,7 @@
1
1
  // Drizzle ORM implementations of repository interfaces
2
2
  export { DrizzleDomainEventRepository } from "./domain-event.repo.js";
3
3
  export { DrizzleEntityRepository } from "./entity.repo.js";
4
+ export { DrizzleEntitySnapshotRepository } from "./entity-snapshot.repo.js";
4
5
  export { DrizzleEventRepository } from "./event.repo.js";
5
6
  export { DrizzleFlowRepository } from "./flow.repo.js";
6
7
  export { DrizzleGateRepository } from "./gate.repo.js";