@hviana/sema 0.1.0 → 0.1.1

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 (131) hide show
  1. package/dist/example/demo.d.ts +1 -0
  2. package/dist/example/demo.js +39 -0
  3. package/dist/example/train_base.d.ts +87 -0
  4. package/dist/example/train_base.js +2216 -0
  5. package/dist/src/alphabet.d.ts +7 -0
  6. package/dist/src/alphabet.js +33 -0
  7. package/dist/src/alu/src/alu.d.ts +185 -0
  8. package/dist/src/alu/src/alu.js +440 -0
  9. package/dist/src/alu/src/expr.d.ts +61 -0
  10. package/dist/src/alu/src/expr.js +318 -0
  11. package/dist/src/alu/src/index.d.ts +11 -0
  12. package/dist/src/alu/src/index.js +19 -0
  13. package/dist/src/alu/src/kernel-arith.d.ts +16 -0
  14. package/dist/src/alu/src/kernel-arith.js +264 -0
  15. package/dist/src/alu/src/kernel-bits.d.ts +19 -0
  16. package/dist/src/alu/src/kernel-bits.js +152 -0
  17. package/dist/src/alu/src/kernel-logic.d.ts +4 -0
  18. package/dist/src/alu/src/kernel-logic.js +60 -0
  19. package/dist/src/alu/src/kernel-nd.d.ts +3 -0
  20. package/dist/src/alu/src/kernel-nd.js +208 -0
  21. package/dist/src/alu/src/kernel-numeric.d.ts +54 -0
  22. package/dist/src/alu/src/kernel-numeric.js +366 -0
  23. package/dist/src/alu/src/operation.d.ts +168 -0
  24. package/dist/src/alu/src/operation.js +189 -0
  25. package/dist/src/alu/src/parser.d.ts +212 -0
  26. package/dist/src/alu/src/parser.js +469 -0
  27. package/dist/src/alu/src/resonance.d.ts +55 -0
  28. package/dist/src/alu/src/resonance.js +126 -0
  29. package/dist/src/alu/src/text.d.ts +31 -0
  30. package/dist/src/alu/src/text.js +73 -0
  31. package/dist/src/alu/src/value.d.ts +109 -0
  32. package/dist/src/alu/src/value.js +300 -0
  33. package/dist/src/alu/test/alu.test.d.ts +1 -0
  34. package/dist/src/alu/test/alu.test.js +764 -0
  35. package/dist/src/bytes.d.ts +14 -0
  36. package/dist/src/bytes.js +59 -0
  37. package/dist/src/config.d.ts +114 -0
  38. package/dist/src/config.js +96 -0
  39. package/dist/src/derive/src/deduction.d.ts +125 -0
  40. package/dist/src/derive/src/deduction.js +155 -0
  41. package/dist/src/derive/src/index.d.ts +7 -0
  42. package/dist/src/derive/src/index.js +11 -0
  43. package/dist/src/derive/src/priority-queue.d.ts +20 -0
  44. package/dist/src/derive/src/priority-queue.js +73 -0
  45. package/dist/src/derive/src/rewrite.d.ts +56 -0
  46. package/dist/src/derive/src/rewrite.js +100 -0
  47. package/dist/src/derive/src/trie.d.ts +90 -0
  48. package/dist/src/derive/src/trie.js +217 -0
  49. package/dist/src/derive/test/derive.test.d.ts +1 -0
  50. package/dist/src/derive/test/derive.test.js +122 -0
  51. package/dist/src/extension.d.ts +37 -0
  52. package/dist/src/extension.js +7 -0
  53. package/dist/src/geometry.d.ts +137 -0
  54. package/dist/src/geometry.js +430 -0
  55. package/dist/src/index.d.ts +15 -0
  56. package/dist/src/index.js +21 -0
  57. package/dist/src/ingest-cache.d.ts +41 -0
  58. package/dist/src/ingest-cache.js +161 -0
  59. package/dist/src/mind/articulation.d.ts +6 -0
  60. package/dist/src/mind/articulation.js +99 -0
  61. package/dist/src/mind/attention.d.ts +72 -0
  62. package/dist/src/mind/attention.js +894 -0
  63. package/dist/src/mind/canonical.d.ts +29 -0
  64. package/dist/src/mind/canonical.js +88 -0
  65. package/dist/src/mind/graph-search.d.ts +270 -0
  66. package/dist/src/mind/graph-search.js +847 -0
  67. package/dist/src/mind/index.d.ts +5 -0
  68. package/dist/src/mind/index.js +5 -0
  69. package/dist/src/mind/junction.d.ts +95 -0
  70. package/dist/src/mind/junction.js +262 -0
  71. package/dist/src/mind/learning.d.ts +47 -0
  72. package/dist/src/mind/learning.js +201 -0
  73. package/dist/src/mind/match.d.ts +111 -0
  74. package/dist/src/mind/match.js +422 -0
  75. package/dist/src/mind/mechanisms/alu.d.ts +4 -0
  76. package/dist/src/mind/mechanisms/alu.js +29 -0
  77. package/dist/src/mind/mechanisms/cast.d.ts +35 -0
  78. package/dist/src/mind/mechanisms/cast.js +447 -0
  79. package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
  80. package/dist/src/mind/mechanisms/confluence.js +213 -0
  81. package/dist/src/mind/mechanisms/cover.d.ts +6 -0
  82. package/dist/src/mind/mechanisms/cover.js +179 -0
  83. package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
  84. package/dist/src/mind/mechanisms/extraction.js +342 -0
  85. package/dist/src/mind/mechanisms/recall.d.ts +13 -0
  86. package/dist/src/mind/mechanisms/recall.js +151 -0
  87. package/dist/src/mind/mind.d.ts +147 -0
  88. package/dist/src/mind/mind.js +300 -0
  89. package/dist/src/mind/pipeline-mechanism.d.ts +142 -0
  90. package/dist/src/mind/pipeline-mechanism.js +213 -0
  91. package/dist/src/mind/pipeline.d.ts +20 -0
  92. package/dist/src/mind/pipeline.js +185 -0
  93. package/dist/src/mind/primitives.d.ts +43 -0
  94. package/dist/src/mind/primitives.js +162 -0
  95. package/dist/src/mind/rationale.d.ts +134 -0
  96. package/dist/src/mind/rationale.js +162 -0
  97. package/dist/src/mind/reasoning.d.ts +15 -0
  98. package/dist/src/mind/reasoning.js +162 -0
  99. package/dist/src/mind/recognition.d.ts +20 -0
  100. package/dist/src/mind/recognition.js +223 -0
  101. package/dist/src/mind/resonance.d.ts +23 -0
  102. package/dist/src/mind/resonance.js +0 -0
  103. package/dist/src/mind/trace.d.ts +15 -0
  104. package/dist/src/mind/trace.js +73 -0
  105. package/dist/src/mind/traverse.d.ts +100 -0
  106. package/dist/src/mind/traverse.js +447 -0
  107. package/dist/src/mind/types.d.ts +174 -0
  108. package/dist/src/mind/types.js +84 -0
  109. package/dist/src/rabitq-hnsw/src/database.d.ts +200 -0
  110. package/dist/src/rabitq-hnsw/src/database.js +388 -0
  111. package/dist/src/rabitq-hnsw/src/heap.d.ts +22 -0
  112. package/dist/src/rabitq-hnsw/src/heap.js +89 -0
  113. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +125 -0
  114. package/dist/src/rabitq-hnsw/src/hnsw.js +474 -0
  115. package/dist/src/rabitq-hnsw/src/index.d.ts +10 -0
  116. package/dist/src/rabitq-hnsw/src/index.js +6 -0
  117. package/dist/src/rabitq-hnsw/src/prng.d.ts +19 -0
  118. package/dist/src/rabitq-hnsw/src/prng.js +36 -0
  119. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +95 -0
  120. package/dist/src/rabitq-hnsw/src/rabitq.js +283 -0
  121. package/dist/src/rabitq-hnsw/src/store.d.ts +162 -0
  122. package/dist/src/rabitq-hnsw/src/store.js +825 -0
  123. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +1 -0
  124. package/dist/src/rabitq-hnsw/test/hnsw.test.js +948 -0
  125. package/dist/src/store-sqlite.d.ts +149 -0
  126. package/dist/src/store-sqlite.js +702 -0
  127. package/dist/src/store.d.ts +638 -0
  128. package/dist/src/store.js +1618 -0
  129. package/dist/src/vec.d.ts +31 -0
  130. package/dist/src/vec.js +109 -0
  131. package/package.json +1 -1
