@fragno-dev/test 1.0.1 → 2.0.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/.turbo/turbo-build.log +31 -15
- package/CHANGELOG.md +54 -0
- package/dist/adapters.d.ts +21 -3
- package/dist/adapters.d.ts.map +1 -1
- package/dist/adapters.js +125 -31
- package/dist/adapters.js.map +1 -1
- package/dist/db-test.d.ts.map +1 -1
- package/dist/db-test.js +33 -2
- package/dist/db-test.js.map +1 -1
- package/dist/durable-hooks.d.ts +7 -0
- package/dist/durable-hooks.d.ts.map +1 -0
- package/dist/durable-hooks.js +12 -0
- package/dist/durable-hooks.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/model-checker-actors.d.ts +41 -0
- package/dist/model-checker-actors.d.ts.map +1 -0
- package/dist/model-checker-actors.js +406 -0
- package/dist/model-checker-actors.js.map +1 -0
- package/dist/model-checker-adapter.d.ts +32 -0
- package/dist/model-checker-adapter.d.ts.map +1 -0
- package/dist/model-checker-adapter.js +109 -0
- package/dist/model-checker-adapter.js.map +1 -0
- package/dist/model-checker.d.ts +128 -0
- package/dist/model-checker.d.ts.map +1 -0
- package/dist/model-checker.js +443 -0
- package/dist/model-checker.js.map +1 -0
- package/package.json +13 -12
- package/src/adapter-conformance.test.ts +322 -0
- package/src/adapters.ts +199 -36
- package/src/db-test.test.ts +2 -2
- package/src/db-test.ts +53 -3
- package/src/durable-hooks.ts +13 -0
- package/src/index.test.ts +84 -7
- package/src/index.ts +39 -4
- package/src/model-checker-actors.test.ts +78 -0
- package/src/model-checker-actors.ts +642 -0
- package/src/model-checker-adapter.ts +200 -0
- package/src/model-checker.test.ts +399 -0
- package/src/model-checker.ts +799 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import "./model-checker-adapter.js";
|
|
2
|
+
import { defaultStateHasher } from "./model-checker.js";
|
|
3
|
+
|
|
4
|
+
//#region src/model-checker-actors.ts
|
|
5
|
+
const createDeferred = () => {
|
|
6
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
7
|
+
return {
|
|
8
|
+
promise,
|
|
9
|
+
resolve
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
var PhaseScheduler = class {
|
|
13
|
+
#mode;
|
|
14
|
+
#plan;
|
|
15
|
+
#uowIds = /* @__PURE__ */ new WeakMap();
|
|
16
|
+
#uowPlans = [];
|
|
17
|
+
#pending = [];
|
|
18
|
+
#pendingSignal = createDeferred();
|
|
19
|
+
#inFlight = /* @__PURE__ */ new Map();
|
|
20
|
+
#actorsDone = false;
|
|
21
|
+
constructor(mode, plan) {
|
|
22
|
+
this.#mode = mode;
|
|
23
|
+
this.#plan = plan ?? null;
|
|
24
|
+
}
|
|
25
|
+
markActorsDone() {
|
|
26
|
+
this.#actorsDone = true;
|
|
27
|
+
this.#pendingSignal.resolve();
|
|
28
|
+
}
|
|
29
|
+
getPlan() {
|
|
30
|
+
return { phasesPerTransaction: this.#uowPlans.map((entry) => {
|
|
31
|
+
const phases = [];
|
|
32
|
+
if (entry.hasRetrieve) phases.push("retrieve");
|
|
33
|
+
if (entry.hasMutate) phases.push("mutate");
|
|
34
|
+
return phases;
|
|
35
|
+
}) };
|
|
36
|
+
}
|
|
37
|
+
async beforePhase(ctx, phase) {
|
|
38
|
+
const uow = ctx.uow;
|
|
39
|
+
const uowId = this.#getOrCreateUowId(uow);
|
|
40
|
+
if (phase === "mutate") {
|
|
41
|
+
const plan = this.#uowPlans[uowId];
|
|
42
|
+
if (plan) plan.hasMutate = true;
|
|
43
|
+
} else {
|
|
44
|
+
const plan = this.#uowPlans[uowId];
|
|
45
|
+
if (plan) plan.hasRetrieve = true;
|
|
46
|
+
}
|
|
47
|
+
if (this.#mode === "record") return;
|
|
48
|
+
this.#assertPlanned(uowId, phase);
|
|
49
|
+
const release = createDeferred();
|
|
50
|
+
const completion = createDeferred();
|
|
51
|
+
this.#inFlight.set(`${uowId}:${phase}`, completion);
|
|
52
|
+
const gate = {
|
|
53
|
+
uowId,
|
|
54
|
+
phase,
|
|
55
|
+
release: () => {
|
|
56
|
+
release.resolve();
|
|
57
|
+
},
|
|
58
|
+
completion: completion.promise
|
|
59
|
+
};
|
|
60
|
+
this.#pending.push(gate);
|
|
61
|
+
this.#pendingSignal.resolve();
|
|
62
|
+
this.#pendingSignal = createDeferred();
|
|
63
|
+
await release.promise;
|
|
64
|
+
}
|
|
65
|
+
async afterPhase(ctx, phase) {
|
|
66
|
+
const uow = ctx.uow;
|
|
67
|
+
const key = `${this.#getOrCreateUowId(uow)}:${phase}`;
|
|
68
|
+
const completion = this.#inFlight.get(key);
|
|
69
|
+
if (completion) {
|
|
70
|
+
completion.resolve();
|
|
71
|
+
this.#inFlight.delete(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async releaseStep(step, actorsPromise) {
|
|
75
|
+
for (;;) {
|
|
76
|
+
const matchIndex = this.#pending.findIndex((gate) => gate.uowId === step.txId && gate.phase === step.phase);
|
|
77
|
+
if (matchIndex !== -1) {
|
|
78
|
+
const [gate] = this.#pending.splice(matchIndex, 1);
|
|
79
|
+
gate.release();
|
|
80
|
+
await gate.completion;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (this.#pending.length > 0) {
|
|
84
|
+
const pendingSummary = this.#pending.map((gate) => `${gate.uowId}:${gate.phase}`).join(", ");
|
|
85
|
+
throw new Error(`Model checker schedule mismatch. Expected ${step.txId}:${step.phase}, pending [${pendingSummary}]`);
|
|
86
|
+
}
|
|
87
|
+
await Promise.race([this.#pendingSignal.promise, actorsPromise]);
|
|
88
|
+
if (this.#actorsDone) throw new Error(`Model checker schedule expects ${step.phase} for UOW ${step.txId}, but no gate appeared.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async releaseAny(pickIndex, actorsPromise) {
|
|
92
|
+
for (;;) {
|
|
93
|
+
if (this.#pending.length > 0) {
|
|
94
|
+
const index = Math.max(0, Math.min(pickIndex(this.#pending), this.#pending.length - 1));
|
|
95
|
+
const [gate] = this.#pending.splice(index, 1);
|
|
96
|
+
gate.release();
|
|
97
|
+
await gate.completion;
|
|
98
|
+
return {
|
|
99
|
+
txId: gate.uowId,
|
|
100
|
+
phase: gate.phase
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
await Promise.race([this.#pendingSignal.promise, actorsPromise]);
|
|
104
|
+
if (this.#actorsDone) return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
#getOrCreateUowId(uow) {
|
|
108
|
+
const existing = this.#uowIds.get(uow);
|
|
109
|
+
if (existing !== void 0) return existing;
|
|
110
|
+
const next = this.#uowPlans.length;
|
|
111
|
+
if (this.#plan && next >= this.#plan.phasesPerTransaction.length) throw new Error("Model checker saw more UOWs than planned.");
|
|
112
|
+
this.#uowIds.set(uow, next);
|
|
113
|
+
this.#uowPlans.push({
|
|
114
|
+
hasRetrieve: false,
|
|
115
|
+
hasMutate: false
|
|
116
|
+
});
|
|
117
|
+
return next;
|
|
118
|
+
}
|
|
119
|
+
#assertPlanned(uowId, phase) {
|
|
120
|
+
const plan = this.#plan;
|
|
121
|
+
if (!plan) return;
|
|
122
|
+
const phases = plan.phasesPerTransaction[uowId];
|
|
123
|
+
if (!phases || phases.length === 0) throw new Error(`Model checker saw unexpected UOW ${uowId}.`);
|
|
124
|
+
if (!phases.includes(phase)) throw new Error(`Model checker saw unexpected ${phase} phase for UOW ${uowId}.`);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const mulberry32 = (seed) => {
|
|
128
|
+
let t = seed >>> 0;
|
|
129
|
+
return () => {
|
|
130
|
+
t += 1831565813;
|
|
131
|
+
let r = Math.imul(t ^ t >>> 15, t | 1);
|
|
132
|
+
r ^= r + Math.imul(r ^ r >>> 7, r | 61);
|
|
133
|
+
return ((r ^ r >>> 14) >>> 0) / 4294967296;
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
const generateAllSchedules = (phasesPerTransaction) => {
|
|
137
|
+
const schedules = [];
|
|
138
|
+
const totalSteps = phasesPerTransaction.reduce((sum, phases) => sum + phases.length, 0);
|
|
139
|
+
const progress = phasesPerTransaction.map(() => 0);
|
|
140
|
+
const current = [];
|
|
141
|
+
const walk = () => {
|
|
142
|
+
if (current.length === totalSteps) {
|
|
143
|
+
schedules.push([...current]);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
for (let txId = 0; txId < phasesPerTransaction.length; txId += 1) {
|
|
147
|
+
if (progress[txId] >= phasesPerTransaction[txId].length) continue;
|
|
148
|
+
const phase = phasesPerTransaction[txId][progress[txId]];
|
|
149
|
+
if (!phase) continue;
|
|
150
|
+
current.push({
|
|
151
|
+
txId,
|
|
152
|
+
phase
|
|
153
|
+
});
|
|
154
|
+
progress[txId] += 1;
|
|
155
|
+
walk();
|
|
156
|
+
progress[txId] -= 1;
|
|
157
|
+
current.pop();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
walk();
|
|
161
|
+
return schedules;
|
|
162
|
+
};
|
|
163
|
+
const createHistoryTracker = (history) => {
|
|
164
|
+
if (!history) return null;
|
|
165
|
+
if (history.type === "unbounded") {
|
|
166
|
+
const set = /* @__PURE__ */ new Set();
|
|
167
|
+
return {
|
|
168
|
+
add: (key) => {
|
|
169
|
+
const has = set.has(key);
|
|
170
|
+
if (!has) set.add(key);
|
|
171
|
+
return !has;
|
|
172
|
+
},
|
|
173
|
+
size: () => set.size
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const maxEntries = Math.max(history.maxEntries, 1);
|
|
177
|
+
const map = /* @__PURE__ */ new Map();
|
|
178
|
+
return {
|
|
179
|
+
add: (key) => {
|
|
180
|
+
const existed = map.delete(key);
|
|
181
|
+
map.set(key, true);
|
|
182
|
+
if (map.size > maxEntries) {
|
|
183
|
+
const first = map.keys().next().value;
|
|
184
|
+
if (first) map.delete(first);
|
|
185
|
+
}
|
|
186
|
+
return !existed;
|
|
187
|
+
},
|
|
188
|
+
size: () => map.size
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
const serializeSchedulePrefix = (schedule) => schedule.map((step) => `${step.txId}:${step.phase}`).join("|");
|
|
192
|
+
const buildPathKey = (stateHash, prefix) => `${stateHash}::${serializeSchedulePrefix(prefix)}`;
|
|
193
|
+
const defaultHistoryConfig = {
|
|
194
|
+
type: "lru",
|
|
195
|
+
maxEntries: 5e3
|
|
196
|
+
};
|
|
197
|
+
const planTransactions = (phasesPerTransaction) => {
|
|
198
|
+
if (phasesPerTransaction.length === 0) throw new Error("Model checker requires at least one transaction.");
|
|
199
|
+
return { phasesPerTransaction };
|
|
200
|
+
};
|
|
201
|
+
const recordPlan = async (config) => {
|
|
202
|
+
const { adapter, ctx, cleanup } = await config.createContext();
|
|
203
|
+
try {
|
|
204
|
+
if (config.setup) await config.setup(ctx);
|
|
205
|
+
const scheduler = new PhaseScheduler("record");
|
|
206
|
+
adapter.setScheduler(scheduler);
|
|
207
|
+
const actors = config.buildActors(ctx);
|
|
208
|
+
await Promise.all(actors.map((actor) => actor.run(ctx)));
|
|
209
|
+
return planTransactions(scheduler.getPlan().phasesPerTransaction);
|
|
210
|
+
} finally {
|
|
211
|
+
adapter.setScheduler(null);
|
|
212
|
+
if (cleanup) await cleanup();
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const executeSchedule = async (schedule, config, plan, history, stateHasher) => {
|
|
216
|
+
const { adapter, ctx, queryEngine, cleanup } = await config.createContext();
|
|
217
|
+
try {
|
|
218
|
+
if (config.setup) await config.setup(ctx);
|
|
219
|
+
const scheduler = new PhaseScheduler("schedule", plan);
|
|
220
|
+
adapter.setScheduler(scheduler);
|
|
221
|
+
const actors = config.buildActors(ctx);
|
|
222
|
+
const actorsPromise = Promise.all(actors.map((actor) => actor.run(ctx))).then(() => {
|
|
223
|
+
scheduler.markActorsDone();
|
|
224
|
+
});
|
|
225
|
+
let lastStateHash = null;
|
|
226
|
+
let newPathsAdded = false;
|
|
227
|
+
for (let stepIndex = 0; stepIndex < schedule.length; stepIndex += 1) {
|
|
228
|
+
const step = schedule[stepIndex];
|
|
229
|
+
if (!step) throw new Error("Schedule step is missing.");
|
|
230
|
+
await scheduler.releaseStep(step, actorsPromise);
|
|
231
|
+
if (config.invariants) for (const invariant of config.invariants) await invariant({
|
|
232
|
+
schema: config.schema,
|
|
233
|
+
queryEngine,
|
|
234
|
+
context: ctx,
|
|
235
|
+
schedule,
|
|
236
|
+
stepIndex
|
|
237
|
+
});
|
|
238
|
+
if (history) {
|
|
239
|
+
lastStateHash = await stateHasher({
|
|
240
|
+
schema: config.schema,
|
|
241
|
+
queryEngine
|
|
242
|
+
});
|
|
243
|
+
const prefix = schedule.slice(0, stepIndex + 1);
|
|
244
|
+
if (history.add(buildPathKey(lastStateHash, prefix))) newPathsAdded = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
await actorsPromise;
|
|
248
|
+
if (!lastStateHash) lastStateHash = await stateHasher({
|
|
249
|
+
schema: config.schema,
|
|
250
|
+
queryEngine
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
stateHash: lastStateHash,
|
|
254
|
+
newPathsAdded
|
|
255
|
+
};
|
|
256
|
+
} finally {
|
|
257
|
+
adapter.setScheduler(null);
|
|
258
|
+
if (cleanup) await cleanup();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
const executeScheduleDynamic = async (config, history, stateHasher, pickIndex) => {
|
|
262
|
+
const { adapter, ctx, queryEngine, cleanup } = await config.createContext();
|
|
263
|
+
try {
|
|
264
|
+
if (config.setup) await config.setup(ctx);
|
|
265
|
+
const scheduler = new PhaseScheduler("schedule");
|
|
266
|
+
adapter.setScheduler(scheduler);
|
|
267
|
+
const actors = config.buildActors(ctx);
|
|
268
|
+
const actorsPromise = Promise.all(actors.map((actor) => actor.run(ctx))).then(() => {
|
|
269
|
+
scheduler.markActorsDone();
|
|
270
|
+
});
|
|
271
|
+
const schedule = [];
|
|
272
|
+
let lastStateHash = null;
|
|
273
|
+
let newPathsAdded = false;
|
|
274
|
+
for (;;) {
|
|
275
|
+
const step = await scheduler.releaseAny(pickIndex, actorsPromise);
|
|
276
|
+
if (!step) break;
|
|
277
|
+
schedule.push(step);
|
|
278
|
+
const stepIndex = schedule.length - 1;
|
|
279
|
+
if (config.invariants) for (const invariant of config.invariants) await invariant({
|
|
280
|
+
schema: config.schema,
|
|
281
|
+
queryEngine,
|
|
282
|
+
context: ctx,
|
|
283
|
+
schedule,
|
|
284
|
+
stepIndex
|
|
285
|
+
});
|
|
286
|
+
if (history) {
|
|
287
|
+
lastStateHash = await stateHasher({
|
|
288
|
+
schema: config.schema,
|
|
289
|
+
queryEngine
|
|
290
|
+
});
|
|
291
|
+
const prefix = schedule.slice(0, stepIndex + 1);
|
|
292
|
+
if (history.add(buildPathKey(lastStateHash, prefix))) newPathsAdded = true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
await actorsPromise;
|
|
296
|
+
if (!lastStateHash) lastStateHash = await stateHasher({
|
|
297
|
+
schema: config.schema,
|
|
298
|
+
queryEngine
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
schedule,
|
|
302
|
+
stateHash: lastStateHash,
|
|
303
|
+
newPathsAdded
|
|
304
|
+
};
|
|
305
|
+
} finally {
|
|
306
|
+
adapter.setScheduler(null);
|
|
307
|
+
if (cleanup) await cleanup();
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
const executeScheduleDynamicWithChoices = async (config, history, stateHasher, choices) => {
|
|
311
|
+
const choicePoints = [];
|
|
312
|
+
return {
|
|
313
|
+
...await executeScheduleDynamic(config, history, stateHasher, (pending) => {
|
|
314
|
+
const stepIndex = choicePoints.length;
|
|
315
|
+
choicePoints.push(pending.length);
|
|
316
|
+
const choice = choices[stepIndex];
|
|
317
|
+
if (choice === void 0 || choice < 0 || choice >= pending.length) return 0;
|
|
318
|
+
return choice;
|
|
319
|
+
}),
|
|
320
|
+
choicePoints
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
const serializeChoices = (choices) => choices.join(",");
|
|
324
|
+
const runModelCheckerWithActors = async (config) => {
|
|
325
|
+
const mode = config.mode ?? "exhaustive";
|
|
326
|
+
const history = createHistoryTracker(config.history ?? defaultHistoryConfig);
|
|
327
|
+
const stateHasher = config.stateHasher ?? defaultStateHasher;
|
|
328
|
+
const schedules = [];
|
|
329
|
+
const maxSchedules = config.maxSchedules;
|
|
330
|
+
const seed = config.seed ?? 1;
|
|
331
|
+
if (mode === "exhaustive") {
|
|
332
|
+
const plan = await recordPlan(config);
|
|
333
|
+
const allSchedules = generateAllSchedules(plan.phasesPerTransaction);
|
|
334
|
+
for (const schedule of allSchedules) {
|
|
335
|
+
if (maxSchedules !== void 0 && schedules.length >= maxSchedules) break;
|
|
336
|
+
const result = await executeSchedule(schedule, config, plan, history, stateHasher);
|
|
337
|
+
schedules.push({
|
|
338
|
+
schedule,
|
|
339
|
+
stateHash: result.stateHash
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
} else if (mode === "bounded-exhaustive") {
|
|
343
|
+
const maxSteps = config.bounds?.maxSteps;
|
|
344
|
+
if (!maxSteps || maxSteps < 1) throw new Error("bounded-exhaustive mode requires bounds.maxSteps >= 1.");
|
|
345
|
+
const stack = [[]];
|
|
346
|
+
const seen = /* @__PURE__ */ new Set();
|
|
347
|
+
seen.add("");
|
|
348
|
+
while (stack.length > 0) {
|
|
349
|
+
if (maxSchedules !== void 0 && schedules.length >= maxSchedules) break;
|
|
350
|
+
const choices = stack.pop();
|
|
351
|
+
if (!choices) break;
|
|
352
|
+
const result = await executeScheduleDynamicWithChoices(config, history, stateHasher, choices);
|
|
353
|
+
schedules.push({
|
|
354
|
+
schedule: result.schedule,
|
|
355
|
+
stateHash: result.stateHash
|
|
356
|
+
});
|
|
357
|
+
const limit = Math.min(result.choicePoints.length, maxSteps);
|
|
358
|
+
for (let stepIndex = 0; stepIndex < limit; stepIndex += 1) {
|
|
359
|
+
const count = result.choicePoints[stepIndex];
|
|
360
|
+
const current = choices[stepIndex] ?? 0;
|
|
361
|
+
for (let alt = current + 1; alt < count; alt += 1) {
|
|
362
|
+
const next = choices.slice(0, stepIndex);
|
|
363
|
+
next[stepIndex] = alt;
|
|
364
|
+
const key = serializeChoices(next);
|
|
365
|
+
if (!seen.has(key)) {
|
|
366
|
+
seen.add(key);
|
|
367
|
+
stack.push(next);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} else if (mode === "random") {
|
|
373
|
+
const rng = mulberry32(seed);
|
|
374
|
+
const total = maxSchedules ?? 1;
|
|
375
|
+
for (let i = 0; i < total; i += 1) {
|
|
376
|
+
const result = await executeScheduleDynamic(config, history, stateHasher, (pending) => Math.floor(rng() * pending.length));
|
|
377
|
+
schedules.push({
|
|
378
|
+
schedule: result.schedule,
|
|
379
|
+
stateHash: result.stateHash
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
const rng = mulberry32(seed);
|
|
384
|
+
const stopWhenFrontierExhausted = config.stopWhenFrontierExhausted ?? false;
|
|
385
|
+
let iterations = 0;
|
|
386
|
+
let frontierExhausted = false;
|
|
387
|
+
while (!frontierExhausted) {
|
|
388
|
+
if (maxSchedules !== void 0 && iterations >= maxSchedules) break;
|
|
389
|
+
const result = await executeScheduleDynamic(config, history, stateHasher, (pending) => Math.floor(rng() * pending.length));
|
|
390
|
+
schedules.push({
|
|
391
|
+
schedule: result.schedule,
|
|
392
|
+
stateHash: result.stateHash
|
|
393
|
+
});
|
|
394
|
+
iterations += 1;
|
|
395
|
+
if (stopWhenFrontierExhausted && history) frontierExhausted = !result.newPathsAdded;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
schedules,
|
|
400
|
+
visitedPaths: history?.size() ?? 0
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
//#endregion
|
|
405
|
+
export { runModelCheckerWithActors };
|
|
406
|
+
//# sourceMappingURL=model-checker-actors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-checker-actors.js","names":["#mode","#plan","#actorsDone","#pendingSignal","#uowPlans","phases: ModelCheckerPhase[]","#getOrCreateUowId","#assertPlanned","#inFlight","gate: PendingGate","#pending","#uowIds","schedules: ModelCheckerStep[][]","current: ModelCheckerStep[]","defaultHistoryConfig: ModelCheckerHistory","lastStateHash: string | null","schedule: ModelCheckerStep[]","choicePoints: number[]","schedules: ModelCheckerScheduleResult[]","stack: number[][]"],"sources":["../src/model-checker-actors.ts"],"sourcesContent":["import type { AnySchema } from \"@fragno-dev/db/schema\";\nimport type { SimpleQueryInterface } from \"@fragno-dev/db/query\";\nimport type { UOWInstrumentationContext } from \"@fragno-dev/db/unit-of-work\";\nimport {\n defaultStateHasher,\n type ModelCheckerBounds,\n type ModelCheckerHistory,\n type ModelCheckerMode,\n type ModelCheckerPhase,\n type ModelCheckerRunResult,\n type ModelCheckerScheduleResult,\n type ModelCheckerStateHasherContext,\n type ModelCheckerStep,\n} from \"./model-checker\";\nimport { ModelCheckerAdapter, type ModelCheckerScheduler } from \"./model-checker-adapter\";\n\ntype DeferredPromise<T> = {\n promise: Promise<T>;\n resolve: (value: T) => void;\n};\n\nconst createDeferred = <T>(): DeferredPromise<T> => {\n const { promise, resolve } = Promise.withResolvers<T>();\n return { promise, resolve };\n};\n\ntype PendingGate = {\n uowId: number;\n phase: ModelCheckerPhase;\n release: () => void;\n completion: Promise<void>;\n};\n\ntype UowPlan = {\n hasRetrieve: boolean;\n hasMutate: boolean;\n};\n\ntype SchedulePlan = {\n phasesPerTransaction: ModelCheckerPhase[][];\n};\n\ntype SchedulerMode = \"record\" | \"schedule\";\n\nclass PhaseScheduler implements ModelCheckerScheduler {\n #mode: SchedulerMode;\n #plan: SchedulePlan | null;\n #uowIds = new WeakMap<object, number>();\n #uowPlans: UowPlan[] = [];\n #pending: PendingGate[] = [];\n #pendingSignal = createDeferred<void>();\n #inFlight = new Map<string, DeferredPromise<void>>();\n #actorsDone = false;\n\n constructor(mode: SchedulerMode, plan?: SchedulePlan) {\n this.#mode = mode;\n this.#plan = plan ?? null;\n }\n\n markActorsDone(): void {\n this.#actorsDone = true;\n this.#pendingSignal.resolve();\n }\n\n getPlan(): SchedulePlan {\n return {\n phasesPerTransaction: this.#uowPlans.map((entry) => {\n const phases: ModelCheckerPhase[] = [];\n if (entry.hasRetrieve) {\n phases.push(\"retrieve\");\n }\n if (entry.hasMutate) {\n phases.push(\"mutate\");\n }\n return phases;\n }),\n };\n }\n\n async beforePhase(ctx: UOWInstrumentationContext, phase: ModelCheckerPhase): Promise<void> {\n const uow = ctx.uow as object;\n const uowId = this.#getOrCreateUowId(uow);\n\n if (phase === \"mutate\") {\n const plan = this.#uowPlans[uowId];\n if (plan) {\n plan.hasMutate = true;\n }\n } else {\n const plan = this.#uowPlans[uowId];\n if (plan) {\n plan.hasRetrieve = true;\n }\n }\n\n if (this.#mode === \"record\") {\n return;\n }\n\n this.#assertPlanned(uowId, phase);\n const release = createDeferred<void>();\n const completion = createDeferred<void>();\n this.#inFlight.set(`${uowId}:${phase}`, completion);\n const gate: PendingGate = {\n uowId,\n phase,\n release: () => {\n release.resolve();\n },\n completion: completion.promise,\n };\n this.#pending.push(gate);\n this.#pendingSignal.resolve();\n this.#pendingSignal = createDeferred<void>();\n await release.promise;\n }\n\n async afterPhase(ctx: UOWInstrumentationContext, phase: ModelCheckerPhase): Promise<void> {\n const uow = ctx.uow as object;\n const uowId = this.#getOrCreateUowId(uow);\n const key = `${uowId}:${phase}`;\n const completion = this.#inFlight.get(key);\n if (completion) {\n completion.resolve();\n this.#inFlight.delete(key);\n }\n }\n\n async releaseStep(step: ModelCheckerStep, actorsPromise: Promise<void>): Promise<void> {\n for (;;) {\n const matchIndex = this.#pending.findIndex(\n (gate) => gate.uowId === step.txId && gate.phase === step.phase,\n );\n if (matchIndex !== -1) {\n const [gate] = this.#pending.splice(matchIndex, 1);\n gate.release();\n await gate.completion;\n return;\n }\n\n if (this.#pending.length > 0) {\n const pendingSummary = this.#pending\n .map((gate) => `${gate.uowId}:${gate.phase}`)\n .join(\", \");\n throw new Error(\n `Model checker schedule mismatch. Expected ${step.txId}:${step.phase}, pending [${pendingSummary}]`,\n );\n }\n\n await Promise.race([this.#pendingSignal.promise, actorsPromise]);\n if (this.#actorsDone) {\n throw new Error(\n `Model checker schedule expects ${step.phase} for UOW ${step.txId}, but no gate appeared.`,\n );\n }\n }\n }\n\n async releaseAny(\n pickIndex: (pending: PendingGate[]) => number,\n actorsPromise: Promise<void>,\n ): Promise<ModelCheckerStep | null> {\n for (;;) {\n if (this.#pending.length > 0) {\n const index = Math.max(0, Math.min(pickIndex(this.#pending), this.#pending.length - 1));\n const [gate] = this.#pending.splice(index, 1);\n gate.release();\n await gate.completion;\n return { txId: gate.uowId, phase: gate.phase };\n }\n\n await Promise.race([this.#pendingSignal.promise, actorsPromise]);\n if (this.#actorsDone) {\n return null;\n }\n }\n }\n\n #getOrCreateUowId(uow: object): number {\n const existing = this.#uowIds.get(uow);\n if (existing !== undefined) {\n return existing;\n }\n\n const next = this.#uowPlans.length;\n if (this.#plan && next >= this.#plan.phasesPerTransaction.length) {\n throw new Error(\"Model checker saw more UOWs than planned.\");\n }\n\n this.#uowIds.set(uow, next);\n this.#uowPlans.push({ hasRetrieve: false, hasMutate: false });\n return next;\n }\n\n #assertPlanned(uowId: number, phase: ModelCheckerPhase): void {\n const plan = this.#plan;\n if (!plan) {\n return;\n }\n\n const phases = plan.phasesPerTransaction[uowId];\n if (!phases || phases.length === 0) {\n throw new Error(`Model checker saw unexpected UOW ${uowId}.`);\n }\n\n if (!phases.includes(phase)) {\n throw new Error(`Model checker saw unexpected ${phase} phase for UOW ${uowId}.`);\n }\n }\n}\n\nconst mulberry32 = (seed: number) => {\n let t = seed >>> 0;\n return () => {\n t += 1831565813;\n let r = Math.imul(t ^ (t >>> 15), t | 1);\n r ^= r + Math.imul(r ^ (r >>> 7), r | 61);\n return ((r ^ (r >>> 14)) >>> 0) / 4294967296;\n };\n};\n\nconst generateAllSchedules = (\n phasesPerTransaction: ModelCheckerPhase[][],\n): ModelCheckerStep[][] => {\n const schedules: ModelCheckerStep[][] = [];\n const totalSteps = phasesPerTransaction.reduce((sum, phases) => sum + phases.length, 0);\n const progress = phasesPerTransaction.map(() => 0);\n const current: ModelCheckerStep[] = [];\n\n const walk = () => {\n if (current.length === totalSteps) {\n schedules.push([...current]);\n return;\n }\n\n for (let txId = 0; txId < phasesPerTransaction.length; txId += 1) {\n if (progress[txId] >= phasesPerTransaction[txId].length) {\n continue;\n }\n const phase = phasesPerTransaction[txId][progress[txId]];\n if (!phase) {\n continue;\n }\n current.push({ txId, phase });\n progress[txId] += 1;\n walk();\n progress[txId] -= 1;\n current.pop();\n }\n };\n\n walk();\n return schedules;\n};\n\ntype HistoryTracker = {\n add: (key: string) => boolean;\n size: () => number;\n};\n\nconst createHistoryTracker = (history: ModelCheckerHistory): HistoryTracker | null => {\n if (!history) {\n return null;\n }\n\n if (history.type === \"unbounded\") {\n const set = new Set<string>();\n return {\n add: (key: string) => {\n const has = set.has(key);\n if (!has) {\n set.add(key);\n }\n return !has;\n },\n size: () => set.size,\n };\n }\n\n const maxEntries = Math.max(history.maxEntries, 1);\n const map = new Map<string, true>();\n return {\n add: (key: string) => {\n const existed = map.delete(key);\n map.set(key, true);\n if (map.size > maxEntries) {\n const first = map.keys().next().value;\n if (first) {\n map.delete(first);\n }\n }\n return !existed;\n },\n size: () => map.size,\n };\n};\n\nconst serializeSchedulePrefix = (schedule: ModelCheckerStep[]): string =>\n schedule.map((step) => `${step.txId}:${step.phase}`).join(\"|\");\n\nconst buildPathKey = (stateHash: string, prefix: ModelCheckerStep[]): string =>\n `${stateHash}::${serializeSchedulePrefix(prefix)}`;\n\nexport type ModelCheckerActor<TContext> = {\n name?: string;\n run: (ctx: TContext) => Promise<void> | void;\n};\n\nexport type ModelCheckerInvariantContext<TSchema extends AnySchema, TUowConfig, TContext> = {\n schema: TSchema;\n queryEngine: SimpleQueryInterface<TSchema, TUowConfig>;\n context: TContext;\n schedule: ModelCheckerStep[];\n stepIndex: number;\n};\n\nexport type ModelCheckerInvariant<TSchema extends AnySchema, TUowConfig, TContext> = (\n ctx: ModelCheckerInvariantContext<TSchema, TUowConfig, TContext>,\n) => Promise<void> | void;\n\nexport type ModelCheckerActorsConfig<TSchema extends AnySchema, TUowConfig, TContext> = {\n schema: TSchema;\n mode?: ModelCheckerMode;\n seed?: number;\n maxSchedules?: number;\n bounds?: ModelCheckerBounds;\n history?: ModelCheckerHistory;\n stopWhenFrontierExhausted?: boolean;\n stateHasher?: (ctx: ModelCheckerStateHasherContext<TSchema, TUowConfig>) => Promise<string>;\n createContext: () => Promise<{\n adapter: ModelCheckerAdapter<TUowConfig>;\n ctx: TContext;\n queryEngine: SimpleQueryInterface<TSchema, TUowConfig>;\n cleanup?: () => Promise<void>;\n }>;\n setup?: (ctx: TContext) => Promise<void>;\n buildActors: (ctx: TContext) => ModelCheckerActor<TContext>[];\n invariants?: ModelCheckerInvariant<TSchema, TUowConfig, TContext>[];\n};\n\nconst defaultHistoryConfig: ModelCheckerHistory = { type: \"lru\", maxEntries: 5000 };\n\nconst planTransactions = (phasesPerTransaction: ModelCheckerPhase[][]): SchedulePlan => {\n if (phasesPerTransaction.length === 0) {\n throw new Error(\"Model checker requires at least one transaction.\");\n }\n return { phasesPerTransaction };\n};\n\nconst recordPlan = async <TSchema extends AnySchema, TUowConfig, TContext>(\n config: ModelCheckerActorsConfig<TSchema, TUowConfig, TContext>,\n): Promise<SchedulePlan> => {\n const { adapter, ctx, cleanup } = await config.createContext();\n\n try {\n if (config.setup) {\n await config.setup(ctx);\n }\n const scheduler = new PhaseScheduler(\"record\");\n adapter.setScheduler(scheduler);\n const actors = config.buildActors(ctx);\n await Promise.all(actors.map((actor) => actor.run(ctx)));\n return planTransactions(scheduler.getPlan().phasesPerTransaction);\n } finally {\n adapter.setScheduler(null);\n if (cleanup) {\n await cleanup();\n }\n }\n};\n\nconst executeSchedule = async <TSchema extends AnySchema, TUowConfig, TContext>(\n schedule: ModelCheckerStep[],\n config: ModelCheckerActorsConfig<TSchema, TUowConfig, TContext>,\n plan: SchedulePlan,\n history: HistoryTracker | null,\n stateHasher: (ctx: ModelCheckerStateHasherContext<TSchema, TUowConfig>) => Promise<string>,\n): Promise<{ stateHash: string; newPathsAdded: boolean }> => {\n const { adapter, ctx, queryEngine, cleanup } = await config.createContext();\n\n try {\n if (config.setup) {\n await config.setup(ctx);\n }\n const scheduler = new PhaseScheduler(\"schedule\", plan);\n adapter.setScheduler(scheduler);\n\n const actors = config.buildActors(ctx);\n const actorsPromise = Promise.all(actors.map((actor) => actor.run(ctx))).then(() => {\n scheduler.markActorsDone();\n });\n\n let lastStateHash: string | null = null;\n let newPathsAdded = false;\n\n for (let stepIndex = 0; stepIndex < schedule.length; stepIndex += 1) {\n const step = schedule[stepIndex];\n if (!step) {\n throw new Error(\"Schedule step is missing.\");\n }\n\n await scheduler.releaseStep(step, actorsPromise);\n\n if (config.invariants) {\n for (const invariant of config.invariants) {\n await invariant({\n schema: config.schema,\n queryEngine,\n context: ctx,\n schedule,\n stepIndex,\n });\n }\n }\n\n if (history) {\n lastStateHash = await stateHasher({\n schema: config.schema,\n queryEngine,\n });\n const prefix = schedule.slice(0, stepIndex + 1);\n const added = history.add(buildPathKey(lastStateHash, prefix));\n if (added) {\n newPathsAdded = true;\n }\n }\n }\n\n await actorsPromise;\n\n if (!lastStateHash) {\n lastStateHash = await stateHasher({ schema: config.schema, queryEngine });\n }\n\n return { stateHash: lastStateHash, newPathsAdded };\n } finally {\n adapter.setScheduler(null);\n if (cleanup) {\n await cleanup();\n }\n }\n};\n\nconst executeScheduleDynamic = async <TSchema extends AnySchema, TUowConfig, TContext>(\n config: ModelCheckerActorsConfig<TSchema, TUowConfig, TContext>,\n history: HistoryTracker | null,\n stateHasher: (ctx: ModelCheckerStateHasherContext<TSchema, TUowConfig>) => Promise<string>,\n pickIndex: (pending: PendingGate[]) => number,\n): Promise<{ schedule: ModelCheckerStep[]; stateHash: string; newPathsAdded: boolean }> => {\n const { adapter, ctx, queryEngine, cleanup } = await config.createContext();\n\n try {\n if (config.setup) {\n await config.setup(ctx);\n }\n\n const scheduler = new PhaseScheduler(\"schedule\");\n adapter.setScheduler(scheduler);\n\n const actors = config.buildActors(ctx);\n const actorsPromise = Promise.all(actors.map((actor) => actor.run(ctx))).then(() => {\n scheduler.markActorsDone();\n });\n\n const schedule: ModelCheckerStep[] = [];\n let lastStateHash: string | null = null;\n let newPathsAdded = false;\n\n for (;;) {\n const step = await scheduler.releaseAny(pickIndex, actorsPromise);\n if (!step) {\n break;\n }\n\n schedule.push(step);\n const stepIndex = schedule.length - 1;\n\n if (config.invariants) {\n for (const invariant of config.invariants) {\n await invariant({\n schema: config.schema,\n queryEngine,\n context: ctx,\n schedule,\n stepIndex,\n });\n }\n }\n\n if (history) {\n lastStateHash = await stateHasher({\n schema: config.schema,\n queryEngine,\n });\n const prefix = schedule.slice(0, stepIndex + 1);\n const added = history.add(buildPathKey(lastStateHash, prefix));\n if (added) {\n newPathsAdded = true;\n }\n }\n }\n\n await actorsPromise;\n\n if (!lastStateHash) {\n lastStateHash = await stateHasher({ schema: config.schema, queryEngine });\n }\n\n return { schedule, stateHash: lastStateHash, newPathsAdded };\n } finally {\n adapter.setScheduler(null);\n if (cleanup) {\n await cleanup();\n }\n }\n};\n\nconst executeScheduleDynamicWithChoices = async <TSchema extends AnySchema, TUowConfig, TContext>(\n config: ModelCheckerActorsConfig<TSchema, TUowConfig, TContext>,\n history: HistoryTracker | null,\n stateHasher: (ctx: ModelCheckerStateHasherContext<TSchema, TUowConfig>) => Promise<string>,\n choices: number[],\n): Promise<{\n schedule: ModelCheckerStep[];\n stateHash: string;\n newPathsAdded: boolean;\n choicePoints: number[];\n}> => {\n const choicePoints: number[] = [];\n const result = await executeScheduleDynamic(config, history, stateHasher, (pending) => {\n const stepIndex = choicePoints.length;\n choicePoints.push(pending.length);\n const choice = choices[stepIndex];\n if (choice === undefined || choice < 0 || choice >= pending.length) {\n return 0;\n }\n return choice;\n });\n\n return {\n ...result,\n choicePoints,\n };\n};\n\nconst serializeChoices = (choices: number[]): string => choices.join(\",\");\n\nexport const runModelCheckerWithActors = async <TSchema extends AnySchema, TUowConfig, TContext>(\n config: ModelCheckerActorsConfig<TSchema, TUowConfig, TContext>,\n): Promise<ModelCheckerRunResult> => {\n const mode = config.mode ?? \"exhaustive\";\n const historyConfig = config.history ?? defaultHistoryConfig;\n const history = createHistoryTracker(historyConfig);\n const stateHasher = config.stateHasher ?? defaultStateHasher;\n const schedules: ModelCheckerScheduleResult[] = [];\n const maxSchedules = config.maxSchedules;\n const seed = config.seed ?? 1;\n\n if (mode === \"exhaustive\") {\n const plan = await recordPlan(config);\n const allSchedules = generateAllSchedules(plan.phasesPerTransaction);\n for (const schedule of allSchedules) {\n if (maxSchedules !== undefined && schedules.length >= maxSchedules) {\n break;\n }\n const result = await executeSchedule(schedule, config, plan, history, stateHasher);\n schedules.push({ schedule, stateHash: result.stateHash });\n }\n } else if (mode === \"bounded-exhaustive\") {\n const maxSteps = config.bounds?.maxSteps;\n if (!maxSteps || maxSteps < 1) {\n throw new Error(\"bounded-exhaustive mode requires bounds.maxSteps >= 1.\");\n }\n\n const stack: number[][] = [[]];\n const seen = new Set<string>();\n seen.add(\"\");\n\n while (stack.length > 0) {\n if (maxSchedules !== undefined && schedules.length >= maxSchedules) {\n break;\n }\n\n const choices = stack.pop();\n if (!choices) {\n break;\n }\n\n const result = await executeScheduleDynamicWithChoices(config, history, stateHasher, choices);\n schedules.push({ schedule: result.schedule, stateHash: result.stateHash });\n\n const limit = Math.min(result.choicePoints.length, maxSteps);\n for (let stepIndex = 0; stepIndex < limit; stepIndex += 1) {\n const count = result.choicePoints[stepIndex];\n const current = choices[stepIndex] ?? 0;\n for (let alt = current + 1; alt < count; alt += 1) {\n const next = choices.slice(0, stepIndex);\n next[stepIndex] = alt;\n const key = serializeChoices(next);\n if (!seen.has(key)) {\n seen.add(key);\n stack.push(next);\n }\n }\n }\n }\n } else if (mode === \"random\") {\n const rng = mulberry32(seed);\n const total = maxSchedules ?? 1;\n for (let i = 0; i < total; i += 1) {\n const result = await executeScheduleDynamic(config, history, stateHasher, (pending) =>\n Math.floor(rng() * pending.length),\n );\n schedules.push({ schedule: result.schedule, stateHash: result.stateHash });\n }\n } else {\n const rng = mulberry32(seed);\n const stopWhenFrontierExhausted = config.stopWhenFrontierExhausted ?? false;\n let iterations = 0;\n let frontierExhausted = false;\n\n while (!frontierExhausted) {\n if (maxSchedules !== undefined && iterations >= maxSchedules) {\n break;\n }\n const result = await executeScheduleDynamic(config, history, stateHasher, (pending) =>\n Math.floor(rng() * pending.length),\n );\n schedules.push({ schedule: result.schedule, stateHash: result.stateHash });\n iterations += 1;\n\n if (stopWhenFrontierExhausted && history) {\n frontierExhausted = !result.newPathsAdded;\n }\n }\n }\n\n return {\n schedules,\n visitedPaths: history?.size() ?? 0,\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ModelCheckerPhase } from "./model-checker.js";
|
|
2
|
+
import { DatabaseAdapter, DatabaseAdapterMetadata, DatabaseContextStorage, fragnoDatabaseAdapterNameFakeSymbol, fragnoDatabaseAdapterVersionFakeSymbol } from "@fragno-dev/db/adapters";
|
|
3
|
+
import { AnySchema } from "@fragno-dev/db/schema";
|
|
4
|
+
import { SimpleQueryInterface } from "@fragno-dev/db/query";
|
|
5
|
+
import { RequestContextStorage } from "@fragno-dev/core/internal/request-context-storage";
|
|
6
|
+
import { UOWInstrumentationContext } from "@fragno-dev/db/unit-of-work";
|
|
7
|
+
|
|
8
|
+
//#region src/model-checker-adapter.d.ts
|
|
9
|
+
type SchedulerHook = (ctx: UOWInstrumentationContext, phase: ModelCheckerPhase) => Promise<void>;
|
|
10
|
+
type ModelCheckerScheduler = {
|
|
11
|
+
beforePhase: SchedulerHook;
|
|
12
|
+
afterPhase: SchedulerHook;
|
|
13
|
+
};
|
|
14
|
+
declare class ModelCheckerAdapter<TUowConfig = void> implements DatabaseAdapter<TUowConfig> {
|
|
15
|
+
#private;
|
|
16
|
+
[fragnoDatabaseAdapterNameFakeSymbol]: string;
|
|
17
|
+
[fragnoDatabaseAdapterVersionFakeSymbol]: number;
|
|
18
|
+
readonly contextStorage: RequestContextStorage<DatabaseContextStorage>;
|
|
19
|
+
readonly adapterMetadata?: DatabaseAdapterMetadata;
|
|
20
|
+
readonly namingStrategy: DatabaseAdapter<TUowConfig>["namingStrategy"];
|
|
21
|
+
prepareMigrations?: DatabaseAdapter<TUowConfig>["prepareMigrations"];
|
|
22
|
+
constructor(baseAdapter: DatabaseAdapter<TUowConfig>);
|
|
23
|
+
setScheduler(scheduler: ModelCheckerScheduler | null): void;
|
|
24
|
+
getHookProcessingAdapter(): DatabaseAdapter<TUowConfig>;
|
|
25
|
+
getSchemaVersion(namespace: string): Promise<string | undefined>;
|
|
26
|
+
createQueryEngine<const T extends AnySchema>(schema: T, namespace: string | null): SimpleQueryInterface<T, TUowConfig>;
|
|
27
|
+
isConnectionHealthy(): Promise<boolean>;
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ModelCheckerAdapter, ModelCheckerScheduler };
|
|
32
|
+
//# sourceMappingURL=model-checker-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-checker-adapter.d.ts","names":[],"sources":["../src/model-checker-adapter.ts"],"sourcesContent":[],"mappings":";;;;;;;;KAoBK,aAAA,SAAsB,kCAAkC,sBAAsB;AAA9E,KAEO,qBAAA,GAFM;EAAS,WAAA,EAGZ,aAHY;EAAkC,UAAA,EAI/C,aAJ+C;CAAsB;AAAO,cAwF7E,mBAxF6E,CAAA,aAAA,IAAA,CAAA,YAwF3B,eAxF2B,CAwFX,UAxFW,CAAA,CAAA;EAE9E,CAAA,OAAA;EAsFC,CACV,mCAAA,CAD6B,EAAA,MAAA;EAA+C,CAE5E,sCAAA,CAF4E,EAAA,MAAA;EAC5E,SAAA,cAAA,EAGwB,qBAHxB,CAG8C,sBAH9C,CAAA;EACA,SAAA,eAAA,CAAA,EAG0B,uBAH1B;EAE8C,SAAA,cAAA,EAEtB,eAFsB,CAEN,UAFM,CAAA,CAAA,gBAAA,CAAA;EAAtB,iBAAA,CAAA,EAGL,eAHK,CAGW,UAHX,CAAA,CAAA,mBAAA,CAAA;EACE,WAAA,CAAA,WAAA,EAOF,eAPE,CAOc,UAPd,CAAA;EACc,YAAA,CAAA,SAAA,EAiBjB,qBAjBiB,GAAA,IAAA,CAAA,EAAA,IAAA;EAAhB,wBAAA,CAAA,CAAA,EAqBG,eArBH,CAqBmB,UArBnB,CAAA;EACW,gBAAA,CAAA,SAAA,EAAA,MAAA,CAAA,EAwBC,OAxBD,CAAA,MAAA,GAAA,SAAA,CAAA;EAAhB,iBAAA,CAAA,gBA4Bc,SA5Bd,CAAA,CAAA,MAAA,EA6BV,CA7BU,EAAA,SAAA,EAAA,MAAA,GAAA,IAAA,CAAA,EA+BjB,oBA/BiB,CA+BI,CA/BJ,EA+BO,UA/BP,CAAA;EAKqB,mBAAA,CAAA,CAAA,EAwElB,OAxEkB,CAAA,OAAA,CAAA;EAAhB,KAAA,CAAA,CAAA,EA4EhB,OA5EgB,CAAA,IAAA,CAAA"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { fragnoDatabaseAdapterNameFakeSymbol, fragnoDatabaseAdapterVersionFakeSymbol } from "@fragno-dev/db/adapters";
|
|
2
|
+
|
|
3
|
+
//#region src/model-checker-adapter.ts
|
|
4
|
+
const isInstrumentationInjection = (value) => {
|
|
5
|
+
if (!value || typeof value !== "object") return false;
|
|
6
|
+
const candidate = value;
|
|
7
|
+
return candidate.type === "conflict" || candidate.type === "error";
|
|
8
|
+
};
|
|
9
|
+
const mergeHook = (primary, secondary, options = {}) => {
|
|
10
|
+
if (!primary && !secondary) return;
|
|
11
|
+
const hook = async (ctx) => {
|
|
12
|
+
let result;
|
|
13
|
+
if (options.alwaysCallSecondary) try {
|
|
14
|
+
result = await primary?.(ctx);
|
|
15
|
+
} finally {
|
|
16
|
+
await secondary?.(ctx);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
result = await primary?.(ctx);
|
|
20
|
+
if (!isInstrumentationInjection(result)) {
|
|
21
|
+
const secondaryResult = await secondary?.(ctx);
|
|
22
|
+
if (isInstrumentationInjection(secondaryResult)) return secondaryResult;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (isInstrumentationInjection(result)) return result;
|
|
26
|
+
};
|
|
27
|
+
return hook;
|
|
28
|
+
};
|
|
29
|
+
const mergeInstrumentation = (existing, scheduler) => {
|
|
30
|
+
if (!scheduler) return { instrumentation: existing };
|
|
31
|
+
const schedulerBeforeRetrieve = (ctx) => scheduler.beforePhase(ctx, "retrieve");
|
|
32
|
+
const schedulerAfterRetrieve = (ctx) => scheduler.afterPhase(ctx, "retrieve");
|
|
33
|
+
const schedulerBeforeMutate = (ctx) => scheduler.beforePhase(ctx, "mutate");
|
|
34
|
+
const schedulerAfterMutate = (ctx) => scheduler.afterPhase(ctx, "mutate");
|
|
35
|
+
return {
|
|
36
|
+
instrumentation: {
|
|
37
|
+
beforeRetrieve: mergeHook(schedulerBeforeRetrieve, existing?.beforeRetrieve),
|
|
38
|
+
afterRetrieve: existing?.afterRetrieve,
|
|
39
|
+
beforeMutate: mergeHook(schedulerBeforeMutate, existing?.beforeMutate),
|
|
40
|
+
afterMutate: existing?.afterMutate
|
|
41
|
+
},
|
|
42
|
+
instrumentationFinalizer: {
|
|
43
|
+
afterRetrieve: schedulerAfterRetrieve,
|
|
44
|
+
afterMutate: schedulerAfterMutate
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
var ModelCheckerAdapter = class {
|
|
49
|
+
[fragnoDatabaseAdapterNameFakeSymbol];
|
|
50
|
+
[fragnoDatabaseAdapterVersionFakeSymbol];
|
|
51
|
+
contextStorage;
|
|
52
|
+
adapterMetadata;
|
|
53
|
+
namingStrategy;
|
|
54
|
+
prepareMigrations;
|
|
55
|
+
#baseAdapter;
|
|
56
|
+
#scheduler = null;
|
|
57
|
+
constructor(baseAdapter) {
|
|
58
|
+
this.#baseAdapter = baseAdapter;
|
|
59
|
+
this.contextStorage = baseAdapter.contextStorage;
|
|
60
|
+
this.adapterMetadata = baseAdapter.adapterMetadata;
|
|
61
|
+
this.namingStrategy = baseAdapter.namingStrategy;
|
|
62
|
+
this[fragnoDatabaseAdapterNameFakeSymbol] = baseAdapter[fragnoDatabaseAdapterNameFakeSymbol];
|
|
63
|
+
this[fragnoDatabaseAdapterVersionFakeSymbol] = baseAdapter[fragnoDatabaseAdapterVersionFakeSymbol];
|
|
64
|
+
this.prepareMigrations = baseAdapter.prepareMigrations?.bind(baseAdapter);
|
|
65
|
+
}
|
|
66
|
+
setScheduler(scheduler) {
|
|
67
|
+
this.#scheduler = scheduler;
|
|
68
|
+
}
|
|
69
|
+
getHookProcessingAdapter() {
|
|
70
|
+
return this.#baseAdapter;
|
|
71
|
+
}
|
|
72
|
+
getSchemaVersion(namespace) {
|
|
73
|
+
return this.#baseAdapter.getSchemaVersion(namespace);
|
|
74
|
+
}
|
|
75
|
+
createQueryEngine(schema, namespace) {
|
|
76
|
+
const engine = this.#baseAdapter.createQueryEngine(schema, namespace);
|
|
77
|
+
const wrappedCreateUnitOfWork = (name, config) => {
|
|
78
|
+
const scheduler = this.#scheduler;
|
|
79
|
+
if (!scheduler) return engine.createUnitOfWork(name, config);
|
|
80
|
+
const configWithInstrumentation = (() => {
|
|
81
|
+
if (!config || typeof config !== "object") return mergeInstrumentation(void 0, scheduler);
|
|
82
|
+
const typedConfig = config;
|
|
83
|
+
const merged = mergeInstrumentation(typedConfig.instrumentation, scheduler);
|
|
84
|
+
return {
|
|
85
|
+
...typedConfig,
|
|
86
|
+
instrumentation: merged.instrumentation,
|
|
87
|
+
instrumentationFinalizer: merged.instrumentationFinalizer ?? typedConfig.instrumentationFinalizer
|
|
88
|
+
};
|
|
89
|
+
})();
|
|
90
|
+
return engine.createUnitOfWork(name, configWithInstrumentation);
|
|
91
|
+
};
|
|
92
|
+
return new Proxy(engine, { get(target, prop, receiver) {
|
|
93
|
+
if (prop === "createUnitOfWork") return wrappedCreateUnitOfWork;
|
|
94
|
+
const value = Reflect.get(target, prop, receiver);
|
|
95
|
+
if (typeof value === "function") return value.bind(target);
|
|
96
|
+
return value;
|
|
97
|
+
} });
|
|
98
|
+
}
|
|
99
|
+
isConnectionHealthy() {
|
|
100
|
+
return this.#baseAdapter.isConnectionHealthy();
|
|
101
|
+
}
|
|
102
|
+
close() {
|
|
103
|
+
return this.#baseAdapter.close();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
export { ModelCheckerAdapter };
|
|
109
|
+
//# sourceMappingURL=model-checker-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-checker-adapter.js","names":["result: void | UOWInstrumentationInjection","#baseAdapter","#scheduler"],"sources":["../src/model-checker-adapter.ts"],"sourcesContent":["import type {\n DatabaseAdapter,\n DatabaseAdapterMetadata,\n DatabaseContextStorage,\n} from \"@fragno-dev/db/adapters\";\nimport {\n fragnoDatabaseAdapterNameFakeSymbol,\n fragnoDatabaseAdapterVersionFakeSymbol,\n} from \"@fragno-dev/db/adapters\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport type { SimpleQueryInterface } from \"@fragno-dev/db/query\";\nimport type { RequestContextStorage } from \"@fragno-dev/core/internal/request-context-storage\";\nimport type {\n UOWInstrumentation,\n UOWInstrumentationContext,\n UOWInstrumentationInjection,\n UOWInstrumentationFinalizer,\n} from \"@fragno-dev/db/unit-of-work\";\nimport type { ModelCheckerPhase } from \"./model-checker\";\n\ntype SchedulerHook = (ctx: UOWInstrumentationContext, phase: ModelCheckerPhase) => Promise<void>;\n\nexport type ModelCheckerScheduler = {\n beforePhase: SchedulerHook;\n afterPhase: SchedulerHook;\n};\n\nconst isInstrumentationInjection = (value: unknown): value is UOWInstrumentationInjection => {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const candidate = value as { type?: string };\n return candidate.type === \"conflict\" || candidate.type === \"error\";\n};\n\nconst mergeHook = (\n primary: UOWInstrumentation[keyof UOWInstrumentation] | undefined,\n secondary: UOWInstrumentation[keyof UOWInstrumentation] | undefined,\n options: { alwaysCallSecondary?: boolean } = {},\n): UOWInstrumentation[keyof UOWInstrumentation] | undefined => {\n if (!primary && !secondary) {\n return undefined;\n }\n\n const hook = async (\n ctx: UOWInstrumentationContext,\n ): Promise<void | UOWInstrumentationInjection> => {\n let result: void | UOWInstrumentationInjection;\n if (options.alwaysCallSecondary) {\n try {\n result = await primary?.(ctx);\n } finally {\n await secondary?.(ctx);\n }\n } else {\n result = await primary?.(ctx);\n if (!isInstrumentationInjection(result)) {\n const secondaryResult = await secondary?.(ctx);\n if (isInstrumentationInjection(secondaryResult)) {\n return secondaryResult;\n }\n }\n }\n\n if (isInstrumentationInjection(result)) {\n return result;\n }\n\n return undefined;\n };\n\n return hook as UOWInstrumentation[keyof UOWInstrumentation];\n};\n\nconst mergeInstrumentation = (\n existing: UOWInstrumentation | undefined,\n scheduler: ModelCheckerScheduler | null,\n): {\n instrumentation?: UOWInstrumentation;\n instrumentationFinalizer?: UOWInstrumentationFinalizer;\n} => {\n if (!scheduler) {\n return { instrumentation: existing };\n }\n\n const schedulerBeforeRetrieve = (ctx: UOWInstrumentationContext) =>\n scheduler.beforePhase(ctx, \"retrieve\");\n const schedulerAfterRetrieve = (ctx: UOWInstrumentationContext) =>\n scheduler.afterPhase(ctx, \"retrieve\");\n const schedulerBeforeMutate = (ctx: UOWInstrumentationContext) =>\n scheduler.beforePhase(ctx, \"mutate\");\n const schedulerAfterMutate = (ctx: UOWInstrumentationContext) =>\n scheduler.afterPhase(ctx, \"mutate\");\n\n return {\n instrumentation: {\n beforeRetrieve: mergeHook(schedulerBeforeRetrieve, existing?.beforeRetrieve),\n afterRetrieve: existing?.afterRetrieve,\n beforeMutate: mergeHook(schedulerBeforeMutate, existing?.beforeMutate),\n afterMutate: existing?.afterMutate,\n },\n instrumentationFinalizer: {\n afterRetrieve: schedulerAfterRetrieve,\n afterMutate: schedulerAfterMutate,\n },\n };\n};\n\nexport class ModelCheckerAdapter<TUowConfig = void> implements DatabaseAdapter<TUowConfig> {\n [fragnoDatabaseAdapterNameFakeSymbol]: string;\n [fragnoDatabaseAdapterVersionFakeSymbol]: number;\n\n readonly contextStorage: RequestContextStorage<DatabaseContextStorage>;\n readonly adapterMetadata?: DatabaseAdapterMetadata;\n readonly namingStrategy: DatabaseAdapter<TUowConfig>[\"namingStrategy\"];\n prepareMigrations?: DatabaseAdapter<TUowConfig>[\"prepareMigrations\"];\n\n #baseAdapter: DatabaseAdapter<TUowConfig>;\n #scheduler: ModelCheckerScheduler | null = null;\n\n constructor(baseAdapter: DatabaseAdapter<TUowConfig>) {\n this.#baseAdapter = baseAdapter;\n this.contextStorage = baseAdapter.contextStorage;\n this.adapterMetadata = baseAdapter.adapterMetadata;\n this.namingStrategy = baseAdapter.namingStrategy;\n this[fragnoDatabaseAdapterNameFakeSymbol] = baseAdapter[fragnoDatabaseAdapterNameFakeSymbol];\n this[fragnoDatabaseAdapterVersionFakeSymbol] =\n baseAdapter[fragnoDatabaseAdapterVersionFakeSymbol];\n this.prepareMigrations = baseAdapter.prepareMigrations?.bind(baseAdapter);\n }\n\n setScheduler(scheduler: ModelCheckerScheduler | null): void {\n this.#scheduler = scheduler;\n }\n\n getHookProcessingAdapter(): DatabaseAdapter<TUowConfig> {\n return this.#baseAdapter;\n }\n\n getSchemaVersion(namespace: string): Promise<string | undefined> {\n return this.#baseAdapter.getSchemaVersion(namespace);\n }\n\n createQueryEngine<const T extends AnySchema>(\n schema: T,\n namespace: string | null,\n ): SimpleQueryInterface<T, TUowConfig> {\n const engine = this.#baseAdapter.createQueryEngine(schema, namespace);\n\n const wrappedCreateUnitOfWork = (name?: string, config?: TUowConfig) => {\n const scheduler = this.#scheduler;\n if (!scheduler) {\n return engine.createUnitOfWork(name, config);\n }\n\n const configWithInstrumentation = (() => {\n if (!config || typeof config !== \"object\") {\n const merged = mergeInstrumentation(undefined, scheduler);\n return merged as TUowConfig;\n }\n\n const typedConfig = config as {\n instrumentation?: UOWInstrumentation;\n instrumentationFinalizer?: UOWInstrumentationFinalizer;\n };\n const merged = mergeInstrumentation(typedConfig.instrumentation, scheduler);\n return {\n ...typedConfig,\n instrumentation: merged.instrumentation,\n instrumentationFinalizer:\n merged.instrumentationFinalizer ?? typedConfig.instrumentationFinalizer,\n } as TUowConfig;\n })();\n\n return engine.createUnitOfWork(name, configWithInstrumentation);\n };\n\n const proxy = new Proxy(engine, {\n get(target, prop, receiver) {\n if (prop === \"createUnitOfWork\") {\n return wrappedCreateUnitOfWork;\n }\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return value.bind(target);\n }\n return value;\n },\n });\n return proxy;\n }\n\n isConnectionHealthy(): Promise<boolean> {\n return this.#baseAdapter.isConnectionHealthy();\n }\n\n close(): Promise<void> {\n return this.#baseAdapter.close();\n }\n}\n"],"mappings":";;;AA2BA,MAAM,8BAA8B,UAAyD;AAC3F,KAAI,CAAC,SAAS,OAAO,UAAU,SAC7B,QAAO;CAET,MAAM,YAAY;AAClB,QAAO,UAAU,SAAS,cAAc,UAAU,SAAS;;AAG7D,MAAM,aACJ,SACA,WACA,UAA6C,EAAE,KACc;AAC7D,KAAI,CAAC,WAAW,CAAC,UACf;CAGF,MAAM,OAAO,OACX,QACgD;EAChD,IAAIA;AACJ,MAAI,QAAQ,oBACV,KAAI;AACF,YAAS,MAAM,UAAU,IAAI;YACrB;AACR,SAAM,YAAY,IAAI;;OAEnB;AACL,YAAS,MAAM,UAAU,IAAI;AAC7B,OAAI,CAAC,2BAA2B,OAAO,EAAE;IACvC,MAAM,kBAAkB,MAAM,YAAY,IAAI;AAC9C,QAAI,2BAA2B,gBAAgB,CAC7C,QAAO;;;AAKb,MAAI,2BAA2B,OAAO,CACpC,QAAO;;AAMX,QAAO;;AAGT,MAAM,wBACJ,UACA,cAIG;AACH,KAAI,CAAC,UACH,QAAO,EAAE,iBAAiB,UAAU;CAGtC,MAAM,2BAA2B,QAC/B,UAAU,YAAY,KAAK,WAAW;CACxC,MAAM,0BAA0B,QAC9B,UAAU,WAAW,KAAK,WAAW;CACvC,MAAM,yBAAyB,QAC7B,UAAU,YAAY,KAAK,SAAS;CACtC,MAAM,wBAAwB,QAC5B,UAAU,WAAW,KAAK,SAAS;AAErC,QAAO;EACL,iBAAiB;GACf,gBAAgB,UAAU,yBAAyB,UAAU,eAAe;GAC5E,eAAe,UAAU;GACzB,cAAc,UAAU,uBAAuB,UAAU,aAAa;GACtE,aAAa,UAAU;GACxB;EACD,0BAA0B;GACxB,eAAe;GACf,aAAa;GACd;EACF;;AAGH,IAAa,sBAAb,MAA2F;CACzF,CAAC;CACD,CAAC;CAED,AAAS;CACT,AAAS;CACT,AAAS;CACT;CAEA;CACA,aAA2C;CAE3C,YAAY,aAA0C;AACpD,QAAKC,cAAe;AACpB,OAAK,iBAAiB,YAAY;AAClC,OAAK,kBAAkB,YAAY;AACnC,OAAK,iBAAiB,YAAY;AAClC,OAAK,uCAAuC,YAAY;AACxD,OAAK,0CACH,YAAY;AACd,OAAK,oBAAoB,YAAY,mBAAmB,KAAK,YAAY;;CAG3E,aAAa,WAA+C;AAC1D,QAAKC,YAAa;;CAGpB,2BAAwD;AACtD,SAAO,MAAKD;;CAGd,iBAAiB,WAAgD;AAC/D,SAAO,MAAKA,YAAa,iBAAiB,UAAU;;CAGtD,kBACE,QACA,WACqC;EACrC,MAAM,SAAS,MAAKA,YAAa,kBAAkB,QAAQ,UAAU;EAErE,MAAM,2BAA2B,MAAe,WAAwB;GACtE,MAAM,YAAY,MAAKC;AACvB,OAAI,CAAC,UACH,QAAO,OAAO,iBAAiB,MAAM,OAAO;GAG9C,MAAM,mCAAmC;AACvC,QAAI,CAAC,UAAU,OAAO,WAAW,SAE/B,QADe,qBAAqB,QAAW,UAAU;IAI3D,MAAM,cAAc;IAIpB,MAAM,SAAS,qBAAqB,YAAY,iBAAiB,UAAU;AAC3E,WAAO;KACL,GAAG;KACH,iBAAiB,OAAO;KACxB,0BACE,OAAO,4BAA4B,YAAY;KAClD;OACC;AAEJ,UAAO,OAAO,iBAAiB,MAAM,0BAA0B;;AAejE,SAZc,IAAI,MAAM,QAAQ,EAC9B,IAAI,QAAQ,MAAM,UAAU;AAC1B,OAAI,SAAS,mBACX,QAAO;GAET,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,SAAS;AACjD,OAAI,OAAO,UAAU,WACnB,QAAO,MAAM,KAAK,OAAO;AAE3B,UAAO;KAEV,CAAC;;CAIJ,sBAAwC;AACtC,SAAO,MAAKD,YAAa,qBAAqB;;CAGhD,QAAuB;AACrB,SAAO,MAAKA,YAAa,OAAO"}
|