@classytic/repo-core 0.28.0 → 0.30.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 (55) hide show
  1. package/CHANGELOG.md +1290 -1098
  2. package/dist/cache/engine.d.mts +7 -5
  3. package/dist/cache/engine.mjs +34 -16
  4. package/dist/cache/memo.d.mts +4 -0
  5. package/dist/cache/memo.mjs +17 -4
  6. package/dist/cache/memory-adapter.mjs +6 -4
  7. package/dist/cache/plugin/read-hooks.mjs +1 -1
  8. package/dist/cache/stable-stringify.d.mts +7 -8
  9. package/dist/cache/stable-stringify.mjs +14 -11
  10. package/dist/cache/tag-index.mjs +1 -2
  11. package/dist/cache/types.d.mts +4 -3
  12. package/dist/errors/codes.d.mts +117 -0
  13. package/dist/errors/codes.mjs +114 -0
  14. package/dist/errors/contract.d.mts +2 -1
  15. package/dist/errors/contract.mjs +9 -3
  16. package/dist/errors/index.d.mts +3 -2
  17. package/dist/errors/index.mjs +2 -2
  18. package/dist/errors/types.d.mts +3 -30
  19. package/dist/filter/builders.mjs +1 -1
  20. package/dist/filter/match-record.mjs +9 -3
  21. package/dist/filter/match.mjs +25 -9
  22. package/dist/idempotency/index.d.mts +462 -0
  23. package/dist/idempotency/index.mjs +419 -0
  24. package/dist/pagination/types.d.mts +6 -0
  25. package/dist/query-parser/coerce.d.mts +18 -11
  26. package/dist/query-parser/coerce.mjs +37 -69
  27. package/dist/query-parser/errors.d.mts +30 -0
  28. package/dist/query-parser/errors.mjs +32 -0
  29. package/dist/query-parser/grammar.d.mts +107 -0
  30. package/dist/query-parser/grammar.mjs +462 -0
  31. package/dist/query-parser/index.d.mts +5 -2
  32. package/dist/query-parser/index.mjs +5 -2
  33. package/dist/query-parser/parse-url.d.mts +1 -1
  34. package/dist/query-parser/parse-url.mjs +72 -205
  35. package/dist/query-parser/regex-safety.d.mts +31 -0
  36. package/dist/query-parser/regex-safety.mjs +151 -0
  37. package/dist/query-parser/reserved.mjs +1 -0
  38. package/dist/query-parser/types.d.mts +15 -25
  39. package/dist/repository/types.d.mts +6 -5
  40. package/dist/schema/field-rules.mjs +49 -6
  41. package/dist/schema/standard-schema.mjs +1 -1
  42. package/dist/sync/index.d.mts +90 -55
  43. package/dist/sync/index.mjs +0 -0
  44. package/dist/testing/change-log-conformance.d.mts +21 -0
  45. package/dist/testing/change-log-conformance.mjs +204 -0
  46. package/dist/testing/command-stream-conformance.d.mts +15 -0
  47. package/dist/testing/command-stream-conformance.mjs +163 -0
  48. package/dist/testing/idempotency-conformance.d.mts +15 -0
  49. package/dist/testing/idempotency-conformance.mjs +315 -0
  50. package/dist/testing/index.d.mts +5 -1
  51. package/dist/testing/index.mjs +5 -1
  52. package/dist/testing/query-grammar-conformance.d.mts +60 -0
  53. package/dist/testing/query-grammar-conformance.mjs +380 -0
  54. package/package.json +6 -2
  55. package/dist/errors/types.mjs +0 -29
