@wopr-network/defcon 1.12.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 (26) hide show
  1. package/dist/src/execution/cli.js +19 -2
  2. package/dist/src/repositories/drizzle/domain-event.repo.d.ts +1 -0
  3. package/dist/src/repositories/drizzle/domain-event.repo.js +4 -1
  4. package/dist/src/repositories/drizzle/entity-snapshot.repo.d.ts +14 -0
  5. package/dist/src/repositories/drizzle/entity-snapshot.repo.js +68 -0
  6. package/dist/src/repositories/drizzle/index.d.ts +1 -0
  7. package/dist/src/repositories/drizzle/index.js +1 -0
  8. package/dist/src/repositories/drizzle/schema.d.ts +329 -0
  9. package/dist/src/repositories/drizzle/schema.js +23 -0
  10. package/dist/src/repositories/event-sourced/entity.repo.d.ts +29 -0
  11. package/dist/src/repositories/event-sourced/entity.repo.js +84 -0
  12. package/dist/src/repositories/event-sourced/index.d.ts +3 -0
  13. package/dist/src/repositories/event-sourced/index.js +3 -0
  14. package/dist/src/repositories/event-sourced/invocation.repo.d.ts +20 -0
  15. package/dist/src/repositories/event-sourced/invocation.repo.js +61 -0
  16. package/dist/src/repositories/event-sourced/replay.d.ts +11 -0
  17. package/dist/src/repositories/event-sourced/replay.js +135 -0
  18. package/dist/src/repositories/interfaces.d.ts +11 -0
  19. package/drizzle/0015_clean_redwing.sql +11 -0
  20. package/drizzle/0016_hesitant_bill_hollister.sql +22 -0
  21. package/drizzle/0017_hesitant_bill_hollister.sql +22 -0
  22. package/drizzle/0018_volatile_reavers.sql +1 -0
  23. package/drizzle/meta/0016_snapshot.json +1316 -0
  24. package/drizzle/meta/0018_snapshot.json +1351 -0
  25. package/drizzle/meta/_journal.json +15 -1
  26. package/package.json +1 -1
@@ -19,6 +19,7 @@ import { buildConfigFromEnv, isLitestreamEnabled, LitestreamManager } from "../l
19
19
  import { withTransaction } from "../main.js";
20
20
  import { DrizzleDomainEventRepository } from "../repositories/drizzle/domain-event.repo.js";
21
21
  import { DrizzleEntityRepository } from "../repositories/drizzle/entity.repo.js";
22
+ import { DrizzleEntitySnapshotRepository } from "../repositories/drizzle/entity-snapshot.repo.js";
22
23
  import { DrizzleEventRepository } from "../repositories/drizzle/event.repo.js";
23
24
  import { DrizzleFlowRepository } from "../repositories/drizzle/flow.repo.js";
24
25
  import { DrizzleGateRepository } from "../repositories/drizzle/gate.repo.js";
@@ -26,6 +27,8 @@ import { DrizzleInvocationRepository } from "../repositories/drizzle/invocation.
26
27
  import * as schema from "../repositories/drizzle/schema.js";
27
28
  import { entities, entityHistory, flowDefinitions, flowVersions, gateDefinitions, gateResults, invocations, stateDefinitions, transitionRules, } from "../repositories/drizzle/schema.js";
28
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";
29
32
  import { UiSseAdapter } from "../ui/sse.js";
30
33
  import { WebSocketBroadcaster } from "../ws/broadcast.js";
31
34
  import { createMcpServer, startStdioServer } from "./mcp-server.js";
@@ -171,12 +174,26 @@ program
171
174
  if (litestreamMgr) {
172
175
  litestreamMgr.start();
173
176
  }
174
- const entityRepo = new DrizzleEntityRepository(db);
177
+ const mutableEntityRepo = new DrizzleEntityRepository(db);
175
178
  const flowRepo = new DrizzleFlowRepository(db);
176
- const invocationRepo = new DrizzleInvocationRepository(db);
179
+ const mutableInvocationRepo = new DrizzleInvocationRepository(db);
177
180
  const gateRepo = new DrizzleGateRepository(db);
178
181
  const transitionLogRepo = new DrizzleTransitionLogRepository(db);
179
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
+ }
180
197
  const eventEmitter = new EventEmitter();
