@harperfast/harper 5.3.0 → 5.3.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.
- package/bin/copyDb.ts +5 -1
- package/bin/deployRenderer.ts +3 -0
- package/components/Application.ts +66 -22
- package/components/DESIGN.md +89 -36
- package/components/componentLoader.ts +2 -0
- package/components/deploymentRecorder.ts +18 -1
- package/components/installFingerprint.ts +123 -0
- package/components/mcp/DESIGN.md +4 -4
- package/components/operations.js +29 -0
- package/config/DESIGN.md +5 -5
- package/dataLayer/DESIGN.md +37 -22
- package/dataLayer/backupRepository.ts +248 -0
- package/dataLayer/restoreMarker.ts +30 -75
- package/dataLayer/rocksdbBackup.ts +253 -110
- package/dist/bin/copyDb.js +3 -0
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/deployRenderer.js +3 -0
- package/dist/bin/deployRenderer.js.map +1 -1
- package/dist/components/Application.d.ts +5 -0
- package/dist/components/Application.js +51 -19
- package/dist/components/Application.js.map +1 -1
- package/dist/components/componentLoader.js +2 -0
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deploymentRecorder.js +18 -1
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/installFingerprint.d.ts +39 -0
- package/dist/components/installFingerprint.js +113 -0
- package/dist/components/installFingerprint.js.map +1 -0
- package/dist/components/operations.d.ts +2 -0
- package/dist/components/operations.js +31 -0
- package/dist/components/operations.js.map +1 -1
- package/dist/dataLayer/backupRepository.d.ts +33 -0
- package/dist/dataLayer/backupRepository.js +232 -0
- package/dist/dataLayer/backupRepository.js.map +1 -0
- package/dist/dataLayer/restoreMarker.d.ts +4 -1
- package/dist/dataLayer/restoreMarker.js +29 -68
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/dataLayer/rocksdbBackup.js +221 -112
- package/dist/dataLayer/rocksdbBackup.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +14 -0
- package/dist/resources/DatabaseTransaction.d.ts +140 -11
- package/dist/resources/DatabaseTransaction.js +1055 -464
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/IterableEventQueue.d.ts +2 -1
- package/dist/resources/IterableEventQueue.js +16 -8
- package/dist/resources/IterableEventQueue.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +13 -2
- package/dist/resources/LMDBTransaction.js +219 -93
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/RecordEncoder.js +2 -3
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Resource.js +5 -6
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +3 -1
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/RocksIndexStore.d.ts +2 -1
- package/dist/resources/RocksIndexStore.js +3 -0
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/Table.js +116 -12
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +6 -2
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +71 -17
- package/dist/resources/auditStore.js +248 -64
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/branchDatabase.js +2 -0
- package/dist/resources/branchDatabase.js.map +1 -1
- package/dist/resources/databases.js +8 -2
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +6 -2
- package/dist/resources/indexes/fullTextNativeBinding.js +12 -2
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -1
- package/dist/resources/indexes/fullTextQueryIndex.d.ts +4 -0
- package/dist/resources/indexes/fullTextQueryIndex.js +207 -10
- package/dist/resources/indexes/fullTextQueryIndex.js.map +1 -1
- package/dist/resources/models/Models.d.ts +11 -0
- package/dist/resources/models/Models.js +95 -4
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +8 -0
- package/dist/resources/models/backendRegistry.js +15 -1
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +2 -0
- package/dist/resources/models/bootstrap.js +6 -0
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/models/calibration.d.ts +88 -0
- package/dist/resources/models/calibration.js +244 -0
- package/dist/resources/models/calibration.js.map +1 -0
- package/dist/resources/models/calibrationStore.d.ts +163 -0
- package/dist/resources/models/calibrationStore.js +986 -0
- package/dist/resources/models/calibrationStore.js.map +1 -0
- package/dist/resources/models/decisionStore.d.ts +24 -0
- package/dist/resources/models/decisionStore.js +45 -0
- package/dist/resources/models/decisionStore.js.map +1 -1
- package/dist/resources/models/generativeDecision.js +44 -15
- package/dist/resources/models/generativeDecision.js.map +1 -1
- package/dist/resources/models/types.d.ts +7 -0
- package/dist/resources/recordLock.d.ts +8 -5
- package/dist/resources/recordLock.js +6 -4
- package/dist/resources/recordLock.js.map +1 -1
- package/dist/resources/replayLogs.js +8 -1
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/scheduler/engine.d.ts +4 -0
- package/dist/resources/scheduler/engine.js +30 -2
- package/dist/resources/scheduler/engine.js.map +1 -1
- package/dist/resources/search.d.ts +16 -4
- package/dist/resources/search.js +117 -13
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/transaction.js +85 -5
- package/dist/resources/transaction.js.map +1 -1
- package/dist/resources/transactionBroadcast.d.ts +8 -0
- package/dist/resources/transactionBroadcast.js +65 -0
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/keys.d.ts +1 -1
- package/dist/security/keys.js +170 -120
- package/dist/security/keys.js.map +1 -1
- package/dist/server/http.js +10 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/serverHelpers/Request.js +11 -2
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +8 -3
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +38 -7
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +3 -3
- package/dist/server/threads/manageThreads.js +15 -13
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/threadServer.d.ts +3 -0
- package/dist/server/threads/threadServer.js +24 -6
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.js +9 -3
- package/dist/upgrade/directives/5-3-0.js.map +1 -1
- package/dist/utility/durableFile.d.ts +36 -0
- package/dist/utility/durableFile.js +106 -0
- package/dist/utility/durableFile.js.map +1 -0
- package/dist/utility/errors/hdbError.d.ts +4 -0
- package/dist/utility/errors/hdbError.js +10 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +1 -0
- package/dist/utility/hdbTerms.js +1 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/validation/configValidator.js +29 -0
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +7 -0
- package/json/systemSchema.json +14 -0
- package/npm-shrinkwrap.json +22 -22
- package/package.json +2 -2
- package/resources/DESIGN.md +57 -272
- package/resources/DatabaseTransaction.ts +708 -87
- package/resources/FULL_TEXT.md +6 -2
- package/resources/IterableEventQueue.ts +14 -7
- package/resources/LMDBTransaction.ts +212 -84
- package/resources/RecordEncoder.ts +2 -3
- package/resources/Resource.ts +5 -6
- package/resources/ResourceInterface.ts +3 -1
- package/resources/RocksIndexStore.ts +5 -0
- package/resources/Table.ts +118 -15
- package/resources/analytics/write.ts +6 -2
- package/resources/audit-retention.md +203 -0
- package/resources/auditStore.ts +306 -84
- package/resources/branchDatabase.ts +2 -0
- package/resources/databases.ts +6 -2
- package/resources/indexes/DESIGN.md +120 -177
- package/resources/indexes/fullTextNativeBinding.ts +22 -3
- package/resources/indexes/fullTextQueryIndex.ts +274 -11
- package/resources/models/DESIGN.md +11 -3
- package/resources/models/Models.ts +127 -4
- package/resources/models/backendRegistry.ts +17 -0
- package/resources/models/bootstrap.ts +9 -1
- package/resources/models/calibration.ts +298 -0
- package/resources/models/calibrationStore.ts +1167 -0
- package/resources/models/decisionStore.ts +57 -0
- package/resources/models/generativeDecision.ts +54 -16
- package/resources/models/types.ts +6 -0
- package/resources/record-locks.md +155 -141
- package/resources/recordLock.ts +12 -6
- package/resources/replayLogs.ts +9 -2
- package/resources/scheduler/DESIGN.md +4 -3
- package/resources/scheduler/engine.ts +35 -2
- package/resources/search.ts +154 -16
- package/resources/transaction.ts +50 -5
- package/resources/transactionBroadcast.ts +69 -0
- package/security/DESIGN.md +9 -30
- package/security/keys.ts +175 -127
- package/server/DESIGN.md +70 -36
- package/server/http.ts +10 -0
- package/server/serverHelpers/Request.ts +10 -2
- package/server/serverHelpers/contentTypes.ts +5 -3
- package/server/serverHelpers/uwsServer.ts +34 -7
- package/server/threads/manageThreads.js +16 -14
- package/server/threads/threadServer.js +24 -6
- package/studio/web/assets/{Chat-CNEKMllJ.js → Chat-4LMxI0yB.js} +1 -1
- package/studio/web/assets/{FloatingChat-CEnWhHeR.js → FloatingChat-kWHApEtF.js} +3 -3
- package/studio/web/assets/{SearchDialog-CwhJdEdY.js → SearchDialog-B7ymna97.js} +1 -1
- package/studio/web/assets/{apiToken-DC5vsaaO.js → apiToken-CE3AAt9f.js} +1 -1
- package/studio/web/assets/{applications-DQZxDy0x.js → applications-DdN7CqsB.js} +1 -1
- package/studio/web/assets/{cssMode-Dz2DxF4e.js → cssMode-BpySZUSt.js} +1 -1
- package/studio/web/assets/{editor-B3mjd3tz.js → editor-myLCVv9C.js} +1 -1
- package/studio/web/assets/{html-BsRGM7tp.js → html-DGNYO55O.js} +1 -1
- package/studio/web/assets/{htmlMode-tUKJaA3o.js → htmlMode-BUgpQr41.js} +1 -1
- package/studio/web/assets/{index-BuqFjN5K.js → index-FN4KC9va.js} +39 -39
- package/studio/web/assets/index-QLZw6xrU.css +1 -0
- package/studio/web/assets/{index.lazy-CcpfQFY_.js → index.lazy-DZFuaumG.js} +1 -1
- package/studio/web/assets/{javascript-BZ_uRGHl.js → javascript-DiBzh4pj.js} +1 -1
- package/studio/web/assets/{jsonMode-DgB-aFMj.js → jsonMode-Bm312nmu.js} +1 -1
- package/studio/web/assets/{languageServices-BleuBw17.js → languageServices-Dz2K9rZN.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-ByVYLSsQ.js → lspLanguageFeatures-CNu1L5xn.js} +1 -1
- package/studio/web/assets/{notifications-TuzN0IMi.js → notifications--nwFLGmM.js} +1 -1
- package/studio/web/assets/{notifications-D1Nf-Edn.js → notifications-BelKw5bR.js} +1 -1
- package/studio/web/assets/{profile-D7C7Lupz.js → profile-AU5FjOBx.js} +1 -1
- package/studio/web/assets/{regions-nGQCm-v8.js → regions-C0sTSEHH.js} +1 -1
- package/studio/web/assets/{register-C3x9Ze6P.js → register-DyNR2rGk.js} +2 -2
- package/studio/web/assets/{setComponentFile-DpjrRqeG.js → setComponentFile-BkWx8AN3.js} +1 -1
- package/studio/web/assets/{setup-CGiKLZ9e.js → setup-D6LZS78d.js} +2 -2
- package/studio/web/assets/{status-CGJwrNPH.js → status-BCWnn70o.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-C-Ne6P7T.js → toggleHighContrast-pyeGJ6W1.js} +1 -1
- package/studio/web/assets/{tsMode-CSreYTys.js → tsMode-CGV3iaWC.js} +1 -1
- package/studio/web/assets/{typescript-MnxBk89W.js → typescript-Ci7Khvwo.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-TNrh77-s.js → useEntityRestURL-DNiz3aZ0.js} +1 -1
- package/studio/web/assets/{workers-BfZlUNVI.js → workers-byzxjbqy.js} +1 -1
- package/studio/web/assets/{xml-BHd6qGH7.js → xml-DGnHQ3CX.js} +1 -1
- package/studio/web/assets/{yaml-C53R8OQq.js → yaml-BUoTnPWb.js} +1 -1
- package/studio/web/index.html +2 -2
- package/upgrade/directives/5-3-0.ts +10 -3
- package/utility/durableFile.ts +96 -0
- package/utility/errors/hdbError.ts +9 -0
- package/utility/hdbTerms.ts +1 -0
- package/validation/configValidator.ts +31 -0
- package/studio/web/assets/index-72cEH474.css +0 -1
|
@@ -13,15 +13,14 @@ Index of every design note: [DESIGN.md](../../DESIGN.md).
|
|
|
13
13
|
The ef auto-scale needs to know how big the graph is, on every query. Two sources that look right
|
|
14
14
|
are not.
|
|
15
15
|
|
|
16
|
-
`getKeysCount()` on a RocksDB store is an exact key scan, so it is O(N)
|
|
17
|
-
|
|
18
|
-
front of every vector search — 34% of query latency at 20K vectors on the real table stack.
|
|
16
|
+
`getKeysCount()` on a RocksDB store is an exact key scan, so it is O(N); calling it per query puts a
|
|
17
|
+
linear-in-corpus-size term in front of every vector search (measured in #2125).
|
|
19
18
|
|
|
20
19
|
RocksDB's `rocksdb.estimate-num-keys` property is O(1) and looks like the obvious replacement, but it
|
|
21
20
|
counts entries across memtable and SST files without reconciling overwrites. Building an HNSW graph
|
|
22
|
-
rewrites each node many times as its neighbours change, so on a real index it reads far high
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
rewrites each node many times as its neighbours change, so on a real index it reads far high (#2125),
|
|
22
|
+
and worse after deletes. It reads exact on a fresh store with simple puts, so it validates clean in
|
|
23
|
+
isolation and only misleads on a real index.
|
|
25
24
|
|
|
26
25
|
Node ids are the sound source. They are allocated monotonically from a `getUserSharedBuffer` counter,
|
|
27
26
|
so the counter (or one reverse seek to the largest id) gives the node count in O(1), unaffected by
|
|
@@ -38,24 +37,16 @@ reason, and any change between the two units has to move it to keep the resolved
|
|
|
38
37
|
|
|
39
38
|
Each layer above 0 exists to hand the next layer down an entry point: `search()` and `index()` both
|
|
40
39
|
take `results[0]` and discard the rest. Searching them at the full `ef` therefore buys nothing and
|
|
41
|
-
costs work proportional to the layer's population rather than to ef — layer 1 holds ~N/M nodes,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
(`
|
|
45
|
-
on every layer, across 16 (size, `ef`) points on a held-out real-embedding corpus, the worst
|
|
46
|
-
recall@10 change was -0.002 — one displaced neighbour at a single point — and 0.000 everywhere else.
|
|
40
|
+
costs work proportional to the layer's population rather than to ef — layer 1 holds ~N/M nodes, a
|
|
41
|
+
second linear-in-N term. Greedy descent (`ROUTING_EF`) is what standard HNSW does; against the same
|
|
42
|
+
graphs searched at the full `ef` on every layer it changed recall@10 at only one of 16 measured
|
|
43
|
+
(size, `ef`) points, by one displaced neighbour (#2125).
|
|
47
44
|
|
|
48
45
|
The connection-building pass in `index()` is not routing — it selects the edges that get stored — so
|
|
49
|
-
it keeps `efConstruction`.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
index-time descent). At 20,000 real 768-dim embeddings with identical corpus and level assignments,
|
|
54
|
-
the two builds were indistinguishable on every metric measured — same recall at each `ef`, same visit
|
|
55
|
-
counts, same mean layer-0 degree — and the greedy build was 1.28x faster. That is consistent with the
|
|
56
|
-
graphs being identical, though equal metrics do not prove it. It is the expected result either way:
|
|
57
|
-
the upper layers are sparse enough that a greedy walk reaches the same entry point, which is why
|
|
58
|
-
standard HNSW descends this way.
|
|
46
|
+
it keeps `efConstruction`. The insert-side descent does alter stored graphs, recoverable only by a
|
|
47
|
+
reindex; its A/B (`benchmarks/hnsw-scale.js --build-upper-ef=100` restores the previous index-time
|
|
48
|
+
descent) found the two builds indistinguishable on every measured metric (#2125), as expected: the
|
|
49
|
+
upper layers are sparse enough that a greedy walk reaches the same entry point.
|
|
59
50
|
|
|
60
51
|
Greedy-equals-full is statistical, not per-graph: rare level layouts route to a different layer-0
|
|
61
52
|
entry point and displace the tail of the top-k (~2-3% of random 600-node graphs in the unit test's
|
|
@@ -70,13 +61,12 @@ necessarily a routing regression.
|
|
|
70
61
|
|
|
71
62
|
The connection-building pass selects each node's stored edges from a candidate list of
|
|
72
63
|
`efConstruction` entries. Held at a constant (100) while the corpus grows, edge quality erodes in a
|
|
73
|
-
way no search-side setting can compensate: at 1M nodes
|
|
74
|
-
recall@10
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
(float32 rebuild: 0.952); construction quality was the dominant term. Full sweep in #2180.
|
|
64
|
+
way no search-side setting can compensate: at 1M nodes, sweeping the search `ef` from 512 to 1536
|
|
65
|
+
moved recall@10 only from 0.935 to 0.957 (0.967 set) at 4.7x the latency — the missing neighbours
|
|
66
|
+
were not deep in the candidate list, they were unreachable. Rebuilding the identical corpus with
|
|
67
|
+
`efConstruction` 200 restored recall and made queries _faster_ at the same `ef` (better-selected
|
|
68
|
+
edges route more directly); quantization was a minor term beside construction quality. Full sweep in
|
|
69
|
+
#2180.
|
|
80
70
|
|
|
81
71
|
So when the schema does not configure `efConstruction`, it scales as `AUTO_EF_BASE * sqrt(nodes /
|
|
82
72
|
AUTO_EFC_REF)`, capped at `AUTO_EFC_MAX`. The healthy write path reads the count directly from the
|
|
@@ -94,24 +84,19 @@ to 100. Retaining the former large-graph search default while opting out of buil
|
|
|
94
84
|
an explicit `efConstructionSearch` as well (512 after the former auto-scale reached its plateau).
|
|
95
85
|
There is currently no "pinned build, auto search" combination.
|
|
96
86
|
|
|
97
|
-
The search side scales past its old plateau for the same reason
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
decisions, and an explicit ef (per-query or schema) still raises the budget for callers who own
|
|
111
|
-
the cost. Both ceilings are finite on
|
|
112
|
-
purpose: total build work grows as N^1.5 under sqrt scaling, and past roughly tens of millions of
|
|
113
|
-
nodes per graph, sharded medium graphs beat one huge graph on build and query cost alike — scaling
|
|
114
|
-
the constants further is the wrong tool there.
|
|
87
|
+
The search side scales past its old plateau for the same reason: past `AUTO_EF_LARGE_REF` (1M
|
|
88
|
+
nodes) the scale resumes from `AUTO_EF_MAX` (512) — `512 * sqrt(nodes / 1M)` — up to
|
|
89
|
+
`AUTO_EF_CEILING` (2048, binding at ~16M). Why the plateau was calibrated where it was, and the
|
|
90
|
+
recall a pinned 512 leaves on the table past 1M, are in the comment above `AUTO_EF_BASE` (#2181).
|
|
91
|
+
The default's query latency therefore grows as sqrt(N) on large tables; that is the recall-first
|
|
92
|
+
trade chosen here, and apps preferring latency pin `efConstructionSearch` or a per-query `ef`. The
|
|
93
|
+
filtered-traversal budget (`maxVisits`, #1241) deliberately does not follow the second regime: each
|
|
94
|
+
budgeted visit is a synchronous record load plus predicate evaluation, so an auto-scaled ef's budget
|
|
95
|
+
contribution stays capped at `AUTO_EF_MAX` — the recall decision and the filtered-scan bound are
|
|
96
|
+
separate decisions, and an explicit ef (per-query or schema) still raises the budget for callers who
|
|
97
|
+
own the cost. Both ceilings are finite on purpose: total build work grows as N^1.5 under sqrt
|
|
98
|
+
scaling, and past roughly tens of millions of nodes per graph, sharded medium graphs beat one huge
|
|
99
|
+
graph on build and query cost alike — scaling the constants further is the wrong tool there.
|
|
115
100
|
|
|
116
101
|
Two caveats are accepted deliberately, both inherited from the count being a lifetime high-water
|
|
117
102
|
mark of allocated node ids rather than a live count. First, churn: a table that deletes heavily
|
|
@@ -127,9 +112,8 @@ that point on. A reindex in a live process rebuilds roughly uniformly (the id co
|
|
|
127
112
|
high-water mark), but a reindex after a restart re-seeds the counter from the largest id in the
|
|
128
113
|
rebuilding store and therefore repeats the ramp — its first 250K nodes rebuild at the base efC.
|
|
129
114
|
Later inserts add reverse edges to older nodes, but a default-ramp 1M build has not been compared
|
|
130
|
-
directly with the uniform-200 A/B
|
|
131
|
-
|
|
132
|
-
at those sizes without proving uniform convergence.
|
|
115
|
+
directly with the uniform-200 A/B; the larger default-ramp runs (#2181) kept the measured neighbours
|
|
116
|
+
reachable at 2M and 5M without proving uniform convergence.
|
|
133
117
|
|
|
134
118
|
Deletes have a separate tail-latency cost: connectivity repair can synchronously reinsert an orphan
|
|
135
119
|
and up to 256 nodes from a severed island. Those reinserts use the current auto-scaled efC, so the
|
|
@@ -205,6 +189,14 @@ a configured `filterExpansion` stays authoritative. `CandidateKeySet` is the one
|
|
|
205
189
|
the scan's intersection and admission. A node-id bitset for the native plane is not built: the
|
|
206
190
|
pk → node-id lookup costs more than the record load it saves.
|
|
207
191
|
|
|
192
|
+
## Filtered full-text search reuses companion secondary indexes before loading source records
|
|
193
|
+
|
|
194
|
+
Full-text search uses the same `planCandidateKeys` contract as HNSW and chooses between two existing RocksDB access patterns. A selective range becomes a bounded key set; a broad equality condition uses one point read of the existing `[indexed value, primary key]` secondary-index entry per Tantivy hit. This avoids retaining a large set only to reject non-matching hits. Materialization has a 4,096-key budget. HNSW keeps the planner's synchronous collection path; Fulltext requests its cooperative path, which yields every 256 scanned entries to observe cancellation, transaction state and the query deadline.
|
|
195
|
+
|
|
196
|
+
Point probes run only when their estimated index-read cost does not exceed the source loads they should avoid. The estimate uses the covered terms' selectivity and the same eight-key-reads-per-source-read ratio as materialization. Candidate-set budgets use the expected rejection fraction and the native reported hit count, including unbounded queries whose target fills the native window.
|
|
197
|
+
|
|
198
|
+
The planner marks a gate complete only when its indexed terms cover every companion condition, the index encoding preserves the predicate's equality semantics and no opaque record guard remains. An indexed numeric equality on an `Any` field is incomplete because storage can encode an equivalent `number` and `bigint` together while the record predicate compares them strictly. An incomplete gate rejects definite misses, but admitted records still run the residual predicate. Both paths retain the normal post-filter, source-version, expiry and current-entry checks. A budget overrun or point-read failure falls back to the record predicate, briefly backs off the failing gate and emits one warning per failure episode. Unless the lead index requests point probes, the planner does not inspect probe capabilities or allocate probe state; ordinary HNSW traversal stays unchanged.
|
|
199
|
+
|
|
208
200
|
## Derived-index runtime: committed-log delivery to native index backends (`resources/derivedIndexRuntime.ts`)
|
|
209
201
|
|
|
210
202
|
A derived index (the native HNSW or Tantivy full-text plane) is a materialized view
|
|
@@ -248,15 +240,16 @@ turn. Ownership is sticky: the lock is not one record writers take, so holding i
|
|
|
248
240
|
delays no commit, and it is released only after an idle grace period once durable progress equals
|
|
249
241
|
offered progress. The intended wake for a waiting runner is the lock's own unlock callback
|
|
250
242
|
(`tryLock(key, onUnlocked)`): one primitive, and a holder that dies releases natively and wakes the
|
|
251
|
-
waiters the same way. **Temporarily** the lock is taken without one:
|
|
243
|
+
waiters the same way. **Temporarily** the lock is taken without one: rocksdb-js before 2.9.1 queued
|
|
252
244
|
that callback as a thread-safe function of the caller's env, and on Node 22 one left behind by a
|
|
253
245
|
worker that was `terminate()`d aborts the process when another thread unlocks (Harper's thread
|
|
254
|
-
manager terminates workers on restart).
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
246
|
+
manager terminates workers on restart). The pinned 2.10.0 includes the fix
|
|
247
|
+
(HarperFast/rocksdb-js#849, released in 2.9.1), but the workaround is not yet reverted: successors
|
|
248
|
+
are woken by the releasing owner's `notify()` on the `derived-index:<id>:wake` buffer (the readiness
|
|
249
|
+
buffer when the registration has no generation-specific readiness id) and, for an owner that died
|
|
250
|
+
without releasing, by a retry timer (`lockRetryMilliseconds`, 5 s by default); the releasing runner
|
|
251
|
+
ignores the one notification it caused, and a runner already parked on that timer does not re-probe
|
|
252
|
+
the lock on commit wakes. The callback path is simpler and picks up a dead owner immediately.
|
|
260
253
|
|
|
261
254
|
Configuration-level database aliases can load several table classes over the same physical audit
|
|
262
255
|
store, index column family and plane file. They must not run that backend more than once. Registration
|
|
@@ -308,28 +301,11 @@ record values. Any other projection or primary-read failure is fail-closed.
|
|
|
308
301
|
|
|
309
302
|
### Backend contract
|
|
310
303
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
deliver(batch: DerivedIndexBatch): DERIVED_INDEX_ACCEPTED | DERIVED_INDEX_DEFERRED | DERIVED_INDEX_FAILED;
|
|
317
|
-
flush(reason: 'age' | 'threshold' | 'shutdown'): void | Promise<void>; // barrier request
|
|
318
|
-
shutdown(ownerEpoch: bigint): void | Promise<void>; // quiescence: nothing further applies or publishes for the epoch
|
|
319
|
-
onStateChange(wake: (change?: 'changed' | 'accepted-work-lost' | 'failed') => void): () => void;
|
|
320
|
-
reset?(ownerEpoch: bigint): void | Promise<void>; // destroy state and cursor; first durable action invalidates the cursor
|
|
321
|
-
}
|
|
322
|
-
type DerivedIndexBatch = {
|
|
323
|
-
ownerEpoch: bigint;
|
|
324
|
-
transactions: { logName; timestamp; mutations }[];
|
|
325
|
-
records: DerivedIndexMutation[]; // last-write-wins per (tableId, writeKeyId(recordId)); non-enumerable
|
|
326
|
-
through?: DerivedIndexCursor; // absent on a rebuild scan chunk and while a transaction is still open
|
|
327
|
-
bytes: number; // estimate; non-enumerable
|
|
328
|
-
rebuild?: true;
|
|
329
|
-
};
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
There is one contract and every hook is required. What an ownership handoff has to fence is work
|
|
304
|
+
The contract is `DerivedIndexBackend` and `DerivedIndexBatch` in `derivedIndexRuntime.ts`; their doc
|
|
305
|
+
comments state each hook's obligation. Three facts they do not: `records` and `bytes` are
|
|
306
|
+
non-enumerable (a backend that spreads or clones the batch loses them), `bytes` is an estimate, and
|
|
307
|
+
`through` is also absent while a transaction is still open (see Bounded delivery). What an ownership
|
|
308
|
+
handoff has to fence is work
|
|
333
309
|
that survives a method return — a queued apply, a barrier that completes later, a cursor that
|
|
334
310
|
trails delivery — so every backend supplies the epoch fence, the barrier request and the quiescence
|
|
335
311
|
handshake; one that completes inside `deliver()` implements them trivially. `deliver()` is not
|
|
@@ -533,19 +509,15 @@ plaintext, so Harper authorizes every highlighted source field before search; se
|
|
|
533
509
|
by both record count and encoded source bytes and sends only the fields used by that query leaf. A
|
|
534
510
|
single record larger than the native trace limit is rejected before crossing the binding.
|
|
535
511
|
|
|
536
|
-
Search hits carry the source record version. Harper
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
REST exposes the index coverage header
|
|
545
|
-
`maxIndexLagMilliseconds: 0` or `waitForIndexMilliseconds`.
|
|
546
|
-
Full-text score descending is the only supported ordering in this release. Count requests return
|
|
547
|
-
`recordCount: null` and `recordCountExact: false`; the native candidate total cannot become an exact
|
|
548
|
-
Harper count after authorization, structured filtering and source-version checks.
|
|
512
|
+
Search hits carry the source record version. Harper reads the authoritative record in its transaction and omits a hit when the version no longer matches; it never combines an old score or highlight with new content. Bounded searches begin by over-fetching 32–256 native hits. When version checks or structured filters under-fill a page, Harper sizes the next request from observed yield. Pages grow within the native response envelope, with 4,096 as an absolute ceiling, then shrink as selectivity improves; a zero-yield page grows geometrically because it provides no selectivity estimate.
|
|
513
|
+
|
|
514
|
+
Each page yields after at most 256 estimated source and companion-index point reads to check cancellation and the execution deadline. Equality point probes are limited to four index reads per candidate; wider equality plans use the authoritative record predicate. The native result window remains the hard bound, and one reader lease keeps every page on one native snapshot.
|
|
515
|
+
|
|
516
|
+
Harper does not configure native `filterFields` or send native `filter` or `candidateIds` payloads. Candidate planning, companion-index probes and the authoritative record predicate remain Harper-owned; the wrapper's candidate limits do not cap those mechanisms.
|
|
517
|
+
|
|
518
|
+
A lower-bound total must describe retrievable hits: while more hits remain, a page must return its requested limit. Fulltext 0.5.0 checks its deadline around the complete Tantivy collection and returns `E_TIMEOUT` instead of a partial page. Harper fails closed on a short page claiming more hits. Stale-version exhaustion observed in loaded records or a bounded sample of companion-index rejections is retryable index lag; filter-only exhaustion asks the client to narrow the query. Stale rejections outside that sample can still be classified as filter exhaustion; the bounded diagnostic avoids unbounded source reads. During bounded lag, results can temporarily omit a recent change when native hits end before the window.
|
|
519
|
+
|
|
520
|
+
Harper clamps autocomplete to the maximum-size hits that fit one native response without reducing ordinary search. The response-capacity calculation is pinned to the Fulltext 0.5.0 Query API v3 framing: 13 response bytes plus 13 bytes per versioned hit before string data. A later query API must advertise or version its framing overhead before Harper accepts it. REST exposes the index coverage header; callers requiring current coverage use `maxIndexLagMilliseconds: 0` or `waitForIndexMilliseconds`. Full-text score descending is the only supported ordering in this release. Count requests return `recordCount: null` and `recordCountExact: false`; the native candidate total cannot become an exact Harper count after authorization, structured filtering and source-version checks.
|
|
549
521
|
|
|
550
522
|
A declared Blob source is part of one index document. An oversized, invalid UTF-8 or otherwise
|
|
551
523
|
permanently unusable Blob makes that whole document unindexable rather than publishing a partial
|
|
@@ -612,24 +584,13 @@ consumes at its next wake.
|
|
|
612
584
|
### Native HNSW query coverage
|
|
613
585
|
|
|
614
586
|
Generation readiness and read freshness are separate. A `ready` native index can still be applying
|
|
615
|
-
committed mutations. Native vector sort/threshold conditions accept `maxIndexLagMilliseconds
|
|
616
|
-
**3000 ms by default
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
```json
|
|
621
|
-
{
|
|
622
|
-
"sort": {
|
|
623
|
-
"attribute": "vector",
|
|
624
|
-
"target": [1, 0, 0, 0],
|
|
625
|
-
"distance": "cosine",
|
|
626
|
-
"maxIndexLagMilliseconds": 0
|
|
627
|
-
},
|
|
628
|
-
"limit": 10
|
|
629
|
-
}
|
|
630
|
-
```
|
|
587
|
+
committed mutations. Native vector sort/threshold conditions accept `maxIndexLagMilliseconds` on the
|
|
588
|
+
sort object or the vector condition: **3000 ms by default** (`DEFAULT_MAX_INDEX_LAG_MILLISECONDS`),
|
|
589
|
+
`0` for strict coverage, or an explicit finite nonnegative number. This default allows three ordinary
|
|
590
|
+
1000 ms flush ages; it does not change the separate writer backpressure budget. Synchronous/non-native
|
|
591
|
+
indexes ignore the native coverage options.
|
|
631
592
|
|
|
632
|
-
For read-after-write,
|
|
593
|
+
For read-after-write, the same sort or condition takes `waitForIndexMilliseconds`.
|
|
633
594
|
Omitted or `0` preserves immediate admission; a positive finite number, capped at **30,000 ms**,
|
|
634
595
|
opts into a bounded wait for coverage of writes committed before the native search begins on first iteration.
|
|
635
596
|
The wait takes precedence over lag tolerance: a recent but stale proof cannot satisfy it. An already
|
|
@@ -637,7 +598,8 @@ physically current index proceeds immediately. Otherwise the query captures one
|
|
|
637
598
|
waits for the owner's certified time to reach it; later writes never reset the target. Each consumed
|
|
638
599
|
waiting branch has its own budget; sequential OR or concatenated branches can take longer in total.
|
|
639
600
|
A timeout throws retryable `DERIVED_INDEX_LAGGING`. On an already-started HTTP stream this is an error
|
|
640
|
-
record, not a new HTTP status. Request cancellation and iterator closure also end pending waits
|
|
601
|
+
record, not a new HTTP status. Request cancellation and iterator closure also end pending waits and
|
|
602
|
+
skip late result materialization.
|
|
641
603
|
|
|
642
604
|
Waiting preserves the normal 1000 ms flush-age schedule: a small write followed by a search commonly
|
|
643
605
|
waits about a second plus barrier time. Shorter deadlines are valid but may expire. Replay must reach
|
|
@@ -847,45 +809,35 @@ per-query distance overrides are rejected; the default capacity is 16M nodes; an
|
|
|
847
809
|
to a populated table keeps searches unavailable for the rebuild, which can take hours at large
|
|
848
810
|
sizes. `nativePlane: false` is the durable per-index opt-out.
|
|
849
811
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
per query is the only shape that reaches the ceiling; measured 7.2 ms → 0.75 ms p50 at 1M × 768-d,
|
|
855
|
-
recall@10 0.997 → 0.999.
|
|
812
|
+
The plane's own design — why the whole search loop is native rather than just the distance kernel,
|
|
813
|
+
the file format, per-slot locking and dead-writer takeover, `msync` durability, the search path and
|
|
814
|
+
the platform policy — belongs to the crate and lives in the `DESIGN.md` shipped with
|
|
815
|
+
`@harperfast/hnsw` (HarperFast/hnsw); Harper's measured result is in #2430. What Harper owns:
|
|
856
816
|
|
|
857
817
|
### File format
|
|
858
818
|
|
|
859
|
-
One file per index, `<index store path>/<store name>.hnsw
|
|
860
|
-
slots (16M default; a structural, create-time header field —
|
|
861
|
-
unavailable until the value is raised and the index rebuilt).
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
id remains on the query path.
|
|
874
|
-
|
|
875
|
-
Degree cap is the per-index `nativePlaneLayer0Cap`, **default 64** (supersedes the fixed 128 of
|
|
876
|
-
2026-08-31, re-measured in [hnsw#14](https://github.com/HarperFast/hnsw/pull/14)): at 128-d and
|
|
877
|
-
768-d int8, cap 64 holds recall@10 within ~0.5 pt of cap 128 at every ef ≥ 128 at 1M and 4M, and
|
|
878
|
-
within ~0.3 pt at 768-d, at the same resident latency — while cutting the 128-d slot 704 → 448 B and
|
|
879
|
-
the 768-d slot 1,344 → 1,088 B, which keeps a 4M-node plane resident under a 2 GB limit that makes
|
|
880
|
-
the cap-128 plane thrash. A live 7.7M-node plane carries a mean layer-0 degree of 29
|
|
819
|
+
One file per index, `<index store path>/<URI-encoded store name>.hnsw` (`hnswPlaneBinding.ts`),
|
|
820
|
+
created sparse at `nativePlaneMaxNodes` slots (16M default; a structural, create-time header field —
|
|
821
|
+
exhausting it makes the index unavailable until the value is raised and the index rebuilt). A
|
|
822
|
+
format-version mismatch is a rebuild, by contract. Each slot ends with the record's msgpack-encoded
|
|
823
|
+
primary key in `nativePlaneKeyCap` inline bytes (default 40, which fits a UUID in the slot padding);
|
|
824
|
+
a longer key spills to the crate's overflow arena, so a table whose keys are mostly longer than 40
|
|
825
|
+
encoded bytes should raise `nativePlaneKeyCap` rather than live in the arena. Searches and predicate
|
|
826
|
+
batches return each hit's key with it, so no lookup by node id remains on the query path.
|
|
827
|
+
|
|
828
|
+
Degree cap is the per-index `nativePlaneLayer0Cap`, **default 64** (supersedes the fixed 128;
|
|
829
|
+
measured in [hnsw#14](https://github.com/HarperFast/hnsw/pull/14) and #2701): cap 64 holds recall@10
|
|
830
|
+
within ~0.5 pt of cap 128 at every ef ≥ 128 (at 1M and 4M nodes) while cutting the 128-d slot 704 →
|
|
831
|
+
448 B and the 768-d slot 1,344 → 1,088 B, which keeps a 4M-node plane resident under a 2 GB limit
|
|
832
|
+
that makes the cap-128 plane thrash; a live plane's mean layer-0 degree is ~29
|
|
881
833
|
([hnsw#7](https://github.com/HarperFast/hnsw/issues/7)), so the reserved slot was mostly padding.
|
|
882
|
-
Cap 32 halves the slot again but trails cap 128 by 1.3–2.2 pts below ef 1024 at 4M and by 1.7 pts
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
834
|
+
Cap 32 halves the slot again but trails cap 128 by 1.3–2.2 pts below ef 1024 at 4M and by 1.7 pts at
|
|
835
|
+
1M for 768-d vectors, so it is a declaration for narrow vectors on a plane that outgrows RAM, not a
|
|
836
|
+
default. The cap is a create-time header field: `getPlane` compares it with the index's value on
|
|
837
|
+
attach and invalidates a plane that disagrees rather than reusing or truncating it, so revising it
|
|
838
|
+
is a rebuild, not a format change. A file-primary index builds no JS graph, so this is the only
|
|
839
|
+
layer-0 maximum it has; the JS graph's own cap in `addConnection` governs non-`nativePlane` indexes
|
|
840
|
+
only. A binary-code v2 slot reopens the question.
|
|
889
841
|
|
|
890
842
|
Upgrading a plane built at 128 costs one rebuild per node, the first time a process opens it under
|
|
891
843
|
the new default; no GA release line carries plane files, so this reaches 5.3 pre-releases only.
|
|
@@ -896,40 +848,31 @@ it is already the persisted declaration. The default is not written into a descr
|
|
|
896
848
|
the option, so nodes upgrade independently: a mixed-version cluster has each node rebuild its own
|
|
897
849
|
file as it reaches the new code, and no node invalidates another's.
|
|
898
850
|
|
|
899
|
-
### Concurrency
|
|
900
|
-
|
|
901
|
-
Per-slot lock word: bit 31 locked, low bits the owner's pid; unlocked values are generations that
|
|
902
|
-
readers validate seqlock-style. A lock unchanged for 20 ms whose owner pid is dead is taken over and
|
|
903
|
-
the slot sanitized (marked invalid — a dead writer's payload is half-written; invisible until
|
|
904
|
-
rewritten, never spliced-but-valid). Elapsed time alone never robs a live writer. There is no
|
|
905
|
-
cross-slot atomicity: an insert writes its slot plus ~M neighbours' back-edges independently, and a
|
|
906
|
-
traversal may see a half-linked state — a missing edge or a just-deleted neighbour is skipped. That
|
|
907
|
-
relaxed adherence is safe _here_ because the read path loads the record and rescores exactly, which
|
|
908
|
-
rejects a wrong candidate; it is not a general storage pattern. Fields a reader acts on are read
|
|
909
|
-
through aligned `read_volatile`; the stored vector is an ordinary load so the int8 kernel keeps
|
|
910
|
-
autovectorizing, and a torn vector only perturbs a distance the generation check discards.
|
|
911
|
-
|
|
912
|
-
### Durability
|
|
851
|
+
### Concurrency and durability
|
|
913
852
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
853
|
+
The plane has no cross-slot atomicity: a traversal may see a half-linked graph, or a slot reused
|
|
854
|
+
since it was reached. That relaxed contract is safe only because Harper's read path loads the record
|
|
855
|
+
and rescores exactly, which rejects a wrong candidate; it is not a general storage pattern. The file
|
|
856
|
+
is `msync`ed on a cadence, not per commit, so the graph has bounded-lag durability with
|
|
857
|
+
deterministic catch-up while the source of truth (records, mappings, cursor) stays transactional.
|
|
858
|
+
Backup treats the file as node-local derived state: include it after a barrier, or rebuild on
|
|
859
|
+
restore. A file whose format or checksum does not validate is rebuilt from records. macOS `msync` is a
|
|
860
|
+
weaker barrier than Linux — the crate's `msync()` is a plain mapping flush with no `F_FULLFSYNC` pass —
|
|
861
|
+
so on macOS a power loss after `flushAsync()` resolves and the cursor is written can leave the cursor
|
|
862
|
+
past graph writes the file lost. Those vectors stay missing until a rebuild — nothing compares the
|
|
863
|
+
plane's `getWatermark()` with the cursor on attach — so the Delivery crash contract does not hold there.
|
|
920
864
|
|
|
921
865
|
### Search
|
|
922
866
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
close to the live count; deletes leave it generous until a rebuild, as with the RocksDB graph). Every vector reaching the plane — a
|
|
867
|
+
A search is one crossing per query, run off the event loop. An unfiltered search calls
|
|
868
|
+
`plane.search()`; a filtered one always goes through `plane.searchWithPredicate()` — the crate hands
|
|
869
|
+
back batches of candidate keys for `admit()` while traversal keeps expanding — bounded by
|
|
870
|
+
`filterState.maxVisits`, the same `filterExpansion` budget as the JS path, and never passes the crate's
|
|
871
|
+
node-id bitset (see the filtered vector search note above).
|
|
872
|
+
A plane-backed `customIndex.search()` returns a promise-backed, async-only iterable
|
|
873
|
+
(`resources/search.ts` wraps it); a synchronous consumer throws. Auto-ef reads the node count from
|
|
874
|
+
the plane's `idHighWater()` (freed ids are reused, so it stays close to the live count; deletes leave
|
|
875
|
+
it generous until a rebuild, as with the RocksDB graph). Every vector reaching the plane — a
|
|
933
876
|
committed projection or a query target — passes one invariant (`assertPlaneVector`): array-like of
|
|
934
877
|
positive length, every component a finite f32, a magnitude representable in f32, and the plane's
|
|
935
878
|
`dims` once known. What fails it is the client's 400, never a plane failure; a query the plane
|
|
@@ -992,5 +935,5 @@ Not promised: a single total order across concurrent CRDT/source-resolution arri
|
|
|
992
935
|
and replay re-read the authoritative record, so the index converges on what the primary store
|
|
993
936
|
resolved; divergence is bounded by candidate selection, which the exact rescore filters), byte-identical
|
|
994
937
|
graphs across nodes or rebuilds, or in-place format upgrades. Rebuild rate falls with graph size
|
|
995
|
-
(
|
|
996
|
-
|
|
938
|
+
(measured in #2430), so a 16M rebuild is hours of 503; the pinned `@harperfast/hnsw` (0.4.0) exports
|
|
939
|
+
a native `insertBatch`, but the rebuild does not use it yet.
|
|
@@ -2,8 +2,8 @@ import type { FullTextDerivedIndexEngine } from './fullTextDerivedIndex.ts';
|
|
|
2
2
|
import { loggerWithTag } from '../../utility/logging/logger.ts';
|
|
3
3
|
|
|
4
4
|
const FULLTEXT_LIFECYCLE_API_VERSION = 1;
|
|
5
|
-
const FULLTEXT_MUTATION_BATCH_API_VERSION =
|
|
6
|
-
const FULLTEXT_QUERY_API_VERSION =
|
|
5
|
+
const FULLTEXT_MUTATION_BATCH_API_VERSION = 5;
|
|
6
|
+
const FULLTEXT_QUERY_API_VERSION = 3;
|
|
7
7
|
const logger = loggerWithTag('fulltext-derived-index');
|
|
8
8
|
|
|
9
9
|
export interface NativeFullTextIndexConfiguration {
|
|
@@ -38,7 +38,6 @@ export type NativeFullTextSearchRequest = {
|
|
|
38
38
|
mode?: NativeFullTextSearchMode;
|
|
39
39
|
operator?: 'any' | 'all';
|
|
40
40
|
fields?: string[];
|
|
41
|
-
candidateIds?: string[];
|
|
42
41
|
offset?: number;
|
|
43
42
|
limit?: number;
|
|
44
43
|
exactTotal?: boolean;
|
|
@@ -94,8 +93,13 @@ export type NativeFullTextRuntimeInfo = {
|
|
|
94
93
|
storageBackends: ReadonlyArray<'native'>;
|
|
95
94
|
limits: {
|
|
96
95
|
maxCommitPayloadBytes: number;
|
|
96
|
+
maxRecordIdBytes: number;
|
|
97
|
+
maxRecordVersionBytes: number;
|
|
98
|
+
maxCandidateIds: number;
|
|
99
|
+
maxCandidateBytes: number;
|
|
97
100
|
maxSearchWindow: number;
|
|
98
101
|
maxAutocompleteResults: number;
|
|
102
|
+
maxSearchResponseBytes: number;
|
|
99
103
|
maxSearchBudgetMilliseconds: number;
|
|
100
104
|
maxTraceRecords: number;
|
|
101
105
|
maxTraceSourceBytes: number;
|
|
@@ -110,6 +114,11 @@ export type NativeFullTextQueryRuntimeInfo = Pick<
|
|
|
110
114
|
NativeFullTextRuntimeInfo['limits'],
|
|
111
115
|
| 'maxSearchWindow'
|
|
112
116
|
| 'maxAutocompleteResults'
|
|
117
|
+
| 'maxRecordIdBytes'
|
|
118
|
+
| 'maxRecordVersionBytes'
|
|
119
|
+
| 'maxCandidateIds'
|
|
120
|
+
| 'maxCandidateBytes'
|
|
121
|
+
| 'maxSearchResponseBytes'
|
|
113
122
|
| 'maxSearchBudgetMilliseconds'
|
|
114
123
|
| 'maxTraceRecords'
|
|
115
124
|
| 'maxTraceSourceBytes'
|
|
@@ -259,11 +268,21 @@ export function validateFullTextQueryRuntimeInfo(info: unknown): NativeFullTextQ
|
|
|
259
268
|
!Number.isSafeInteger(runtime.queryClassIsolationMinimumSearchThreads) ||
|
|
260
269
|
runtime.queryClassIsolationMinimumSearchThreads <= 0 ||
|
|
261
270
|
!runtime.limits ||
|
|
271
|
+
!Number.isSafeInteger(runtime.limits.maxRecordIdBytes) ||
|
|
272
|
+
runtime.limits.maxRecordIdBytes <= 0 ||
|
|
273
|
+
!Number.isSafeInteger(runtime.limits.maxRecordVersionBytes) ||
|
|
274
|
+
runtime.limits.maxRecordVersionBytes <= 0 ||
|
|
275
|
+
!Number.isSafeInteger(runtime.limits.maxCandidateIds) ||
|
|
276
|
+
runtime.limits.maxCandidateIds <= 0 ||
|
|
277
|
+
!Number.isSafeInteger(runtime.limits.maxCandidateBytes) ||
|
|
278
|
+
runtime.limits.maxCandidateBytes <= 0 ||
|
|
262
279
|
!Number.isSafeInteger(runtime.limits.maxSearchWindow) ||
|
|
263
280
|
runtime.limits.maxSearchWindow <= 0 ||
|
|
264
281
|
!Number.isSafeInteger(runtime.limits.maxAutocompleteResults) ||
|
|
265
282
|
runtime.limits.maxAutocompleteResults <= 0 ||
|
|
266
283
|
runtime.limits.maxAutocompleteResults > runtime.limits.maxSearchWindow ||
|
|
284
|
+
!Number.isSafeInteger(runtime.limits.maxSearchResponseBytes) ||
|
|
285
|
+
runtime.limits.maxSearchResponseBytes <= 0 ||
|
|
267
286
|
!Number.isSafeInteger(runtime.limits.maxSearchBudgetMilliseconds) ||
|
|
268
287
|
runtime.limits.maxSearchBudgetMilliseconds <= 0 ||
|
|
269
288
|
!Number.isSafeInteger(runtime.limits.maxTraceRecords) ||
|