@@ -0,0 +1,174 @@
1
+ import type { Vec } from "../vec.js";
2
+ import type { Sema } from "../sema.js";
3
+ import type { BoundedMap, Store } from "../store.js";
4
+ import type { Space } from "../sema.js";
5
+ import type { Alphabet } from "../alphabet.js";
6
+ import type { MindConfig } from "../config.js";
7
+ import type { GraphSearch, Leaf, Seg, Site } from "./graph-search.js";
8
+ import type { Rationale } from "./rationale.js";
9
+ import type { FoldPyramid, Grid } from "../geometry.js";
10
+ export type Input = string | Uint8Array | Grid | Grid[];
11
+ /** The host capabilities GraphSearch consults during a cover. MindContext
12
+ * extends this so the Mind can pass itself as the host. */
13
+ export interface GraphSearchHost {
14
+ resolve(bytes: Uint8Array): number | null;
15
+ recogniseSpan?(bytes: Uint8Array): {
16
+ sites: ReadonlyArray<Site>;
17
+ leaves: ReadonlyArray<Leaf>;
18
+ splits: ReadonlySet<number>;
19
+ };
20
+ chooseNext?(node: number): number | undefined;
21
+ }
22
+ export interface Recognition {
23
+ /** Forms that can lead somewhere — they have an edge or a halo. */
24
+ sites: Site[];
25
+ /** The query's perceived leaves (the search's covering axioms). */
26
+ leaves: Leaf[];
27
+ /** Sub-leaf positions where a form boundary falls between leaf edges. */
28
+ splits: Set<number>;
29
+ }
30
+ /** How the consensus climb weights a region's Document-Frequency reach. */
31
+ export type DFMode = "inverse" | "direct" | "combined";
32
+ /** One POINT OF ATTENTION the consensus climb resolved. */
33
+ export interface Attention {
34
+ /** The learnt context this point resolves to. */
35
+ anchor: number;
36
+ /** IDF-weighted consensus vote — the strength that orders points. */
37
+ vote: number;
38
+ /** The union of the query byte-spans whose evidence supports this point. */
39
+ start: number;
40
+ end: number;
41
+ }
42
+ /** Both read-outs of one consensus climb. */
43
+ export interface AttentionRead {
44
+ roots: Attention[];
45
+ ranked: Attention[];
46
+ }
47
+ /** A positioned region of a byte stream paired with its gist. */
48
+ export interface Segment {
49
+ start: number;
50
+ end: number;
51
+ v: Vec;
52
+ }
53
+ /** A region of the query's perceived tree for the consensus climb. */
54
+ export interface Region {
55
+ v: Vec;
56
+ start: number;
57
+ end: number;
58
+ chunk: boolean;
59
+ /** Whether the region's bytes resolve to a KNOWN node (content-addressed,
60
+ * exact). Exact regions vote with full weight; approximate ones pay the
61
+ * contrastive margin (see voteRegions) — under the linear fold a raw
62
+ * resonance score is byte-overlap, evidence only in excess of its best
63
+ * rival conclusion. */
64
+ known: boolean;
65
+ }
66
+ /** Per-region vote data from the consensus climb's resonance pass. */
67
+ export interface RegionVote {
68
+ start: number;
69
+ end: number;
70
+ canonicalFailed: boolean;
71
+ roots: readonly number[];
72
+ w: number;
73
+ wFocus: number;
74
+ }
75
+ /** The edge-bearing contexts reached by climbing from a node, plus saturation info. */
76
+ export interface AncestorReach {
77
+ roots: number[];
78
+ contextsReached: number;
79
+ saturated: boolean;
80
+ }
81
+ /** Saturated-interval information for the noise-drop gate. */
82
+ export interface SaturationInfo {
83
+ leadingEnd: number;
84
+ hasLeading: boolean;
85
+ intervals: Array<{
86
+ start: number;
87
+ end: number;
88
+ }>;
89
+ }
90
+ /** The items of poolVotes' deduction system. */
91
+ export type AItem = {
92
+ kind: "region";
93
+ ri: number;
94
+ } | {
95
+ kind: "anchor";
96
+ id: number;
97
+ } | {
98
+ kind: "anchorFocus";
99
+ id: number;
100
+ };
101
+ export interface MindContext extends GraphSearchHost {
102
+ store: Store;
103
+ space: Space;
104
+ alphabet: Alphabet;
105
+ cfg: MindConfig;
106
+ search: GraphSearch;
107
+ trace: Rationale | null;
108
+ climbMemo: WeakMap<Uint8Array, Map<string, AttentionRead>> | null;
109
+ /** Per-response memo of {@link recognise} keyed by the byte-array OBJECT
110
+ * (think, articulate, and the post-grounding pre-consume all recognise the
111
+ * same query/answer objects). Valid because the store is read-only while
112
+ * a response is in flight; bypassed when a trace is attached so every
113
+ * recognise still emits its rationale step. Null outside respond(). */
114
+ recogniseMemo: WeakMap<Uint8Array, Recognition> | null;
115
+ /** Per-response memo of {@link perceive} keyed by the byte-array OBJECT —
116
+ * the GENERAL memo the result-level ones (recogniseMemo, climbMemo,
117
+ * _gistCache) each partially compensate for: resolve(), gistOf(), and
118
+ * every mechanism's re-perception of the same query/answer object hit it
119
+ * (a reason hop used to fold the same answer three times). Valid because
120
+ * the store is read-only while a response is in flight and perception is
121
+ * a pure function of bytes; only inference-shaped calls (plain Uint8Array,
122
+ * no leafAt/lookup capabilities) are memoised, so the deposit path never
123
+ * sees it. Keyed by CONTENT (latin1 of the bytes), not object identity —
124
+ * mechanisms materialise the same span in fresh subarrays constantly
125
+ * (measured on a trained store: 46% of one response's perceptions were
126
+ * byte-identical repeats an identity key missed). NOT bypassed under
127
+ * trace — perception emits no rationale steps, so there is nothing a memo
128
+ * hit could swallow. Null outside respond(). */
129
+ perceiveMemo: Map<string, Sema> | null;
130
+ _edgeGuide: Vec | null;
131
+ _edgeChoice: Map<number, number>;
132
+ _prevSeen: Set<number> | null;
133
+ /** Session cache of node-id → perceived gist, for candidate scoring
134
+ * ({@link chooseAmong} in the reverse projection's recall path re-gists up to
135
+ * √N contexts per pick — the measured bottleneck there). `chooseNext` does
136
+ * NOT use this cache; forward-edge disambiguation uses prevOf counts
137
+ * (distributional evidence) instead of gist comparison, because for short
138
+ * answer candidates the gist is dominated by accidental byte-pattern
139
+ * correlations. A node's bytes are immutable and perception is a pure
140
+ * function of bytes, so an entry stays valid for the store's lifetime —
141
+ * never invalidated. Bounded LRU (byte-sized); a miss only re-perceives,
142
+ * never a correctness risk. */
143
+ _gistCache: BoundedMap<number, Vec>;
144
+ /** DEPOSIT-path stable-prefix tree cache: content key (latin1) of a
145
+ * deposited input → its plain-fold level PYRAMID, so the NEXT deposit
146
+ * whose prefix it is (a conversation's accumulated context) reuses every
147
+ * full aligned block and refolds only the right edge of each level —
148
+ * O(turn) per deposit instead of O(context), with the tree BIT-IDENTICAL
149
+ * to a from-scratch fold (see FoldPyramid). Purely a performance cache.
150
+ * Entry-count bounded (a pyramid is ~KB/byte of content; only the few
151
+ * live conversation chains matter). */
152
+ _depositTrees: BoundedMap<string, FoldPyramid>;
153
+ /** The byte lengths present in {@link _depositTrees} — the candidate
154
+ * prefix lengths probed (longest first). Drifts on eviction (a stale
155
+ * length only costs a miss); cleared with the map when it outgrows the
156
+ * probe budget. */
157
+ _depositLens: Set<number>;
158
+ /** Mind-lifetime intern memo by NODE IDENTITY: perceived-tree node → its
159
+ * content-addressed id. Valid forever (ids are permanent, Sema nodes
160
+ * immutable); WeakMap, so entries live exactly as long as the pyramid
161
+ * cache keeps the shared subtrees alive. Lets internTreeIds skip whole
162
+ * shared subtrees and indexSubSpans keep its seenBefore window skip. */
163
+ _internIds: WeakMap<Sema, number>;
164
+ }
165
+ /** Read a whole node's bytes. */
166
+ export declare const ALL = 2147483647;
167
+ /** Splice every chosen span in order — the whole cover as one byte string. */
168
+ export declare function spliceAll(segs: Seg[]): Uint8Array | null;
169
+ /** Lift the answer out of the cover for think: the recognised region, free of
170
+ * the asker's surrounding (unrecognised) framing. */
171
+ export declare function liftAnswer(segs: Seg[], queryLen: number): Uint8Array | null;
172
+ /** The CHANGED NODES of a freshly-perceived `tree` against the node ids a previous
173
+ * tracked deposit interned (`prevSeen`). */
174
+ export declare function changedNodes(tree: Sema, ids: Map<Sema, number>, prevSeen: Set<number>): Sema[];
@@ -0,0 +1,84 @@
1
+ // types.ts — all interfaces, types, and free functions for the mind.
2
+ //
3
+ // GraphSearchHost is defined first (minimal imports) so GraphSearch can import
4
+ // it without pulling in the full MindContext.
5
+ import { concatBytes } from "../bytes.js";
6
+ import { dominates } from "../geometry.js";
7
+ // ═══════════════════════════════════════════════════════════════════════════
8
+ // FREE FUNCTIONS (pure, no state)
9
+ // ═══════════════════════════════════════════════════════════════════════════
10
+ /** Read a whole node's bytes. */
11
+ export const ALL = 0x7fffffff;
12
+ /** Splice every chosen span in order — the whole cover as one byte string. */
13
+ export function spliceAll(segs) {
14
+ if (!segs.some((s) => s.rec))
15
+ return null;
16
+ return concatBytes(segs.map((s) => s.bytes));
17
+ }
18
+ /** Lift the answer out of the cover for think: the recognised region, free of
19
+ * the asker's surrounding (unrecognised) framing. */
20
+ export function liftAnswer(segs, queryLen) {
21
+ const recognised = [];
22
+ for (let k = 0; k < segs.length; k++)
23
+ if (segs[k].rec)
24
+ recognised.push(k);
25
+ if (recognised.length === 0)
26
+ return null;
27
+ if (recognised.length === 1) {
28
+ const s = segs[recognised[0]];
29
+ if (dominates(s.j - s.i, queryLen)) {
30
+ return concatBytes(segs.map((x) => x.bytes));
31
+ }
32
+ return s.bytes;
33
+ }
34
+ const lo = recognised[0];
35
+ const hi = recognised[recognised.length - 1];
36
+ return concatBytes(segs.slice(lo, hi + 1).map((x) => x.bytes));
37
+ }
38
+ /** The CHANGED NODES of a freshly-perceived `tree` against the node ids a previous
39
+ * tracked deposit interned (`prevSeen`). */
40
+ export function changedNodes(tree, ids, prevSeen) {
41
+ const newCount = new Map();
42
+ const count = (n) => {
43
+ const memo = newCount.get(n);
44
+ if (memo !== undefined)
45
+ return memo;
46
+ const id = ids.get(n);
47
+ // PRUNE: a node whose id the previous deposit already interned is old,
48
+ // and content addressing makes that transitive — the same id names the
49
+ // same content, so every descendant was interned then too. The whole
50
+ // subtree counts 0 without walking it; with the pyramid fold sharing a
51
+ // conversation's prefix subtree, this is what keeps the changed-nodes
52
+ // read O(new nodes) instead of O(context). (A node internTreeIds
53
+ // memo-skipped has an id here exactly when it is such a shared root.)
54
+ if (id !== undefined && prevSeen.has(id)) {
55
+ newCount.set(n, 0);
56
+ return 0;
57
+ }
58
+ let c = 1; // reachable only when NOT pruned above ⇒ this node is new
59
+ if (n.kids) {
60
+ for (const k of n.kids)
61
+ c += count(k);
62
+ }
63
+ newCount.set(n, c);
64
+ return c;
65
+ };
66
+ const total = count(tree);
67
+ if (total === 0)
68
+ return [tree];
69
+ let n = tree;
70
+ for (;;) {
71
+ if (n.kids === null)
72
+ return [n];
73
+ let holder = null;
74
+ for (const k of n.kids) {
75
+ if (newCount.get(k) === total) {
76
+ holder = k;
77
+ break;
78
+ }
79
+ }
80
+ if (holder === null)
81
+ return [n];
82
+ n = holder;
83
+ }
84
+ }
@@ -0,0 +1,200 @@
1
+ /** External ids are integers only. */
2
+ export type ExternalId = number;
3
+ export interface DatabaseOptions {
4
+ /** Path to the SQLite database file (use ":memory:" for a transient store). */
5
+ dbPath: string;
6
+ /** Vector dimensionality. Required for a new database; ignored when reopening. */
7
+ dim?: number;
8
+ /** Max neighbours per node on layers >= 1 (layer 0 uses 2*M). Default 16. */
9
+ M?: number;
10
+ /** Candidate list size during construction. Default 200. */
11
+ efConstruction?: number;
12
+ /** Candidate list size during search. Default 100. Tunable at runtime. */
13
+ efSearch?: number;
14
+ /** Bits used to scalar-quantise the query for the RaBitQ estimator.
15
+ * Query-side only (stored codes and the graph are independent of it), so it
16
+ * is tunable at reopen, like efSearch. Default 8: 4 bits cannot resolve a
17
+ * tight cluster's residual and costs ~18% self-recall there (test 2a); 8
18
+ * bits restores it for the price of a Uint16 per-query LUT. */
19
+ queryBits?: number;
20
+ /** Number of sign-flip + Hadamard rounds in the random rotation. Default 3. */
21
+ rotationRounds?: number;
22
+ /** Seed for the rotation and graph levels. Default fixed. */
23
+ seed?: number;
24
+ /** Optional centroid the vectors are centered by before quantisation. */
25
+ centroid?: ArrayLike<number>;
26
+ /**
27
+ * RAM cache size in MiB -- the single memory knob. It sizes SQLite's page
28
+ * cache AND the immutable-code LRU (whose entry-capacity is derived from this
29
+ * budget and the code size — there is no second knob). Both are purely speed
30
+ * enhancements: correctness and the per-operation storage-read count are
31
+ * identical with it off. Pass 0 to run with essentially no cache. Default 64.
32
+ * Not persisted; set per open.
33
+ */
34
+ cacheSizeMb?: number;
35
+ }
36
+ export interface QueryResult {
37
+ id: ExternalId;
38
+ /** Estimated cosine distance (1 - cosine). */
39
+ distance: number;
40
+ }
41
+ export interface StorageStats {
42
+ /** Bytes a single Float32 copy of the vector would take. */
43
+ float32BytesPerVector: number;
44
+ /** Bytes of 1-bit code kept per vector (on disk). */
45
+ codeBytesPerVector: number;
46
+ /** Bytes kept per vector (just the code; excludes the graph adjacency). */
47
+ bytesPerVector: number;
48
+ /** float32BytesPerVector / bytesPerVector. */
49
+ compressionRatio: number;
50
+ }
51
+ /**
52
+ * A persistent vector database: an HNSW graph over 1-bit RaBitQ codes (cosine),
53
+ * stored entirely in SQLite at `dbPath`. The graph IS the database -- there is no
54
+ * load/save step and no in-RAM copy of the data, so resident memory stays flat as
55
+ * the collection grows and the store survives process restarts. Reopening the
56
+ * same path restores the exact configuration (the rotation is regenerated from
57
+ * the persisted seed), so the codes already on disk remain valid.
58
+ *
59
+ * External ids are integers. The original float vectors are never retained --
60
+ * only the sign codes -- so a 256-d vector costs 32 bytes instead of 1024. `get`
61
+ * returns the stored code, which can be fed straight back into
62
+ * `insert`/`update`/`query`.
63
+ */
64
+ export declare class VectorDatabase {
65
+ readonly dim: number;
66
+ private readonly store;
67
+ private readonly quantizer;
68
+ private readonly index;
69
+ private readonly codeWords;
70
+ constructor(options: DatabaseOptions);
71
+ /** Number of live (non-deleted) vectors. */
72
+ get size(): number;
73
+ /**
74
+ * Physical node count including tombstones from deletes/updates. When it grows
75
+ * well beyond `size`, call `compact()` to reclaim the space on disk.
76
+ */
77
+ get physicalSize(): number;
78
+ get efSearch(): number;
79
+ set efSearch(value: number);
80
+ /** Distance computations performed during the most recent query. */
81
+ get lastQueryDistanceComputations(): number;
82
+ /**
83
+ * Storage row reads issued by the most recent query. This is the honest,
84
+ * cache-independent scalability metric: it counts every node/neighbour fetch
85
+ * that hit the database, so disabling the cache cannot hide a bad access pattern.
86
+ */
87
+ get lastQueryStorageReads(): number;
88
+ /** Per-vector storage cost of the index versus a Float32 baseline. */
89
+ get storage(): StorageStats;
90
+ has(id: ExternalId): boolean;
91
+ /** Stream every live external id (bounded memory). */
92
+ keys(): IterableIterator<ExternalId>;
93
+ /**
94
+ * Stream live entries whose INTERNAL id is > `after`, as
95
+ * {ext, internal} pairs in internal-id order. Internal ids are assigned
96
+ * monotonically at insert and preserved by {@link compact}, so the largest
97
+ * internal id a caller has seen is a durable incremental watermark: a later
98
+ * call with it yields exactly the entries added since.
99
+ */
100
+ keysSince(after: number): IterableIterator<{
101
+ ext: ExternalId;
102
+ internal: number;
103
+ }>;
104
+ private checkId;
105
+ /**
106
+ * Convert a value to code bytes, selecting by length:
107
+ * - `codeWords` elements -> an existing 1-bit code (e.g. from `get()`)
108
+ * - otherwise -> a raw `dim`-vector, encoded first
109
+ * `codeWords < dim` for any dim >= 2, so the two never collide.
110
+ */
111
+ private toCodeBytes;
112
+ /**
113
+ * Create. Accepts a raw `dim`-vector or a 1-bit code (`codeWords` words),
114
+ * detected by length. Throws if the id already exists (use `update`/`upsert`).
115
+ */
116
+ insert(id: ExternalId, value: ArrayLike<number>): void;
117
+ /** Insert with the caller owning the transaction (used by {@link upsertMany}).
118
+ * `ef` optionally narrows the construction beam for this one vector (a
119
+ * caller-declared cheap entry, e.g. a reach-only interior); omitted means
120
+ * the index's configured efConstruction. */
121
+ private insertCore;
122
+ upsert(id: ExternalId, value: ArrayLike<number>): void;
123
+ /**
124
+ * Upsert many vectors under ONE transaction. The HNSW build touches the store
125
+ * on every wired edge, so a transaction per vector is one WAL commit per vector
126
+ * — which dominates a bulk load on disk. Wrapping the whole batch in a single
127
+ * transaction coalesces those commits into one while leaving the graph and its
128
+ * result identical (reads on the connection still see the uncommitted rows).
129
+ *
130
+ * This is purely about commit batching; it changes nothing about the storage
131
+ * model. Codes still live only in SQLite and are read on demand — the
132
+ * cache-independent per-operation read count is unchanged — so a batched load
133
+ * scales exactly as the per-item path does, just with far fewer fsyncs. A throw
134
+ * rolls the whole batch back, so the caller treats it as the per-item path on
135
+ * failure.
136
+ */
137
+ upsertMany(entries: Array<{
138
+ id: ExternalId;
139
+ vector: ArrayLike<number>;
140
+ ef?: number;
141
+ }>): void;
142
+ /**
143
+ * Read the stored 1-bit code for an id (a copy as a Uint32Array), or null. The
144
+ * original vector is not retained; the code round-trips into insert/update/query.
145
+ */
146
+ get(id: ExternalId): Uint32Array | null;
147
+ /**
148
+ * Update the vector bound to an id (raw vector or code, detected by length).
149
+ * The previous node is tombstoned and a fresh one inserted. Throws if absent.
150
+ */
151
+ update(id: ExternalId, value: ArrayLike<number>): void;
152
+ /** Update with the caller owning the transaction (used by {@link upsertMany}). */
153
+ private updateCore;
154
+ /** Update when the live internal node id is already known.
155
+ *
156
+ * Skips the write entirely when the new code equals the stored one — the
157
+ * update would tombstone the node and replay a full graph insert for a
158
+ * byte-identical result. This is not a corner case: content-addressed
159
+ * callers re-upsert unchanged vectors wholesale after a restart (the same
160
+ * content always encodes to the same code), and each such no-op otherwise
161
+ * costs a tombstone (permanent routing/disk overhead until compaction)
162
+ * plus an O(ef·log N) reinsert. */
163
+ private updateAt;
164
+ /**
165
+ * Delete many ids under ONE transaction — same commit-coalescing rationale
166
+ * as {@link upsertMany}: a tombstone per implicit transaction is one WAL
167
+ * commit per id, which dominates a bulk prune. Absent ids are skipped.
168
+ * Returns the number of vectors actually removed.
169
+ */
170
+ deleteMany(ids: ExternalId[]): number;
171
+ /** Delete the vector bound to an id. Returns false if absent. */
172
+ delete(id: ExternalId): boolean;
173
+ /**
174
+ * Pre-fill the RAM caches (codes + neighbour lists) with sequential table
175
+ * scans, up to their budget-derived caps. Optional and purely a latency
176
+ * optimisation: a cold session otherwise pays the same warming through
177
+ * random point reads over its first minutes. Call once after open on a
178
+ * session that will do sustained inserts/queries. Returns rows warmed;
179
+ * a 0-budget database returns 0 immediately.
180
+ */
181
+ warmCache(): number;
182
+ /**
183
+ * Rebuild the graph from the live codes only, dropping tombstones and returning
184
+ * the freed pages to the filesystem. Lossless -- equivalent to the original
185
+ * build -- and needs no original vectors.
186
+ */
187
+ compact(): void;
188
+ /**
189
+ * k-NN search. The argument's length selects the mode:
190
+ * - `dim` elements -> a raw vector (accurate 4-bit-query estimator)
191
+ * - `codeWords` elements -> a 1-bit code (e.g. `get(id)`), by sign-bit Hamming
192
+ * Detection is by length, so a code is recognised whether it is a Uint32Array
193
+ * or a plain number[]. `codeWords < dim` for any dim >= 2, so they never collide.
194
+ */
195
+ query(query: ArrayLike<number>, k?: number, opts?: {
196
+ ef?: number;
197
+ }): QueryResult[];
198
+ /** Close the underlying database. The instance must not be used afterwards. */
199
+ close(): void;
200
+ }