181
198
  eventEmitter.register({
182
199
  emit: async (event) => {
@@ -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";
@@ -2149,3 +2149,332 @@ export declare const domainEvents: import("drizzle-orm/sqlite-core").SQLiteTable
2149
2149
  };
2150
2150
  dialect: "sqlite";
2151
2151
  }>;
2152
+ export declare const entitySnapshots: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
2153
+ name: "entity_snapshots";
2154
+ schema: undefined;
2155
+ columns: {
2156
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2157
+ name: "id";
2158
+ tableName: "entity_snapshots";
2159
+ dataType: "string";
2160
+ columnType: "SQLiteText";
2161
+ data: string;
2162
+ driverParam: string;
2163
+ notNull: true;
2164
+ hasDefault: false;
2165
+ isPrimaryKey: true;
2166
+ isAutoincrement: false;
2167
+ hasRuntimeDefault: false;
2168
+ enumValues: [string, ...string[]];
2169
+ baseColumn: never;
2170
+ identity: undefined;
2171
+ generated: undefined;
2172
+ }, {}, {
2173
+ length: number | undefined;
2174
+ }>;
2175
+ entityId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2176
+ name: "entity_id";
2177
+ tableName: "entity_snapshots";
2178
+ dataType: "string";
2179
+ columnType: "SQLiteText";
2180
+ data: string;
2181
+ driverParam: string;
2182
+ notNull: true;
2183
+ hasDefault: false;
2184
+ isPrimaryKey: false;
2185
+ isAutoincrement: false;
2186
+ hasRuntimeDefault: false;
2187
+ enumValues: [string, ...string[]];
2188
+ baseColumn: never;
2189
+ identity: undefined;
2190
+ generated: undefined;
2191
+ }, {}, {
2192
+ length: number | undefined;
2193
+ }>;
2194
+ sequence: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2195
+ name: "sequence";
2196
+ tableName: "entity_snapshots";
2197
+ dataType: "number";
2198
+ columnType: "SQLiteInteger";
2199
+ data: number;
2200
+ driverParam: number;
2201
+ notNull: true;
2202
+ hasDefault: false;
2203
+ isPrimaryKey: false;
2204
+ isAutoincrement: false;
2205
+ hasRuntimeDefault: false;
2206
+ enumValues: undefined;
2207
+ baseColumn: never;
2208
+ identity: undefined;
2209
+ generated: undefined;
2210
+ }, {}, {}>;
2211
+ state: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2212
+ name: "state";
2213
+ tableName: "entity_snapshots";
2214
+ dataType: "string";
2215
+ columnType: "SQLiteText";
2216
+ data: string;
2217
+ driverParam: string;
2218
+ notNull: true;
2219
+ hasDefault: false;
2220
+ isPrimaryKey: false;
2221
+ isAutoincrement: false;
2222
+ hasRuntimeDefault: false;
2223
+ enumValues: [string, ...string[]];
2224
+ baseColumn: never;
2225
+ identity: undefined;
2226
+ generated: undefined;
2227
+ }, {}, {
2228
+ length: number | undefined;
2229
+ }>;
2230
+ flowId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2231
+ name: "flow_id";
2232
+ tableName: "entity_snapshots";
2233
+ dataType: "string";
2234
+ columnType: "SQLiteText";
2235
+ data: string;
2236
+ driverParam: string;
2237
+ notNull: true;
2238
+ hasDefault: false;
2239
+ isPrimaryKey: false;
2240
+ isAutoincrement: false;
2241
+ hasRuntimeDefault: false;
2242
+ enumValues: [string, ...string[]];
2243
+ baseColumn: never;
2244
+ identity: undefined;
2245
+ generated: undefined;
2246
+ }, {}, {
2247
+ length: number | undefined;
2248
+ }>;
2249
+ refs: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2250
+ name: "refs";
2251
+ tableName: "entity_snapshots";
2252
+ dataType: "json";
2253
+ columnType: "SQLiteTextJson";
2254
+ data: unknown;
2255
+ driverParam: string;
2256
+ notNull: false;
2257
+ hasDefault: false;
2258
+ isPrimaryKey: false;
2259
+ isAutoincrement: false;
2260
+ hasRuntimeDefault: false;
2261
+ enumValues: undefined;
2262
+ baseColumn: never;
2263
+ identity: undefined;
2264
+ generated: undefined;
2265
+ }, {}, {}>;
2266
+ artifacts: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2267
+ name: "artifacts";
2268
+ tableName: "entity_snapshots";
2269
+ dataType: "json";
2270
+ columnType: "SQLiteTextJson";
2271
+ data: unknown;
2272
+ driverParam: string;
2273
+ notNull: false;
2274
+ hasDefault: false;
2275
+ isPrimaryKey: false;
2276
+ isAutoincrement: false;
2277
+ hasRuntimeDefault: false;
2278
+ enumValues: undefined;
2279
+ baseColumn: never;
2280
+ identity: undefined;
2281
+ generated: undefined;
2282
+ }, {}, {}>;
2283
+ claimedBy: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2284
+ name: "claimed_by";
2285
+ tableName: "entity_snapshots";
2286
+ dataType: "string";
2287
+ columnType: "SQLiteText";
2288
+ data: string;
2289
+ driverParam: string;
2290
+ notNull: false;
2291
+ hasDefault: false;
2292
+ isPrimaryKey: false;
2293
+ isAutoincrement: false;
2294
+ hasRuntimeDefault: false;
2295
+ enumValues: [string, ...string[]];
2296
+ baseColumn: never;
2297
+ identity: undefined;
2298
+ generated: undefined;
2299
+ }, {}, {
2300
+ length: number | undefined;
2301
+ }>;
2302
+ claimedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2303
+ name: "claimed_at";
2304
+ tableName: "entity_snapshots";
2305
+ dataType: "number";
2306
+ columnType: "SQLiteInteger";
2307
+ data: number;
2308
+ driverParam: number;
2309
+ notNull: false;
2310
+ hasDefault: false;
2311
+ isPrimaryKey: false;
2312
+ isAutoincrement: false;
2313
+ hasRuntimeDefault: false;
2314
+ enumValues: undefined;
2315
+ baseColumn: never;
2316
+ identity: undefined;
2317
+ generated: undefined;
2318
+ }, {}, {}>;
2319
+ flowVersion: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2320
+ name: "flow_version";
2321
+ tableName: "entity_snapshots";
2322
+ dataType: "number";
2323
+ columnType: "SQLiteInteger";
2324
+ data: number;
2325
+ driverParam: number;
2326
+ notNull: false;
2327
+ hasDefault: false;
2328
+ isPrimaryKey: false;
2329
+ isAutoincrement: false;
2330
+ hasRuntimeDefault: false;
2331
+ enumValues: undefined;
2332
+ baseColumn: never;
2333
+ identity: undefined;
2334
+ generated: undefined;
2335
+ }, {}, {}>;
2336
+ priority: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2337
+ name: "priority";
2338
+ tableName: "entity_snapshots";
2339
+ dataType: "number";
2340
+ columnType: "SQLiteInteger";
2341
+ data: number;
2342
+ driverParam: number;
2343
+ notNull: false;
2344
+ hasDefault: true;
2345
+ isPrimaryKey: false;
2346
+ isAutoincrement: false;
2347
+ hasRuntimeDefault: false;
2348
+ enumValues: undefined;
2349
+ baseColumn: never;
2350
+ identity: undefined;
2351
+ generated: undefined;
2352
+ }, {}, {}>;
2353
+ affinityWorkerId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2354
+ name: "affinity_worker_id";
2355
+ tableName: "entity_snapshots";
2356
+ dataType: "string";
2357
+ columnType: "SQLiteText";
2358
+ data: string;
2359
+ driverParam: string;
2360
+ notNull: false;
2361
+ hasDefault: false;
2362
+ isPrimaryKey: false;
2363
+ isAutoincrement: false;
2364
+ hasRuntimeDefault: false;
2365
+ enumValues: [string, ...string[]];
2366
+ baseColumn: never;
2367
+ identity: undefined;
2368
+ generated: undefined;
2369
+ }, {}, {
2370
+ length: number | undefined;
2371
+ }>;
2372
+ affinityRole: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2373
+ name: "affinity_role";
2374
+ tableName: "entity_snapshots";
2375
+ dataType: "string";
2376
+ columnType: "SQLiteText";
2377
+ data: string;
2378
+ driverParam: string;
2379
+ notNull: false;
2380
+ hasDefault: false;
2381
+ isPrimaryKey: false;
2382
+ isAutoincrement: false;
2383
+ hasRuntimeDefault: false;
2384
+ enumValues: [string, ...string[]];
2385
+ baseColumn: never;
2386
+ identity: undefined;
2387
+ generated: undefined;
2388
+ }, {}, {
2389
+ length: number | undefined;
2390
+ }>;
2391
+ affinityExpiresAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2392
+ name: "affinity_expires_at";
2393
+ tableName: "entity_snapshots";
2394
+ dataType: "number";
2395
+ columnType: "SQLiteInteger";
2396
+ data: number;
2397
+ driverParam: number;
2398
+ notNull: false;
2399
+ hasDefault: false;
2400
+ isPrimaryKey: false;
2401
+ isAutoincrement: false;
2402
+ hasRuntimeDefault: false;
2403
+ enumValues: undefined;
2404
+ baseColumn: never;
2405
+ identity: undefined;
2406
+ generated: undefined;
2407
+ }, {}, {}>;
2408
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2409
+ name: "created_at";
2410
+ tableName: "entity_snapshots";
2411
+ dataType: "number";
2412
+ columnType: "SQLiteInteger";
2413
+ data: number;
2414
+ driverParam: number;
2415
+ notNull: false;
2416
+ hasDefault: false;
2417
+ isPrimaryKey: false;
2418
+ isAutoincrement: false;
2419
+ hasRuntimeDefault: false;
2420
+ enumValues: undefined;
2421
+ baseColumn: never;
2422
+ identity: undefined;
2423
+ generated: undefined;
2424
+ }, {}, {}>;
2425
+ updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2426
+ name: "updated_at";
2427
+ tableName: "entity_snapshots";
2428
+ dataType: "number";
2429
+ columnType: "SQLiteInteger";
2430
+ data: number;
2431
+ driverParam: number;
2432
+ notNull: false;
2433
+ hasDefault: false;
2434
+ isPrimaryKey: false;
2435
+ isAutoincrement: false;
2436
+ hasRuntimeDefault: false;
2437
+ enumValues: undefined;
2438
+ baseColumn: never;
2439
+ identity: undefined;
2440
+ generated: undefined;
2441
+ }, {}, {}>;
2442
+ snapshotAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2443
+ name: "snapshot_at";
2444
+ tableName: "entity_snapshots";
2445
+ dataType: "number";
2446
+ columnType: "SQLiteInteger";
2447
+ data: number;
2448
+ driverParam: number;
2449
+ notNull: true;
2450
+ hasDefault: false;
2451
+ isPrimaryKey: false;
2452
+ isAutoincrement: false;
2453
+ hasRuntimeDefault: false;
2454
+ enumValues: undefined;
2455
+ baseColumn: never;
2456
+ identity: undefined;
2457
+ generated: undefined;
2458
+ }, {}, {}>;
2459
+ parentEntityId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2460
+ name: "parent_entity_id";
2461
+ tableName: "entity_snapshots";
2462
+ dataType: "string";
2463
+ columnType: "SQLiteText";
2464
+ data: string;
2465
+ driverParam: string;
2466
+ notNull: false;
2467
+ hasDefault: false;
2468
+ isPrimaryKey: false;
2469
+ isAutoincrement: false;
2470
+ hasRuntimeDefault: false;
2471
+ enumValues: [string, ...string[]];
2472
+ baseColumn: never;
2473
+ identity: undefined;
2474
+ generated: undefined;
2475
+ }, {}, {
2476
+ length: number | undefined;
2477
+ }>;
2478
+ };
2479
+ dialect: "sqlite";
2480
+ }>;
@@ -173,3 +173,26 @@ export const domainEvents = sqliteTable("domain_events", {
173
173
  entitySeqIdx: uniqueIndex("domain_events_entity_seq_idx").on(table.entityId, table.sequence),
174
174
  typeIdx: index("domain_events_type_idx").on(table.type, table.emittedAt),
175
175
  }));