@@ -0,0 +1,315 @@
1
+ import { completeClaim, decideClaim, identityKey, runIdempotent } from "../idempotency/index.mjs";
2
+ import { describe, it } from "vitest";
3
+ //#region src/testing/idempotency-conformance.ts
4
+ /**
5
+ * `runIdempotencyStoreConformance` — the contract every `IdempotencyClaimStore` passes.
6
+ * Mirrors `runLockAdapterConformance`: a kit runs it against its real backend, and passing it
7
+ * is what "implements the store" means.
8
+ *
9
+ * describe('mongokit/idempotency conformance', () => {
10
+ * runIdempotencyStoreConformance({ createStore: () => createIdempotencyStore(model) });
11
+ * });
12
+ *
13
+ * The concurrency cases are the ones that matter — a store built from read-then-write passes
14
+ * everything else and double-executes in production.
15
+ */
16
+ const T0 = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z");
17
+ const LEASE_MS = 3e4;
18
+ let seq = 0;
19
+ const freshIdentity = (over = {}) => ({
20
+ operation: "conformance.op",
21
+ key: `key-${Date.now().toString(36)}-${++seq}`,
22
+ scope: { organizationId: "org-1" },
23
+ ...over
24
+ });
25
+ function mint(identity, leaseToken, now = T0) {
26
+ const decision = decideClaim(null, {
27
+ identity,
28
+ requestFingerprint: "fp-1",
29
+ now,
30
+ leaseToken,
31
+ leaseMs: LEASE_MS
32
+ });
33
+ if (decision.outcome !== "claimed") throw new Error("unreachable");
34
+ return decision.claim;
35
+ }
36
+ function check(condition, message) {
37
+ if (!condition) throw new Error(`[IdempotencyClaimStore contract] ${message}`);
38
+ }
39
+ const sameInstant = (a, b) => a instanceof Date && a.getTime() === b.getTime();
40
+ /** The contract's cases, runner-agnostic — each `run` throws on failure. */
41
+ function idempotencyStoreCases(harness) {
42
+ return [
43
+ ["get of an unknown identity is null", async (store) => {
44
+ check(await store.get(freshIdentity()) === null, "get must return null for an identity never inserted");
45
+ }],
46
+ ["insert round-trips every field, Dates as Dates", async (store) => {
47
+ const identity = freshIdentity();
48
+ const claim = mint(identity, "lease-a");
49
+ check(await store.insert(claim), "first insert must win");
50
+ const got = await store.get(identity);
51
+ check(got !== null, "inserted claim must be readable");
52
+ check(identityKey(got.identity) === identityKey(identity), "identity must round-trip");
53
+ check(got.requestFingerprint === "fp-1", "requestFingerprint must round-trip");
54
+ check(got.state === "in_flight", "state must round-trip");
55
+ check(got.leaseToken === "lease-a", "leaseToken must round-trip");
56
+ check(got.attempts === 1, "attempts must round-trip");
57
+ check(sameInstant(got.leaseExpiresAt, claim.leaseExpiresAt), "leaseExpiresAt must come back as the same Date");
58
+ check(sameInstant(got.createdAt, claim.createdAt), "createdAt must come back as the same Date");
59
+ }],
60
+ ["a second insert of one identity loses and changes nothing", async (store) => {
61
+ const identity = freshIdentity();
62
+ check(await store.insert(mint(identity, "lease-a")), "first insert must win");
63
+ check(!await store.insert(mint(identity, "lease-b")), "second insert must return false");
64
+ check((await store.get(identity))?.leaseToken === "lease-a", "the loser must not overwrite the winner");
65
+ }],
66
+ ["CONCURRENT inserts of one identity: exactly one wins", async (store) => {
67
+ const identity = freshIdentity();
68
+ const results = await Promise.all(Array.from({ length: 12 }, (_, i) => store.insert(mint(identity, `lease-${i}`))));
69
+ check(results.filter(Boolean).length === 1, `exactly one insert must win, got ${results.filter(Boolean).length}`);
70
+ }],
71
+ ["operation and scope are part of the identity; scope key order is not", async (store) => {
72
+ const base = freshIdentity({ scope: {
73
+ organizationId: "org-1",
74
+ registerId: "r1"
75
+ } });
76
+ check(await store.insert(mint(base, "lease-a")), "base insert must win");
77
+ check(await store.insert(mint({
78
+ ...base,
79
+ operation: "other.op"
80
+ }, "lease-b")), "another operation with the same key is a different claim");
81
+ check(await store.insert(mint({
82
+ ...base,
83
+ scope: {
84
+ organizationId: "org-2",
85
+ registerId: "r1"
86
+ }
87
+ }, "lease-c")), "another scope is a different claim");
88
+ const reordered = {
89
+ ...base,
90
+ scope: {
91
+ registerId: "r1",
92
+ organizationId: "org-1"
93
+ }
94
+ };
95
+ check(!await store.insert(mint(reordered, "lease-d")), "scope key order must not make a new identity");
96
+ }],
97
+ ["swap requires the current lease token", async (store) => {
98
+ const identity = freshIdentity();
99
+ const claim = mint(identity, "lease-a");
100
+ await store.insert(claim);
101
+ const renewed = {
102
+ ...claim,
103
+ leaseExpiresAt: new Date(T0.getTime() + 2 * LEASE_MS)
104
+ };
105
+ check(!await store.swap(identity, "lease-wrong", renewed), "swap with a foreign token must lose");
106
+ check(await store.swap(identity, "lease-a", renewed), "swap with the owning token must win");
107
+ check(sameInstant((await store.get(identity))?.leaseExpiresAt, renewed.leaseExpiresAt), "swap must store `next`");
108
+ }],
109
+ ["CONCURRENT swaps from one token: exactly one wins", async (store) => {
110
+ const identity = freshIdentity();
111
+ await store.insert(mint(identity, "lease-old"));
112
+ const takeovers = Array.from({ length: 12 }, (_, i) => ({
113
+ ...mint(identity, `lease-new-${i}`),
114
+ attempts: 2
115
+ }));
116
+ const results = await Promise.all(takeovers.map((next) => store.swap(identity, "lease-old", next)));
117
+ check(results.filter(Boolean).length === 1, `exactly one takeover must win, got ${results.filter(Boolean).length}`);
118
+ }],
119
+ ["a terminal claim cannot be swapped or released, and replays its result", async (store) => {
120
+ const identity = freshIdentity();
121
+ const claim = mint(identity, "lease-a");
122
+ await store.insert(claim);
123
+ const value = {
124
+ orderNumber: "ORD-1",
125
+ lines: [{
126
+ sku: "a",
127
+ qty: 2
128
+ }]
129
+ };
130
+ const done = completeClaim(claim, {
131
+ leaseToken: "lease-a",
132
+ now: T0,
133
+ result: {
134
+ status: "succeeded",
135
+ value
136
+ }
137
+ });
138
+ check(await store.swap(identity, "lease-a", done), "completion must win");
139
+ check(!await store.swap(identity, "lease-a", done), "a terminal claim must refuse swap");
140
+ check(!await store.release(identity, "lease-a"), "a terminal claim must refuse release");
141
+ const got = await store.get(identity);
142
+ check(got?.state === "succeeded", "terminal state must round-trip");
143
+ check(JSON.stringify(got.result) === JSON.stringify({
144
+ status: "succeeded",
145
+ value
146
+ }), "the stored value must replay exactly");
147
+ }],
148
+ ["a failed result round-trips its code, message and status", async (store) => {
149
+ const identity = freshIdentity();
150
+ const claim = mint(identity, "lease-a");
151
+ await store.insert(claim);
152
+ const error = {
153
+ code: "OUT_OF_STOCK",
154
+ message: "no units",
155
+ status: 409
156
+ };
157
+ await store.swap(identity, "lease-a", completeClaim(claim, {
158
+ leaseToken: "lease-a",
159
+ now: T0,
160
+ result: {
161
+ status: "failed",
162
+ error
163
+ }
164
+ }));
165
+ const got = await store.get(identity);
166
+ check(got?.state === "failed", "failed state must round-trip");
167
+ check(JSON.stringify(got.result) === JSON.stringify({
168
+ status: "failed",
169
+ error
170
+ }), "the stored failure must replay exactly");
171
+ }],
172
+ ["context, progress and lastError round-trip", async (store) => {
173
+ const identity = freshIdentity();
174
+ const claim = {
175
+ ...mint(identity, "lease-a"),
176
+ context: {
177
+ orderNumber: "ORD-1",
178
+ amount: 500
179
+ }
180
+ };
181
+ await store.insert(claim);
182
+ const next = {
183
+ ...claim,
184
+ progress: { gatewayRefundId: "g1" },
185
+ lastError: "timeout"
186
+ };
187
+ check(await store.swap(identity, "lease-a", next), "swap must win");
188
+ const got = await store.get(identity);
189
+ check(JSON.stringify(got?.context) === JSON.stringify({
190
+ orderNumber: "ORD-1",
191
+ amount: 500
192
+ }), "context must round-trip");
193
+ check(got?.progress?.["gatewayRefundId"] === "g1", "progress must round-trip");
194
+ check(got?.lastError === "timeout", "lastError must round-trip");
195
+ }],
196
+ ["listLapsed returns only lapsed in-flight claims of the operation, oldest first", async (store) => {
197
+ const operation = `conformance.sweep.${++seq}`;
198
+ const at = (ms) => new Date(T0.getTime() + ms);
199
+ const lapsedOld = {
200
+ ...mint(freshIdentity({ operation }), "l1"),
201
+ leaseExpiresAt: at(-2e3)
202
+ };
203
+ const lapsedNew = {
204
+ ...mint(freshIdentity({ operation }), "l2"),
205
+ leaseExpiresAt: at(-1e3)
206
+ };
207
+ const live = {
208
+ ...mint(freshIdentity({ operation }), "l3"),
209
+ leaseExpiresAt: at(6e4)
210
+ };
211
+ const otherOp = {
212
+ ...mint(freshIdentity({ operation: `${operation}.other` }), "l4"),
213
+ leaseExpiresAt: at(-5e3)
214
+ };
215
+ const doneClaim = {
216
+ ...mint(freshIdentity({ operation }), "l5"),
217
+ leaseExpiresAt: at(-9e3)
218
+ };
219
+ for (const c of [
220
+ lapsedNew,
221
+ live,
222
+ otherOp,
223
+ lapsedOld,
224
+ doneClaim
225
+ ]) await store.insert(c);
226
+ await store.swap(doneClaim.identity, "l5", completeClaim(doneClaim, {
227
+ leaseToken: "l5",
228
+ now: T0,
229
+ result: {
230
+ status: "succeeded",
231
+ value: 1
232
+ }
233
+ }));
234
+ const found = await store.listLapsed(operation, T0, 10);
235
+ check(JSON.stringify(found.map((c) => c.leaseToken)) === JSON.stringify(["l1", "l2"]), `expected [l1, l2] (lapsed, in flight, this operation, oldest first), got ${JSON.stringify(found.map((c) => c.leaseToken))}`);
236
+ check((await store.listLapsed(operation, T0, 1)).length === 1, "limit must be honoured");
237
+ }],
238
+ ["release frees only the owner's in-flight claim", async (store) => {
239
+ const identity = freshIdentity();
240
+ await store.insert(mint(identity, "lease-a"));
241
+ check(!await store.release(identity, "lease-wrong"), "release with a foreign token must lose");
242
+ check(await store.release(identity, "lease-a"), "release with the owning token must win");
243
+ check(await store.get(identity) === null, "a released identity reads as never claimed");
244
+ check(await store.insert(mint(identity, "lease-b")), "a released identity can be claimed again");
245
+ }],
246
+ ["a recovery point survives a crash and is handed to the takeover", async (store) => {
247
+ const identity = freshIdentity();
248
+ const crash = /* @__PURE__ */ new Error("process died after the order was written");
249
+ await runIdempotent({
250
+ store,
251
+ identity,
252
+ requestFingerprint: "fp-1",
253
+ now: () => T0,
254
+ leaseMs: LEASE_MS,
255
+ execute: async ({ checkpoint }) => {
256
+ await checkpoint({ orderNumber: "ORD-9" });
257
+ throw crash;
258
+ }
259
+ }).then(() => check(false, "the crashed attempt must not report success"), (error) => check(error === crash, "the crash must surface unchanged"));
260
+ const stored = await store.get(identity);
261
+ check(stored?.state === "in_flight", "a failure after a recovery point must KEEP the claim, not release it");
262
+ check(stored.progress?.["orderNumber"] === "ORD-9", "the recovery point must be stored");
263
+ let resumedFrom;
264
+ const later = new Date(T0.getTime() + LEASE_MS);
265
+ const second = await runIdempotent({
266
+ store,
267
+ identity,
268
+ requestFingerprint: "fp-1",
269
+ now: () => later,
270
+ execute: async ({ progress, attempt }) => {
271
+ resumedFrom = {
272
+ progress,
273
+ attempt
274
+ };
275
+ return { orderNumber: progress?.["orderNumber"] };
276
+ }
277
+ });
278
+ check(JSON.stringify(resumedFrom) === JSON.stringify({
279
+ progress: { orderNumber: "ORD-9" },
280
+ attempt: 2
281
+ }), `the takeover must resume from the recovery point, got ${JSON.stringify(resumedFrom)}`);
282
+ check(second.outcome === "executed", "the takeover completes the command");
283
+ }],
284
+ ["runIdempotent over this store executes ONCE under concurrent retries", async (store) => {
285
+ const identity = freshIdentity();
286
+ let executions = 0;
287
+ const attempt = () => runIdempotent({
288
+ store,
289
+ identity,
290
+ requestFingerprint: "fp-1",
291
+ execute: async () => {
292
+ executions++;
293
+ await new Promise((resolve) => setTimeout(resolve, 20));
294
+ return { orderNumber: "ORD-1" };
295
+ }
296
+ });
297
+ const runs = await Promise.all(Array.from({ length: 8 }, attempt));
298
+ const again = await attempt();
299
+ check(executions === 1, `execute must run exactly once, ran ${executions}`);
300
+ check(runs.filter((r) => r.outcome === "executed").length === 1, "exactly one run reports executed");
301
+ check(again.outcome === "replayed" && again.value.orderNumber === "ORD-1", "a later retry replays the stored value");
302
+ }]
303
+ ].map(([name, body]) => ({
304
+ name,
305
+ run: async () => body(await harness.createStore())
306
+ }));
307
+ }
308
+ /** Register every contract case as a vitest test against `harness.createStore()`. */
309
+ function runIdempotencyStoreConformance(harness) {
310
+ describe("IdempotencyClaimStore contract", () => {
311
+ for (const c of idempotencyStoreCases(harness)) it(c.name, c.run);
312
+ });
313
+ }
314
+ //#endregion
315
+ export { idempotencyStoreCases, runIdempotencyStoreConformance };
@@ -1,7 +1,11 @@
1
1
  import { AggregateOpsSupport } from "../repository/capabilities.mjs";
