@cavelang/query 0.27.14 → 0.29.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /** Versioned JSON projection of CAVE-Q matches. */
2
+ import { Record as ClaimRecord } from '@cavelang/store/adapter';
3
+ import type { Store } from '@cavelang/store/adapter';
4
+ import type { Match, Options } from './compile.ts';
5
+ export declare const format: "cave.query-match";
6
+ export declare const version: 1;
7
+ export type t = {
8
+ readonly format: typeof format;
9
+ readonly version: typeof version;
10
+ readonly bindings: Readonly<Record<string, string>>;
11
+ readonly claim?: ClaimRecord.t;
12
+ readonly support?: readonly ClaimRecord.t[];
13
+ readonly at?: Match['at'];
14
+ };
15
+ export declare const of: (store: Store, match: Match) => t;
16
+ /** Decode a persisted query-match record and every nested claim record. */
17
+ export declare const decode: (input: string | unknown) => t;
18
+ export declare const encode: (record: t, space?: number) => string;
19
+ /** Stable library/JSON query surface; raw `query()` remains storage-oriented. */
20
+ export declare const queryRecords: (store: Store, input: string, options?: Options) => t[];
21
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/record.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAEnD,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAGlD,eAAO,MAAM,MAAM,EAAG,kBAA2B,CAAA;AACjD,eAAO,MAAM,OAAO,EAAG,CAAU,CAAA;AAEjC,MAAM,MAAM,CAAC,GAAG;IACd,QAAQ,CAAC,MAAM,EAAE,OAAO,MAAM,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,OAAO,CAAA;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,CAAA;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,WAAW,CAAC,CAAC,EAAE,CAAA;IAC3C,QAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;CAC1B,CAAA;AAED,eAAO,MAAM,EAAE,GAAI,OAAO,KAAK,EAAE,OAAO,KAAK,KAAG,CAO9C,CAAA;AAKF,2EAA2E;AAC3E,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,GAAG,OAAO,KAAG,CAqBhD,CAAA;AAED,eAAO,MAAM,MAAM,GAAI,QAAQ,CAAC,EAAE,QAAQ,MAAM,KAAG,MACT,CAAA;AAE1C,iFAAiF;AACjF,eAAO,MAAM,YAAY,GACvB,OAAO,KAAK,EACZ,OAAO,MAAM,EACb,UAAS,OAAY,KACpB,CAAC,EAAiE,CAAA"}
@@ -0,0 +1,41 @@
1
+ /** Versioned JSON projection of CAVE-Q matches. */
2
+ import { Record as ClaimRecord } from '@cavelang/store/adapter';
3
+ import { query } from "./bounded.js";
4
+ export const format = 'cave.query-match';
5
+ export const version = 1;
6
+ export const of = (store, match) => ({
7
+ format,
8
+ version,
9
+ bindings: match.bindings,
10
+ ...(match.row === undefined ? {} : { claim: store.recordOf(match.row) }),
11
+ ...(match.rows === undefined ? {} : { support: match.rows.map(store.recordOf) }),
12
+ ...(match.at === undefined ? {} : { at: match.at }),
13
+ });
14
+ const object = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
15
+ /** Decode a persisted query-match record and every nested claim record. */
16
+ export const decode = (input) => {
17
+ const value = typeof input === 'string' ? JSON.parse(input) : input;
18
+ if (!object(value) || value['format'] !== format) {
19
+ throw new Error(`CAVE query record: expected format ${JSON.stringify(format)}`);
20
+ }
21
+ if (value['version'] !== version) {
22
+ throw new Error(`CAVE query record: unsupported ${format} version ${JSON.stringify(value['version'])}`);
23
+ }
24
+ if (!object(value['bindings']) ||
25
+ !Object.values(value['bindings']).every(binding => typeof binding === 'string') ||
26
+ (value['support'] !== undefined && !Array.isArray(value['support']))) {
27
+ throw new Error(`CAVE query record: malformed ${format}/v${version}`);
28
+ }
29
+ const decoded = value;
30
+ return {
31
+ ...decoded,
32
+ ...(decoded.claim === undefined ? {} : { claim: ClaimRecord.decode(decoded.claim) }),
33
+ ...(decoded.support === undefined ? {} : {
34
+ support: decoded.support.map(record => ClaimRecord.decode(record))
35
+ }),
36
+ };
37
+ };
38
+ export const encode = (record, space) => JSON.stringify(record, undefined, space);
39
+ /** Stable library/JSON query surface; raw `query()` remains storage-oriented. */
40
+ export const queryRecords = (store, input, options = {}) => query(store, input, options).map(match => of(store, match));
41
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/record.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "@cavelang/query",
3
- "version": "0.27.14",
3
+ "version": "0.29.0",
4
4
  "type": "module",