176
+ export const entitySnapshots = sqliteTable("entity_snapshots", {
177
+ id: text("id").primaryKey(),
178
+ entityId: text("entity_id").notNull(),
179
+ sequence: integer("sequence").notNull(),
180
+ state: text("state").notNull(),
181
+ flowId: text("flow_id").notNull(),
182
+ refs: text("refs", { mode: "json" }),
183
+ artifacts: text("artifacts", { mode: "json" }),
184
+ claimedBy: text("claimed_by"),
185
+ claimedAt: integer("claimed_at"),
186
+ flowVersion: integer("flow_version"),
187
+ priority: integer("priority").default(0),
188
+ affinityWorkerId: text("affinity_worker_id"),
189
+ affinityRole: text("affinity_role"),
190
+ affinityExpiresAt: integer("affinity_expires_at"),
191
+ createdAt: integer("created_at"),
192
+ updatedAt: integer("updated_at"),
193
+ snapshotAt: integer("snapshot_at").notNull(),
194
+ parentEntityId: text("parent_entity_id"),
195
+ }, (table) => ({
196
+ entitySeqUnique: uniqueIndex("entity_snapshots_entity_seq_idx").on(table.entityId, table.sequence),
197
+ entityLatestIdx: index("entity_snapshots_entity_latest_idx").on(table.entityId, table.snapshotAt),
198
+ }));
@@ -0,0 +1,29 @@
1
+ import type { Artifacts, Entity, IDomainEventRepository, IEntityRepository, IEntitySnapshotRepository, Refs } from "../interfaces.js";
2
+ export declare class EventSourcedEntityRepository implements IEntityRepository {
3
+ private readonly mutable;
4
+ private readonly domainEvents;
5
+ private readonly snapshots;
6
+ private readonly snapshotInterval;
7
+ constructor(mutable: IEntityRepository, domainEvents: IDomainEventRepository, snapshots: IEntitySnapshotRepository, snapshotInterval?: number);
8
+ create(flowId: string, initialState: string, refs?: Refs, flowVersion?: number): Promise<Entity>;
9
+ get(id: string): Promise<Entity | null>;
10
+ findByFlowAndState(flowId: string, state: string, limit?: number): Promise<Entity[]>;
11
+ hasAnyInFlowAndState(flowId: string, stateNames: string[]): Promise<boolean>;
12
+ transition(id: string, toState: string, trigger: string, artifacts?: Partial<Artifacts>): Promise<Entity>;
13
+ updateArtifacts(id: string, artifacts: Partial<Artifacts>): Promise<void>;
14
+ claim(flowId: string, state: string, agentId: string): Promise<Entity | null>;
15
+ claimById(entityId: string, agentId: string): Promise<Entity | null>;
16
+ release(entityId: string, agentId: string): Promise<void>;
17
+ reapExpired(ttlMs: number): Promise<string[]>;
18
+ setAffinity(entityId: string, workerId: string, role: string, expiresAt: Date): Promise<void>;
19
+ clearExpiredAffinity(): Promise<string[]>;
20
+ appendSpawnedChild(parentId: string, entry: {
21
+ childId: string;
22
+ childFlow: string;
23
+ spawnedAt: string;
24
+ }): Promise<void>;
25
+ findByParentId(parentEntityId: string): Promise<Entity[]>;
26
+ cancelEntity(entityId: string): Promise<void>;
27
+ resetEntity(entityId: string, targetState: string): Promise<Entity>;
28
+ updateFlowVersion(entityId: string, version: number): Promise<void>;
29
+ }
@@ -0,0 +1,84 @@
1
+ import { replayEntity } from "./replay.js";
2
+ const DEFAULT_SNAPSHOT_INTERVAL = 10;
3
+ export class EventSourcedEntityRepository {
4
+ mutable;
5
+ domainEvents;
6
+ snapshots;
7
+ snapshotInterval;
8
+ constructor(mutable, domainEvents, snapshots, snapshotInterval) {
9
+ this.mutable = mutable;
10
+ this.domainEvents = domainEvents;
11
+ this.snapshots = snapshots;
12
+ this.snapshotInterval = snapshotInterval ?? DEFAULT_SNAPSHOT_INTERVAL;
13
+ }
14
+ async create(flowId, initialState, refs, flowVersion) {
15
+ return this.mutable.create(flowId, initialState, refs, flowVersion);
16
+ }
17
+ async get(id) {
18
+ const snapshot = await this.snapshots.loadLatest(id);
19
+ const afterSeq = snapshot?.sequence ?? 0;
20
+ const eventsAfterSnapshot = await this.domainEvents.list(id, { minSequence: afterSeq, limit: 10000 });
21
+ if (eventsAfterSnapshot.length === 0 && !snapshot) {
22
+ return this.mutable.get(id);
23
+ }
24
+ const entity = replayEntity(snapshot?.state ?? null, eventsAfterSnapshot, id);
25
+ if (!entity) {
26
+ return this.mutable.get(id);
27
+ }
28
+ if (eventsAfterSnapshot.length >= this.snapshotInterval) {
29
+ const lastEvent = eventsAfterSnapshot[eventsAfterSnapshot.length - 1];
30
+ try {
31
+ await this.snapshots.save(id, lastEvent.sequence, entity);
32
+ }
33
+ catch {
34
+ // snapshot write failure is non-fatal — continue with in-memory entity
35
+ }
36
+ }
37
+ return entity;
38
+ }
39
+ async findByFlowAndState(flowId, state, limit) {
40
+ return this.mutable.findByFlowAndState(flowId, state, limit);
41
+ }
42
+ async hasAnyInFlowAndState(flowId, stateNames) {
43
+ return this.mutable.hasAnyInFlowAndState(flowId, stateNames);
44
+ }
45
+ async transition(id, toState, trigger, artifacts) {
46
+ return this.mutable.transition(id, toState, trigger, artifacts);
47
+ }
48
+ async updateArtifacts(id, artifacts) {
49
+ return this.mutable.updateArtifacts(id, artifacts);
50
+ }
51
+ async claim(flowId, state, agentId) {
52
+ return this.mutable.claim(flowId, state, agentId);
53
+ }
54
+ async claimById(entityId, agentId) {
55
+ return this.mutable.claimById(entityId, agentId);
56
+ }
57
+ async release(entityId, agentId) {
58
+ return this.mutable.release(entityId, agentId);
59
+ }
60
+ async reapExpired(ttlMs) {
61
+ return this.mutable.reapExpired(ttlMs);
62
+ }
63
+ async setAffinity(entityId, workerId, role, expiresAt) {
64
+ return this.mutable.setAffinity(entityId, workerId, role, expiresAt);
65
+ }
66
+ async clearExpiredAffinity() {
67
+ return this.mutable.clearExpiredAffinity();
68
+ }
69
+ async appendSpawnedChild(parentId, entry) {
70
+ return this.mutable.appendSpawnedChild(parentId, entry);
71
+ }
72
+ async findByParentId(parentEntityId) {
73
+ return this.mutable.findByParentId(parentEntityId);
74
+ }
75
+ async cancelEntity(entityId) {
76
+ return this.mutable.cancelEntity(entityId);
77
+ }
78
+ async resetEntity(entityId, targetState) {
79
+ return this.mutable.resetEntity(entityId, targetState);
80
+ }
81
+ async updateFlowVersion(entityId, version) {
82
+ return this.mutable.updateFlowVersion(entityId, version);
83
+ }
84
+ }
@@ -0,0 +1,3 @@
1
+ export { EventSourcedEntityRepository } from "./entity.repo.js";
2
+ export { EventSourcedInvocationRepository } from "./invocation.repo.js";
3
+ export { replayEntity, replayInvocation } from "./replay.js";
@@ -0,0 +1,3 @@
1
+ export { EventSourcedEntityRepository } from "./entity.repo.js";
2
+ export { EventSourcedInvocationRepository } from "./invocation.repo.js";
3
+ export { replayEntity, replayInvocation } from "./replay.js";
@@ -0,0 +1,20 @@
1
+ import type { Artifacts, IDomainEventRepository, IInvocationRepository, Invocation, Mode } from "../interfaces.js";
2
+ export declare class EventSourcedInvocationRepository implements IInvocationRepository {
3
+ private readonly mutable;
4
+ private readonly domainEvents;
5
+ constructor(mutable: IInvocationRepository, domainEvents: IDomainEventRepository);
6
+ create(entityId: string, stage: string, prompt: string, mode: Mode, ttlMs: number | undefined, context: Record<string, unknown> | undefined, agentRole: string | null): Promise<Invocation>;
7
+ get(id: string): Promise<Invocation | null>;
8
+ claim(invocationId: string, agentId: string): Promise<Invocation | null>;
9
+ complete(id: string, signal: string, artifacts?: Artifacts): Promise<Invocation>;
10
+ fail(id: string, error: string): Promise<Invocation>;
11
+ releaseClaim(id: string): Promise<void>;
12
+ findByEntity(entityId: string): Promise<Invocation[]>;
13
+ findUnclaimedWithAffinity(flowId: string, role: string, workerId: string): Promise<Invocation[]>;
14
+ findUnclaimedByFlow(flowId: string): Promise<Invocation[]>;
15
+ findByFlow(flowId: string): Promise<Invocation[]>;
16
+ reapExpired(): Promise<Invocation[]>;
17
+ findUnclaimedActive(flowId?: string): Promise<Invocation[]>;
18
+ countActiveByFlow(flowId: string): Promise<number>;
19
+ countPendingByFlow(flowId: string): Promise<number>;
20
+ }