@classytic/repo-core 0.30.0 → 0.31.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/CHANGELOG.md +1329 -1283
- package/dist/errors/codes.d.mts +10 -1
- package/dist/errors/codes.mjs +9 -1
- package/dist/errors/index.d.mts +2 -2
- package/dist/errors/index.mjs +2 -2
- package/dist/pagination/index.d.mts +3 -1
- package/dist/pagination/index.mjs +3 -1
- package/dist/pagination/merge-keyset.d.mts +50 -0
- package/dist/pagination/merge-keyset.mjs +139 -0
- package/dist/query-parser/grammar.d.mts +6 -0
- package/dist/query-parser/grammar.mjs +21 -4
- package/dist/query-parser/reserved.mjs +2 -0
- package/dist/repository/bulk-upsert.d.mts +59 -0
- package/dist/repository/bulk-upsert.mjs +50 -0
- package/dist/repository/capabilities.d.mts +23 -0
- package/dist/repository/index.d.mts +2 -1
- package/dist/repository/index.mjs +2 -1
- package/dist/repository/types.d.mts +7 -1
- package/dist/testing/data-access-conformance.d.mts +89 -0
- package/dist/testing/data-access-conformance.mjs +331 -0
- package/dist/testing/index.d.mts +5 -1
- package/dist/testing/index.mjs +5 -1
- package/dist/testing/merge-keyset-conformance.d.mts +26 -0
- package/dist/testing/merge-keyset-conformance.mjs +85 -0
- package/dist/testing/pagination-property-conformance.d.mts +44 -0
- package/dist/testing/pagination-property-conformance.mjs +160 -0
- package/dist/testing/read-model-property-conformance.d.mts +13 -0
- package/dist/testing/read-model-property-conformance.mjs +74 -0
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RepoCapabilities } from "../repository/capabilities.mjs";
|
|
2
|
+
import { ReadModelFixture } from "./data-access-conformance.mjs";
|
|
3
|
+
//#region src/testing/read-model-property-conformance.d.ts
|
|
4
|
+
interface ReadModelPropertyHarness {
|
|
5
|
+
name: string;
|
|
6
|
+
features: RepoCapabilities;
|
|
7
|
+
fixture?: () => Promise<ReadModelFixture>;
|
|
8
|
+
/** fast-check runs. Default 15. */
|
|
9
|
+
numRuns?: number;
|
|
10
|
+
}
|
|
11
|
+
declare function runReadModelPropertyConformance(harness: ReadModelPropertyHarness): void;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { ReadModelPropertyHarness, runReadModelPropertyConformance };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import fc from "fast-check";
|
|
3
|
+
//#region src/testing/read-model-property-conformance.ts
|
|
4
|
+
/**
|
|
5
|
+
* Read-model properties (fast-check), gated by `RepoCapabilities.readModel`.
|
|
6
|
+
*
|
|
7
|
+
* For random increments with random replays: the incremental model equals the source (no drift,
|
|
8
|
+
* a replay never double-counts); a rebuild from the source keeps zero drift; and corrupting one
|
|
9
|
+
* grain yields EXACTLY one drift row, naming that grain. An undeclared capability is a skipped
|
|
10
|
+
* group naming it; a declared one without a fixture fails.
|
|
11
|
+
*/
|
|
12
|
+
const eventArb = fc.record({
|
|
13
|
+
period: fc.constantFrom("2026-01", "2026-02", "2026-03"),
|
|
14
|
+
rate: fc.constantFrom(0, 5, 15),
|
|
15
|
+
tax: fc.integer({
|
|
16
|
+
min: 1,
|
|
17
|
+
max: 1e3
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
function runReadModelPropertyConformance(harness) {
|
|
21
|
+
const title = `[read-model properties] ${harness.name}`;
|
|
22
|
+
if (harness.features.readModel !== true) {
|
|
23
|
+
describe.skip(`${title} [skipped: capability 'readModel' not declared]`, () => {
|
|
24
|
+
it("skipped", () => void 0);
|
|
25
|
+
});
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
describe(title, () => {
|
|
29
|
+
if (!harness.fixture) {
|
|
30
|
+
it("declares 'readModel' and supplies its fixture", () => {
|
|
31
|
+
throw new Error(`${harness.name} declares 'readModel' but supplies no fixture`);
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const make = harness.fixture;
|
|
36
|
+
it("increments with replays equal the source; rebuild keeps zero drift; one corruption is one drift row", async () => {
|
|
37
|
+
await fc.assert(fc.asyncProperty(fc.array(eventArb, {
|
|
38
|
+
minLength: 1,
|
|
39
|
+
maxLength: 20
|
|
40
|
+
}), fc.array(fc.nat(), { maxLength: 10 }), fc.nat(), async (events, replayPicks, corruptPick) => {
|
|
41
|
+
const f = await make();
|
|
42
|
+
try {
|
|
43
|
+
const tenant = "t-prop";
|
|
44
|
+
const keyed = events.map((e, i) => ({
|
|
45
|
+
...e,
|
|
46
|
+
dedupeKey: `e${i}`
|
|
47
|
+
}));
|
|
48
|
+
await f.seedSource(tenant, events);
|
|
49
|
+
const replays = replayPicks.map((p) => keyed[p % keyed.length]).filter((x) => x !== void 0);
|
|
50
|
+
const half = Math.ceil(keyed.length / 2);
|
|
51
|
+
await f.applyIncrements(tenant, keyed.slice(0, half));
|
|
52
|
+
await f.applyIncrements(tenant, [...replays, ...keyed.slice(half)]);
|
|
53
|
+
expect(await f.reconcile(tenant)).toEqual([]);
|
|
54
|
+
await f.rebuild(tenant);
|
|
55
|
+
expect(await f.reconcile(tenant)).toEqual([]);
|
|
56
|
+
const grains = await f.readTotals(tenant);
|
|
57
|
+
const target = grains[corruptPick % grains.length];
|
|
58
|
+
if (!target) throw new Error("no grain to corrupt");
|
|
59
|
+
await f.tamper(tenant, target.period, target.rate);
|
|
60
|
+
const drift = await f.reconcile(tenant);
|
|
61
|
+
expect(drift).toHaveLength(1);
|
|
62
|
+
expect(drift[0]?.key).toMatchObject({
|
|
63
|
+
period: target.period,
|
|
64
|
+
rate: target.rate
|
|
65
|
+
});
|
|
66
|
+
} finally {
|
|
67
|
+
await f.cleanup();
|
|
68
|
+
}
|
|
69
|
+
}), { numRuns: harness.numRuns ?? 15 });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { runReadModelPropertyConformance };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/repo-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Driver-agnostic repository primitives: hooks, Filter IR, operations, pagination, cache contract. Foundation for mongokit, sqlitekit, pgkit, and prismakit. Lean by design \u2014 no plugins ship here; each kit owns its own.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|