2
+ import { ChangeLogConformanceHarness, changeLogStoreCases, runChangeLogStoreConformance } from "./change-log-conformance.mjs";
3
+ import { CommandStreamConformanceHarness, commandStreamStoreCases, runCommandStreamStoreConformance } from "./command-stream-conformance.mjs";
2
4
  import { ConformanceContext, ConformanceDoc, ConformanceFeatures, ConformanceHarness } from "./types.mjs";
3
5
  import { runStandardRepoConformance } from "./conformance.mjs";
6
+ import { IdempotencyConformanceHarness, idempotencyStoreCases, runIdempotencyStoreConformance } from "./idempotency-conformance.mjs";
4
7
  import { LockConformanceHarness, runLockAdapterConformance } from "./lock-conformance.mjs";
5
8
  import { PurgeConformanceContext, PurgeConformanceHarness, runPurgeConformance } from "./purge-conformance.mjs";
9
+ import { QUERY_GRAMMAR_DOCS, QUERY_GRAMMAR_FILTER_CASES, QUERY_GRAMMAR_PAGING_CASES, QUERY_GRAMMAR_REFUSED, QueryGrammarCaseOptions, QueryGrammarConformanceOptions, QueryGrammarHarness, QueryGrammarParsed, runQueryGrammarConformance } from "./query-grammar-conformance.mjs";
6
10
  import { UsageConformanceHarness, runUsageStoreContract } from "./usage-conformance.mjs";
