@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.
- package/dist/src/execution/cli.js +19 -2
- package/dist/src/repositories/drizzle/domain-event.repo.d.ts +1 -0
- package/dist/src/repositories/drizzle/domain-event.repo.js +4 -1
- package/dist/src/repositories/drizzle/entity-snapshot.repo.d.ts +14 -0
- package/dist/src/repositories/drizzle/entity-snapshot.repo.js +68 -0
- package/dist/src/repositories/drizzle/index.d.ts +1 -0
- package/dist/src/repositories/drizzle/index.js +1 -0
- package/dist/src/repositories/drizzle/schema.d.ts +329 -0
- package/dist/src/repositories/drizzle/schema.js +23 -0
- package/dist/src/repositories/event-sourced/entity.repo.d.ts +29 -0
- package/dist/src/repositories/event-sourced/entity.repo.js +84 -0
- package/dist/src/repositories/event-sourced/index.d.ts +3 -0
- package/dist/src/repositories/event-sourced/index.js +3 -0
- package/dist/src/repositories/event-sourced/invocation.repo.d.ts +20 -0
- package/dist/src/repositories/event-sourced/invocation.repo.js +61 -0
- package/dist/src/repositories/event-sourced/replay.d.ts +11 -0
- package/dist/src/repositories/event-sourced/replay.js +135 -0
- package/dist/src/repositories/interfaces.d.ts +11 -0
- package/drizzle/0015_clean_redwing.sql +11 -0
- package/drizzle/0016_hesitant_bill_hollister.sql +22 -0
- package/drizzle/0017_hesitant_bill_hollister.sql +22 -0
- package/drizzle/0018_volatile_reavers.sql +1 -0
- package/drizzle/meta/0016_snapshot.json +1316 -0
- package/drizzle/meta/0018_snapshot.json +1351 -0
- package/drizzle/meta/_journal.json +15 -1
- 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
|
|
177
|
+
const mutableEntityRepo = new DrizzleEntityRepository(db);
|
|
175
178
|
const flowRepo = new DrizzleFlowRepository(db);
|
|
176
|
-
const
|
|
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) => {
|
|
@@ -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,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
|
+
}
|