5
- "description": "CAVE-Q — graph-pattern queries (?x vars, wildcards, inverse verbs, transitive hops, WHERE filters) compiled to SQL.",
5
+ "description": "CAVE-Q — graph-pattern queries with variables, lifecycle and inverse verbs, transitive hops, and filters.",
6
+ "keywords": [
7
+ "cave",
8
+ "knowledge-graph",
9
+ "knowledge-representation",
10
+ "typescript"
11
+ ],
12
+ "homepage": "https://github.com/mirek/cave#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/mirek/cave/issues"
15
+ },
6
16
  "license": "CC0-1.0",
7
17
  "author": "Mirek Rusin (https://github.com/mirek)",
8
18
  "repository": {
@@ -20,26 +30,29 @@
20
30
  }
21
31
  },
22
32
  "files": [
33
+ "bench",
23
34
  "dist",
24
35
  "!dist/test",
25
36
  "!dist/tsconfig.tsbuildinfo",
26
37
  "src",
27
38
  "!src/**/*.test.ts",
28
39
  "README.md",
29
- "License.md"
40
+ "License.md",
41
+ "Authors.md"
30
42
  ],
31
43
  "publishConfig": {
32
44
  "access": "public"
33
45
  },
34
46
  "dependencies": {
35
- "@cavelang/canonical": "0.27.14",
36
- "@cavelang/parser": "0.27.14",
37
- "@cavelang/core": "0.27.14",
38
- "@cavelang/store": "0.27.14"
47
+ "@cavelang/canonical": "0.29.0",
48
+ "@cavelang/core": "0.29.0",
49
+ "@cavelang/parser": "0.29.0",
50
+ "@cavelang/store": "0.29.0"
39
51
  },
40
52
  "scripts": {
53
+ "bench:transitive": "node --disable-warning=ExperimentalWarning bench/transitive.ts",
41
54
  "build": "tsc -b",
42
- "test": "node --disable-warning=ExperimentalWarning --test 'test/*.test.ts'",
55
+ "test": "node --disable-warning=ExperimentalWarning --test \"test/*.test.ts\"",
43
56
  "typecheck": "tsc -b"
44
57
  }
45
58
  }
package/src/bounded.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { Value } from '@cavelang/core'
2
- import type { Store } from '@cavelang/store'
2
+ import type { Store } from '@cavelang/store/adapter'
3
3
  import * as Compile from './compile.ts'
4
4
  import * as Pattern from './pattern.ts'
5
5
 
6
6
  export type { Match, Options } from './compile.ts'
7
+ export type { Window } from './compile.ts'
7
8
 
8
9
  /**
9
10
  * Compile historical queries against vocabulary reconstructed at the same
@@ -25,17 +26,17 @@ const storeAtBoundary = (store: Store, options: Compile.Options): Store => {
25
26
  * value filter pushes the normalized comparison into SQL; the final check
26
27
  * preserves exact unit and approximation semantics.
27
28
  */