7
- export { type AggregateOpsSupport, type ConformanceContext, type ConformanceDoc, type ConformanceFeatures, type ConformanceHarness, type LockConformanceHarness, type PurgeConformanceContext, type PurgeConformanceHarness, type UsageConformanceHarness, runLockAdapterConformance, runPurgeConformance, runStandardRepoConformance, runUsageStoreContract };
11
+ export { type AggregateOpsSupport, type ChangeLogConformanceHarness, type CommandStreamConformanceHarness, type ConformanceContext, type ConformanceDoc, type ConformanceFeatures, type ConformanceHarness, type IdempotencyConformanceHarness, type LockConformanceHarness, type PurgeConformanceContext, type PurgeConformanceHarness, QUERY_GRAMMAR_DOCS, QUERY_GRAMMAR_FILTER_CASES, QUERY_GRAMMAR_PAGING_CASES, QUERY_GRAMMAR_REFUSED, type QueryGrammarCaseOptions, type QueryGrammarConformanceOptions, type QueryGrammarHarness, type QueryGrammarParsed, type UsageConformanceHarness, changeLogStoreCases, commandStreamStoreCases, idempotencyStoreCases, runChangeLogStoreConformance, runCommandStreamStoreConformance, runIdempotencyStoreConformance, runLockAdapterConformance, runPurgeConformance, runQueryGrammarConformance, runStandardRepoConformance, runUsageStoreContract };
@@ -1,5 +1,9 @@
1
+ import { changeLogStoreCases, runChangeLogStoreConformance } from "./change-log-conformance.mjs";
2
+ import { commandStreamStoreCases, runCommandStreamStoreConformance } from "./command-stream-conformance.mjs";
1
3
  import { runStandardRepoConformance } from "./conformance.mjs";
4
+ import { idempotencyStoreCases, runIdempotencyStoreConformance } from "./idempotency-conformance.mjs";
2
5
  import { runLockAdapterConformance } from "./lock-conformance.mjs";
3
6
  import { runPurgeConformance } from "./purge-conformance.mjs";
7
+ import { QUERY_GRAMMAR_DOCS, QUERY_GRAMMAR_FILTER_CASES, QUERY_GRAMMAR_PAGING_CASES, QUERY_GRAMMAR_REFUSED, runQueryGrammarConformance } from "./query-grammar-conformance.mjs";
4
8
  import { runUsageStoreContract } from "./usage-conformance.mjs";
5
- export { runLockAdapterConformance, runPurgeConformance, runStandardRepoConformance, runUsageStoreContract };
9
+ export { QUERY_GRAMMAR_DOCS, QUERY_GRAMMAR_FILTER_CASES, QUERY_GRAMMAR_PAGING_CASES, QUERY_GRAMMAR_REFUSED, changeLogStoreCases, commandStreamStoreCases, idempotencyStoreCases, runChangeLogStoreConformance, runCommandStreamStoreConformance, runIdempotencyStoreConformance, runLockAdapterConformance, runPurgeConformance, runQueryGrammarConformance, runStandardRepoConformance, runUsageStoreContract };
@@ -0,0 +1,60 @@
1
+ //#region src/testing/query-grammar-conformance.d.ts
2
+ /**
3
+ * `runQueryGrammarConformance` — every list-query parser reads a URL the same way.
4
+ *
5
+ * Cases are SEMANTIC: each parser's output is evaluated against one fixture set and must select
6
+ * the same rows (or refuse the same input with a 400). A parser passes by agreeing on RESULTS,
7
+ * so its emitted shape (Filter IR, Mongo record, a custom dialect) is free.
8
+ */
9
+ interface QueryGrammarCaseOptions {
10
+ readonly maxLimit: number;
11
+ readonly defaultLimit: number;
12
+ readonly allowedFilterFields?: readonly string[];
13
+ readonly allowedSortFields?: readonly string[];
14
+ }
15
+ /** What a harness returns for one parsed query. */
16
+ interface QueryGrammarParsed {
17
+ /** True when the parsed filter selects `doc`. */
18
+ matches(doc: Readonly<Record<string, unknown>>): boolean;
19
+ readonly limit: number;
20
+ readonly page?: number | undefined;
21
+ readonly after?: string | undefined;
22
+ readonly sort?: Readonly<Record<string, 1 | -1>> | undefined;
23
+ }
24
+ interface QueryGrammarHarness {
25
+ /** Parse a raw query string exactly as the kit receives one in production. Throw to refuse. */
26
+ parse(query: string, options: QueryGrammarCaseOptions): QueryGrammarParsed;
27
+ }
28
+ /** The fixture every case runs against: nulls, missing fields, codes, dates, and text metacharacters. */
29
+ declare const QUERY_GRAMMAR_DOCS: readonly Readonly<Record<string, unknown>>[];
30
+ interface FilterCase {
31
+ readonly query: string;
32
+ readonly ids: readonly string[];
33
+ readonly options?: Partial<QueryGrammarCaseOptions>;
34
+ }
35
+ declare const QUERY_GRAMMAR_FILTER_CASES: readonly FilterCase[];
36
+ declare const QUERY_GRAMMAR_REFUSED: readonly {
37
+ query: string;
38
+ options?: Partial<QueryGrammarCaseOptions>;
39
+ }[];
40
+ declare const QUERY_GRAMMAR_PAGING_CASES: readonly {
41
+ query: string;
42
+ expect: {
43
+ limit: number;
44
+ page?: number;
45
+ after?: string;
46
+ sort?: Record<string, 1 | -1>;
47
+ };
48
+ }[];
49
+ /** Register the suite for one parser. */
50
+ interface QueryGrammarConformanceOptions {
51
+ /**
52
+ * Operators this backend cannot execute (e.g. `regex` on SQLite without REGEXP). A case using
53
+ * one must be REFUSED with a 400 — the kit's parser declares them out, it never fails at query
54
+ * time.
55
+ */
56
+ readonly unsupportedOperators?: readonly string[];
57
+ }
58
+ declare function runQueryGrammarConformance(name: string, harness: QueryGrammarHarness, conformance?: QueryGrammarConformanceOptions): void;
59
+ //#endregion
60
+ export { QUERY_GRAMMAR_DOCS, QUERY_GRAMMAR_FILTER_CASES, QUERY_GRAMMAR_PAGING_CASES, QUERY_GRAMMAR_REFUSED, QueryGrammarCaseOptions, QueryGrammarConformanceOptions, QueryGrammarHarness, QueryGrammarParsed, runQueryGrammarConformance };