28
- export const match = (
29
+ const execute = (
29
30
  store: Store,
30
31
  pattern: Pattern.t,
31
32
  options: Compile.Options = {}
32
- ): Compile.Match[] => {
33
+ ): Compile.Window => {
33
34
  if (pattern.payload.kind !== 'attribute' || pattern.payload.value.kind !== 'term') {
34
- return Compile.match(storeAtBoundary(store, options), pattern, options)
35
+ return Compile.window(storeAtBoundary(store, options), pattern, options)
35
36
  }
36
37
  const value = Value.parse(pattern.payload.value.text)
37
38
  if (value.kind !== 'number' || value.num === undefined) {
38
- return Compile.match(storeAtBoundary(store, options), pattern, options)
39
+ return Compile.window(storeAtBoundary(store, options), pattern, options)
39
40
  }
40
41
  const expectedNum = value.num
41
42
  const filter: Pattern.Filter = {
@@ -53,16 +54,28 @@ export const match = (
53
54
  }
54
55
  const expectedUnit = value.unit ?? null
55
56
  const expectedApprox = value.approx ? 1 : 0
56
- return Compile.match(storeAtBoundary(store, options), normalized, options)
57
- .filter(result => {
58
- const row = result.row
59
- return row !== undefined &&
60
- row.value_num === expectedNum &&
61
- row.value_unit === expectedUnit &&
62
- row.value_approx === expectedApprox
63
- })
57
+ const result = Compile.window(storeAtBoundary(store, options), normalized, options)
58
+ return { ...result, matches: result.matches.filter(match => {
59
+ const row = match.row
60
+ return row !== undefined &&
61
+ row.value_num === expectedNum &&
62
+ row.value_unit === expectedUnit &&
63
+ row.value_approx === expectedApprox
64
+ }) }
64
65
  }
65
66
 
67
+ export const window = (
68
+ store: Store,
69
+ pattern: Pattern.t,
70
+ options: Compile.Options = {}
71
+ ): Compile.Window => execute(store, pattern, options)
72
+
73
+ export const match = (
74
+ store: Store,
75
+ pattern: Pattern.t,
76
+ options: Compile.Options = {}
77
+ ): Compile.Match[] => execute(store, pattern, options).matches
78
+
66
79
  export const query = (
67
80
  store: Store,
68
81
  input: string,
package/src/compile.ts CHANGED
@@ -14,16 +14,22 @@
14
14
  * on the object side.
15
15
  *
16
16
  * Transitive patterns (`terrier EXTENDS+ animal`) compile to a recursive
17
- * CTE over current, positive, non-retracted edges (depth-capped at 32).
17
+ * CTE over current, positive, non-retracted edges. Recursive state is the
18
+ * reachable endpoint pair itself, so SQL `UNION` deduplication terminates
19
+ * cycles without an arbitrary semantic depth cap.
18
20
  */
19
21
 
20
- import { Time, Uuidv7, Value } from '@cavelang/core'
22
+ import { Time, Value } from '@cavelang/core'
21
23
  import { Registry } from '@cavelang/canonical'
22
- import { Resolve, Row } from '@cavelang/store'
23
- import type { Store } from '@cavelang/store'
24
+ import { QuerySql, Resolve, Row } from '@cavelang/store/adapter'
25
+ import type { Store } from '@cavelang/store/adapter'
24
26
  import * as Pattern from './pattern.ts'
25
27
 
26
- /** One query solution: variable bindings plus the matched row (absent for transitive hops). */
28
+ /**
29
+ * One storage-oriented query solution. `row`/`rows` expose SQLite-shaped
30
+ * data for in-process reasoning engines; use `queryRecords()` for versioned
31
+ * public JSON and cross-process integrations.
32
+ */
27
33
  export type Match = {
28
34
  readonly bindings: Readonly<Record<string, string>>
29
35
  readonly row?: Row.t
@@ -43,6 +49,10 @@ export type Match = {
43
49
  }
44
50
 
45
51
  export type Options = {
52
+ /** Maximum rows SQLite may return. Prefer the snapshot-aware page API for continuations. */
53
+ readonly limit?: number
54
+ /** Internal SQL offset used by the snapshot-aware page API. */
55
+ readonly offset?: number
46
56
  /** Match all appended rows, not only current beliefs. */
47
57
  readonly all?: boolean
48
58
  /**
@@ -100,47 +110,44 @@ export type Options = {
100
110
  }
101
111
 
102
112
  /**
103
- * UUIDv7 interval `[lo, hi)` for a tx filter value: a bare date covers the
104
- * whole UTC day, a timestamp covers one second. Interval semantics keep
105
- * adjacent operators distinguishable (`<=` includes the boundary day that
106
- * `<` excludes) and make `WHERE tx = 2026-01-01` mean "recorded that day".
113
+ * UUIDv7 interval `[lo, hi)` for a tx filter value: a date-like value covers
114
+ * its whole UTC period and a timestamp covers one second. Interval semantics
115
+ * keep adjacent operators distinguishable (`<=` includes the boundary period
116
+ * that `<` excludes) and make `WHERE tx = 2026-01-01` mean "recorded that day".
107
117
  */
108
118
  const txBounds = (text: string, label = 'tx date'): { lo: string, hi: string } => {
109
- const hasTime = text.includes('T')
110
- const start = Date.parse(hasTime ? text : `${text}T00:00:00Z`)
111
- if (Number.isNaN(start)) {
119
+ const bounds = QuerySql.transactionBounds(text)
120
+ if (bounds === undefined) {
112
121
  throw new Error(`CAVE-Q: cannot parse ${label} ${JSON.stringify(text)}`)
113
122
  }
114
- const end = start + (hasTime ? 1_000 : 86_400_000)
115
- return { lo: Uuidv7.at(start, 0, new Uint8Array(8)), hi: Uuidv7.at(end, 0, new Uint8Array(8)) }
123
+ return bounds
116
124
  }
117
125
 
118
126
  /**
119
127
  * SQL tx condition for an as-of boundary (spec §12.3). A UUIDv7 names an
120
- * exact transaction, included — belief as of that append; a date or
121
- * timestamp is inclusive of the whole named day/second, the same interval
128
+ * exact transaction, included — belief as of that append; a date-like value
129
+ * or timestamp is inclusive of the whole named period/second, the same interval
122
130
  * semantics as `WHERE tx <=` (§12.2). Both forms inline as literals: the
123
- * id is shape-validated hex-and-dashes and the interval bound comes from
124
- * `Uuidv7.at` — no free-form text reaches the SQL, and a literal keeps
131
+ * id is shape-validated hex-and-dashes and the interval bound is a generated
132
+ * UUIDv7 — no free-form text reaches the SQL, and a literal keeps
125
133
  * the fragment embeddable in CTEs that positional parameters would
126
134
  * complicate.
127
135
  */
128
- const asOfCondition = (asOf: string): string => {
129
- const id = asOf.toLowerCase()
130
- return Uuidv7.is(id) ? `tx <= '${id}'` : `tx < '${txBounds(asOf, 'as-of boundary').hi}'`
136
+ const asOfBoundary = (asOf: string): QuerySql.AsOfBoundary => {
137
+ const boundary = QuerySql.asOfBoundary(asOf)
138
+ if (boundary === undefined) {
139
+ throw new Error(`CAVE-Q: cannot parse as-of boundary ${JSON.stringify(asOf)}`)
140
+ }
141
+ return boundary
131
142
  }
132
143
 
133
144
  /** Row universe under resolution: every appended row, or only rows recorded up to the as-of boundary. */
134
145
  const claimsSql = (asOf: undefined | string): string =>
135
- asOf === undefined ? 'cave_claim' : `(SELECT * FROM cave_claim WHERE ${asOfCondition(asOf)})`
146
+ asOf === undefined ? QuerySql.claims() :
147
+ QuerySql.claims(asOfBoundary(asOf))
136
148
 
137
- const currentSql = (asOf: undefined | string): string => `
138
- SELECT c.* FROM cave_claim c
139
- JOIN (
140
- SELECT claim_key, MAX(tx) AS max_tx
141
- FROM ${claimsSql(asOf)} GROUP BY claim_key
142
- ) latest ON c.claim_key = latest.claim_key AND c.tx = latest.max_tx
143
- `
149
+ const currentSql = (asOf: undefined | string): string =>
150
+ QuerySql.current(claimsSql(asOf))
144
151
 
145
152
  /**
146
153
  * Row universe a pattern matches over: the full history under `all`,
@@ -173,23 +180,12 @@ const baseSql = (options: Options, policy: undefined | readonly Resolve.Entry[])
173
180
  * and the closure reconstructs entity resolution as believed then
174
181
  * (spec §12.3).
175
182
  */
176
- const aliasPairSql = (asOf: undefined | string): string => `alias_edge(a, b) AS (
177
- SELECT c.subject, c.object FROM (${currentSql(asOf)}) c
178
- WHERE c.verb = 'ALIAS' AND c.negated = 0 AND c.conf > 0 AND c.object IS NOT NULL
179
- AND ${Row.entityTermSql('c.subject')} AND ${Row.entityTermSql('c.object')}
180
- UNION
181
- SELECT c.object, c.subject FROM (${currentSql(asOf)}) c
182
- WHERE c.verb = 'ALIAS' AND c.negated = 0 AND c.conf > 0 AND c.object IS NOT NULL
183
- AND ${Row.entityTermSql('c.subject')} AND ${Row.entityTermSql('c.object')}
184
- ), alias_pair(a, b) AS (
185
- SELECT a, b FROM alias_edge
186
- UNION
187
- SELECT p.a, e.b FROM alias_pair p JOIN alias_edge e ON e.a = p.b
188
- )`
183
+ const aliasPairSql = (asOf: undefined | string): string =>
184
+ QuerySql.aliasPairs(currentSql(asOf))
189
185
 
190
186
  /** SQL for "these two expressions name the same entity" under the closure. */
191
187
  const aliasSame = (left: string, right: string): string =>
192
- `(${left} = ${right} OR EXISTS (SELECT 1 FROM alias_pair p WHERE p.a = ${left} AND p.b = ${right}))`
188
+ QuerySql.aliasSame(left, right)
193
189
 
194
190
  type Compiled = {
195
191
  readonly sql: string
@@ -200,7 +196,20 @@ type Compiled = {
200
196
  readonly valueVars: readonly string[]
201
197
  }
202
198
 
203
- const compile = (pattern: Pattern.t, registry: Registry.t, options: Options, policy?: readonly Resolve.Entry[]): Compiled => {
199
+ const bounded = (compiled: Compiled, options: Options): Compiled => {
200
+ if (options.limit === undefined) return compiled
201
+ if (!Number.isInteger(options.limit) || options.limit < 1 ||
202
+ !Number.isInteger(options.offset ?? 0) || (options.offset ?? 0) < 0) {
203
+ throw new Error('CAVE-Q: limit must be positive and offset must be non-negative')
204
+ }
205
+ return {
206
+ ...compiled,
207
+ sql: `${compiled.sql}\nLIMIT ? OFFSET ?`,
208
+ params: [...compiled.params, options.limit, options.offset ?? 0]
209
+ }
210
+ }
211
+
212
+ export const compile = (pattern: Pattern.t, registry: Registry.t, options: Options, policy?: readonly Resolve.Entry[]): Compiled => {
204
213
  // Inverse resolution (spec §12.1): swap the pattern's endpoint slots and
205
214
  // query the primary verb.
206
215
  let verb = pattern.verb
@@ -217,11 +226,15 @@ const compile = (pattern: Pattern.t, registry: Registry.t, options: Options, pol
217
226
  objectSlot = subjectSlot
218
227
  subjectSlot = swapped
219
228
  verb = { kind: 'verb', name: primary, transitive: verb.transitive }
229
+ } else {
230
+ // A lifecycle alias keeps direction but resolves to the stable
231
+ // storage spelling before SQL is compiled (spec §5.8).
232
+ verb = { kind: 'verb', name: primary, transitive: verb.transitive }
220
233
  }
221
234
  }
222
235
 
223
236
  if (verb.kind === 'verb' && verb.transitive) {
224
- return compileTransitive(pattern, verb.name, subjectSlot, objectSlot, options, policy)
237
+ return bounded(compileTransitive(pattern, verb.name, subjectSlot, objectSlot, options, policy), options)
225
238
  }
226
239
 
227
240
  const aliases = options.aliases === true
@@ -390,7 +403,7 @@ const compile = (pattern: Pattern.t, registry: Registry.t, options: Options, pol
390
403
  const valueVars = [...varColumns]
391
404
  .filter(([, columns]) => columns[0] === 'value_text')
392
405
  .map(([name]) => name)
393
- return { sql, params, bind, transitive: false, valueVars }
406
+ return bounded({ sql, params, bind, transitive: false, valueVars }, options)
394
407
  }
395
408
 
396
409
  const compileTransitive = (
@@ -412,19 +425,23 @@ const compileTransitive = (
412
425
  aliases ? aliasSame(left, right) : `${left} = ${right}`
413
426
  const conditions: string[] = []
414
427
  const params: (string | number)[] = [verb]
428
+ const pushTerm = (term: string): void => {
429
+ params.push(term)
430
+ if (aliases) params.push(term)
431
+ }
432
+ // A concrete source gives forward recursion its smallest seed. If only the
433
+ // destination is concrete, recurse backwards over the object index. Both
434
+ // forms retain normal (src, dst) orientation in their output.
435
+ const direction = subjectSlot.kind === 'term' ? 'forward' :
436
+ objectSlot?.kind === 'term' ? 'reverse' : 'all-pairs'
415
437
  if (subjectSlot.kind === 'term') {
416
- conditions.push(same('h.src', '?'))
417
- params.push(subjectSlot.text)
418
- if (aliases) {
419
- params.push(subjectSlot.text)
420
- }
438
+ pushTerm(subjectSlot.text)
439
+ } else if (objectSlot?.kind === 'term') {
440
+ pushTerm(objectSlot.text)
421
441
  }
422
- if (objectSlot?.kind === 'term') {
442
+ if (objectSlot?.kind === 'term' && direction !== 'reverse') {
423
443
  conditions.push(same('h.dst', '?'))
424
- params.push(objectSlot.text)
425
- if (aliases) {
426
- params.push(objectSlot.text)
427
- }
444
+ pushTerm(objectSlot.text)
428
445
  }
429
446
  // A repeated variable forces equality here just as in single-hop
430
447
  // patterns: `?x EXTENDS+ ?x` asks for nodes on a cycle, not for every
@@ -433,38 +450,74 @@ const compileTransitive = (
433
450
  conditions.push(same('h.src', 'h.dst'))
434
451
  }
435
452
  const withRecursive = `WITH RECURSIVE ${aliases ? `${aliasPairSql(options.asOf)}, ` : ''}`
436
- const hopsSql = `hops(src, dst, depth) AS (
437
- SELECT src, dst, 1 FROM cur
453
+ // Reachable pairs are the complete recursive state. UNION (not UNION ALL)
454
+ // deduplicates each pair before it can be expanded again, so a finite graph
455
+ // reaches a fixed point even with cycles and no hop-depth cutoff.
456
+ const seedSql = direction === 'forward' ?
457
+ `SELECT src, dst FROM cur WHERE ${same('src', '?')}` :
458
+ direction === 'reverse' ?
459
+ `SELECT src, dst FROM cur WHERE ${same('dst', '?')}` :
460
+ 'SELECT src, dst FROM cur'
461
+ const recursiveSql = direction === 'reverse' ?
462
+ `SELECT cur.src, h.dst FROM hops h JOIN cur ON ${same('cur.dst', 'h.src')}` :
463
+ `SELECT h.src, cur.dst FROM hops h JOIN cur ON ${same('cur.src', 'h.dst')}`
464
+ const hopsSql = `hops(src, dst) AS (
465
+ ${seedSql}
466
+ UNION
467
+ ${recursiveSql}
468
+ )`
469
+ // Support (an opt-in): propagate each edge id with every reachable pair it
470
+ // supports. The triple is finite and UNION-deduplicated like `hops`, while
471
+ // preserving forward/reverse seeding instead of rebuilding all-pairs
472
+ // reachability merely to recover path evidence.
473
+ const supportAddedSql = direction === 'reverse' ?
474
+ `SELECT cur.src, h.dst, cur.edge_id FROM hops h JOIN cur ON ${same('cur.dst', 'h.src')}` :
475
+ `SELECT h.src, cur.dst, cur.edge_id FROM hops h JOIN cur ON ${same('cur.src', 'h.dst')}`
476
+ const supportRecursiveSql = direction === 'reverse' ?
477
+ `SELECT cur.src, s.dst, s.edge_id FROM support s JOIN cur ON ${same('cur.dst', 's.src')}` :
478
+ `SELECT s.src, cur.dst, s.edge_id FROM support s JOIN cur ON ${same('cur.src', 's.dst')}`
479
+ const supportSql = `support(src, dst, edge_id) AS (
480
+ SELECT h.src, h.dst, cur.edge_id FROM hops h JOIN cur
481
+ ON ${same('cur.src', 'h.src')} AND ${same('cur.dst', 'h.dst')}
482
+ UNION
483
+ ${supportAddedSql}
438
484
  UNION
439
- SELECT h.src, cur.dst, h.depth + 1 FROM hops h JOIN cur ON ${same('cur.src', 'h.dst')}
440
- WHERE h.depth < 32
485
+ ${supportRecursiveSql}
441
486
  )`
442
- // Support (an opt-in): an edge row backs a pair when its endpoints sit
443
- // on some path between them reflexive or through the same hop
444
- // closure, so alias-equal endpoints connect exactly as matching did.
487
+ const aliasedSelect = (() => {
488
+ if (!aliases) return 'SELECT DISTINCT h.src AS src, h.dst AS dst FROM hops h'
489
+ // Alias widening can produce several physical endpoint pairs with the
490
+ // same visible bindings. Project unbound slots to constants before
491
+ // DISTINCT so SQL-level pagination sees the same solutions match()
492
+ // exposes, rather than duplicate pairs that only collapse afterwards.
493
+ const repeated = subjectSlot.kind === 'var' && objectSlot?.kind === 'var' &&
494
+ subjectSlot.name === objectSlot.name
495
+ const src = subjectSlot.kind === 'var' ? repeated ? 'h.dst' : 'h.src' : "''"
496
+ const dst = objectSlot?.kind === 'var' ? 'h.dst' : "''"
497
+ return `SELECT DISTINCT ${src} AS src, ${dst} AS dst FROM hops h`
498
+ })()
445
499
  const sql = options.support === true ? `
446
500
  ${withRecursive}edge AS (
447
501
  SELECT c.* FROM (${base}) c
448
502
  WHERE c.verb = ? AND c.negated = 0 AND c.conf > 0 AND c.object IS NOT NULL
449
503
  ), cur AS (
450
- SELECT DISTINCT subject AS src, object AS dst FROM edge
451
- ), ${hopsSql}, pair(src, dst) AS (
504
+ SELECT subject AS src, object AS dst, id AS edge_id FROM edge
505
+ ), ${hopsSql}, ${supportSql}, pair(src, dst) AS (
452
506
  SELECT DISTINCT h.src, h.dst FROM hops h
453
507
  ${conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''}
454
508
  )
455
509
  SELECT p.src AS src, p.dst AS dst, e.*
456
- FROM pair p JOIN edge e
457
- ON (${same('e.subject', 'p.src')} OR EXISTS (SELECT 1 FROM hops f WHERE ${same('f.src', 'p.src')} AND ${same('f.dst', 'e.subject')}))
458
- AND (${same('e.object', 'p.dst')} OR EXISTS (SELECT 1 FROM hops b WHERE ${same('b.src', 'e.object')} AND ${same('b.dst', 'p.dst')}))
510
+ FROM pair p JOIN support s ON ${same('s.src', 'p.src')} AND ${same('s.dst', 'p.dst')}
511
+ JOIN edge e ON e.id = s.edge_id
459
512
  ORDER BY p.src, p.dst, e.tx` : `
460
513
  ${withRecursive}cur AS (
461
514
  SELECT c.subject AS src, c.object AS dst
462
515
  FROM (${base}) c
463
516
  WHERE c.verb = ? AND c.negated = 0 AND c.conf > 0 AND c.object IS NOT NULL
464
517
  ), ${hopsSql}
465
- SELECT DISTINCT h.src AS src, h.dst AS dst FROM hops h
518
+ ${aliasedSelect}
466
519
  ${conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''}
467
- ORDER BY h.src, h.dst`
520
+ ORDER BY src, dst`
468
521
  const bind = (row: Record<string, unknown>): Record<string, string> => {
469
522
  const bindings: Record<string, string> = {}
470
523
  if (subjectSlot.kind === 'var') {
@@ -528,7 +581,14 @@ const evaluateAt = (
528
581
  * (the §24 rules engine substitutes bindings into premise patterns
529
582
  * between joins) rather than as text.
530
583
  */
531
- export const match = (store: Store, pattern: Pattern.t, options: Options = {}): Match[] => {
584
+ export type Window = {
585
+ /** Matches remaining after any valid-time evaluation. */
586
+ readonly matches: Match[]
587
+ /** Rows returned by SQLite before valid-time evaluation. */
588
+ readonly scanned: number
589
+ }
590
+
591
+ const run = (store: Store, pattern: Pattern.t, options: Options = {}): Window => {
532
592
  const instant = options.at === undefined ? undefined : Time.parseInstant(options.at)
533
593
  if (options.at !== undefined && instant === undefined) {
534
594
  throw new Error(
@@ -564,7 +624,10 @@ export const match = (store: Store, pattern: Pattern.t, options: Options = {}):
564
624
  entry.rows.push(row as unknown as Row.t)
565
625
  }
566
626
  }
567
- return [...matches.values()].map(entry => ({ bindings: entry.bindings, rows: entry.rows }))
627
+ return {
628
+ scanned: rows.length,
629
+ matches: [...matches.values()].map(entry => ({ bindings: entry.bindings, rows: entry.rows }))
630
+ }
568
631
  }
569
632
  if (compiled.transitive && options.aliases === true) {
570
633
  // Distinct (src, dst) pairs can repeat a binding set when an endpoint
@@ -572,7 +635,7 @@ export const match = (store: Store, pattern: Pattern.t, options: Options = {}):
572
635
  // transitive match carries no row, so identical bindings are identical
573
636
  // answers.
574
637
  const seen = new Set<string>()
575
- return rows.flatMap(row => {
638
+ return { scanned: rows.length, matches: rows.flatMap(row => {
576
639
  const bindings = compiled.bind(row)
577
640
  const key = JSON.stringify(bindings)
578
641
  if (seen.has(key)) {
@@ -580,18 +643,21 @@ export const match = (store: Store, pattern: Pattern.t, options: Options = {}):
580
643
  }
581
644
  seen.add(key)
582
645
  return [{ bindings }]
583
- })
646
+ }) }
584
647
  }
585
648
  if (compiled.transitive) {
586
- return rows.map(row => ({ bindings: compiled.bind(row) }))
649
+ return { scanned: rows.length, matches: rows.map(row => ({ bindings: compiled.bind(row) })) }
587
650
  }
588
651
  if (instant === undefined) {
589
- return rows.map(row => ({ bindings: compiled.bind(row), row: row as unknown as Row.t }))
652
+ return {
653
+ scanned: rows.length,
654
+ matches: rows.map(row => ({ bindings: compiled.bind(row), row: row as unknown as Row.t }))
655
+ }
590
656
  }
591
657
  // Valid-time pass (spec §32.4): contexts are read per row — coverage
592
658
  // needs period interpretation no SQL expression can do.
593
659
  const contextsOf = store.db.prepare('SELECT context FROM cave_context WHERE claim_id = ?')
594
- return rows.flatMap(row => {
660
+ return { scanned: rows.length, matches: rows.flatMap(row => {
595
661
  const contexts = (contextsOf.all(String(row['id'])) as { context: string }[])
596
662
  .map(entry => entry.context)
597
663
  if (!Time.appliesAt(contexts, instant)) {
@@ -606,5 +672,12 @@ export const match = (store: Store, pattern: Pattern.t, options: Options = {}):
606
672
  bindings[name] = at.text
607
673
  }
608
674
  return [{ bindings, row: row as unknown as Row.t, at }]
609
- })
675
+ }) }
610
676
  }
677
+
678
+ /** Execute one SQL window and report how many pre-filter rows it consumed. */
679
+ export const window = (store: Store, pattern: Pattern.t, options: Options = {}): Window =>
680
+ run(store, pattern, options)
681
+
682
+ export const match = (store: Store, pattern: Pattern.t, options: Options = {}): Match[] =>
683
+ run(store, pattern, options).matches
package/src/index.ts CHANGED
@@ -16,3 +16,7 @@
16
16
  export * as Pattern from './pattern.ts'
17
17
  export { match, query } from './bounded.ts'
18
18
  export type { Match, Options } from './bounded.ts'
19
+ export { queryRecords } from './record.ts'
20
+ export * as Record from './record.ts'
21
+ export { defaultLimit, maxLimit, page } from './page.ts'
22
+ export type { Page, PageOptions } from './page.ts'