@delali/narsil 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 (2) hide show
  1. package/README.md +218 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # Narsil
2
+
3
+ Distributed search, reforged.
4
+
5
+ Narsil is a distributed search engine with full-text, vector, hybrid, and geosearch. It auto-partitions large indexes across workers, serializes them into a cross-language binary format (.nrsl), and merges results back into a single ranked answer. The TypeScript package is the first implementation.
6
+
7
+ > *narsil* is the sword of Elendil in Tolkien's Lord of the Rings, shattered into shards and later reforged. The name maps to the architecture: data shatters into partitions, each shard is independently persisted, and every query reforges them into a unified result.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @delali/narsil
13
+ ```
14
+
15
+ Requires Node.js >= 22.
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import { Narsil } from '@delali/narsil'
21
+
22
+ const narsil = new Narsil()
23
+
24
+ await narsil.createIndex('products', {
25
+ schema: {
26
+ title: 'string',
27
+ description: 'string',
28
+ price: 'number',
29
+ inStock: 'boolean',
30
+ category: 'enum',
31
+ tags: 'string[]',
32
+ },
33
+ language: 'english',
34
+ })
35
+
36
+ await narsil.insert('products', {
37
+ title: 'Mechanical Keyboard',
38
+ description: 'Cherry MX Brown switches with PBT keycaps and USB-C connection',
39
+ price: 129.99,
40
+ inStock: true,
41
+ category: 'electronics',
42
+ tags: ['peripherals', 'typing', 'mechanical'],
43
+ })
44
+
45
+ const results = await narsil.query('products', {
46
+ term: 'mechanical keyboard',
47
+ filters: {
48
+ inStock: { eq: true },
49
+ price: { lte: 200 },
50
+ },
51
+ boost: { title: 2.0 },
52
+ limit: 10,
53
+ })
54
+ ```
55
+
56
+ ## Features
57
+
58
+ ### Full-text search
59
+
60
+ BM25 scoring with field boosting, fuzzy matching via bounded Levenshtein distance, exact phrase search, and configurable term matching policies. Specify how many query terms a document must match, set minimum score thresholds, and sort by any field.
61
+
62
+ ### Auto-partitioning
63
+
64
+ Indexes split into shards when they grow beyond a configurable threshold. Partition assignment uses FNV-1a hashing for deterministic routing. Background rebalancing redistributes documents across new partitions without blocking queries, using a write-ahead queue for zero-downtime resharding.
65
+
66
+ ### Worker isolation
67
+
68
+ Search operations move off the main thread through worker threads (Node.js, Bun) or Web Workers (browser, Deno). The engine starts in direct mode for small indexes and auto-promotes to workers once document counts cross a threshold. You keep using the same API throughout.
69
+
70
+ ### Distributed BM25 scoring
71
+
72
+ Three scoring modes handle the partition-skew problem:
73
+
74
+ - **Local**: each partition scores with its own statistics (fastest, default)
75
+ - **DFS (Distributed Frequency Statistics)**: a two-phase query collects global term statistics first, then scores with unified IDF values
76
+ - **Broadcast**: instances share statistics through the invalidation adapter for pre-computed global scoring
77
+
78
+ ### Vector search
79
+
80
+ Store and query high-dimensional embeddings with cosine similarity, dot product, or Euclidean distance. Small vector sets use brute-force linear scan. When a field exceeds 10,000 vectors, the engine builds an HNSW graph in the background and swaps the search backend transparently.
81
+
82
+ ### Hybrid search
83
+
84
+ Combine full-text BM25 scores with vector similarity scores using weighted alpha blending. A single query searches both text and embeddings, normalizes both score sets to [0,1], and returns a merged ranking.
85
+
86
+ ### Geosearch
87
+
88
+ Index latitude/longitude points and query by radius (Haversine or Vincenty distance) or polygon containment (ray casting). High-precision mode switches to Vincenty's iterative formula for long-distance accuracy.
89
+
90
+ ### Filters
91
+
92
+ Filter on any indexed field using comparison operators (`eq`, `ne`, `gt`, `lt`, `gte`, `lte`, `between`), string operators (`in`, `nin`, `startsWith`, `endsWith`), array operators (`containsAll`, `matchesAny`, `size`), and presence checks (`exists`, `isEmpty`). Combine filters with explicit `and`, `or`, and `not` boolean combinators.
93
+
94
+ ```ts
95
+ const results = await narsil.query('products', {
96
+ term: 'wireless',
97
+ filters: {
98
+ or: [
99
+ { category: { eq: 'electronics' } },
100
+ { category: { eq: 'accessories' } },
101
+ ],
102
+ price: { between: [10, 100] },
103
+ tags: { containsAll: ['bluetooth'] },
104
+ },
105
+ })
106
+ ```
107
+
108
+ ### Faceted search
109
+
110
+ Get aggregate value counts alongside search results for building filter UIs.
111
+
112
+ ```ts
113
+ const results = await narsil.query('products', {
114
+ term: 'laptop',
115
+ facets: {
116
+ category: { limit: 10, sort: 'desc' },
117
+ price: { ranges: [{ from: 0, to: 500 }, { from: 500, to: 1000 }, { from: 1000, to: 5000 }] },
118
+ },
119
+ })
120
+ // results.facets.category.values => { electronics: 42, computers: 28, ... }
121
+ ```
122
+
123
+ ### Grouping
124
+
125
+ Group results by field values with optional custom reducers.
126
+
127
+ ### Match highlighting
128
+
129
+ Get snippets with customizable pre/post tags marking where query terms appear in the original text.
130
+
131
+ ```ts
132
+ const results = await narsil.query('products', {
133
+ term: 'mechanical',
134
+ highlight: {
135
+ fields: ['title', 'description'],
136
+ preTag: '<mark>',
137
+ postTag: '</mark>',
138
+ },
139
+ })
140
+ // hit.highlights.title.snippet => '<mark>Mechanical</mark> Keyboard'
141
+ ```
142
+
143
+ ### Cursor-based pagination
144
+
145
+ Deep pagination using `searchAfter` cursors that track position across partitions. No offset-based performance degradation for deep result sets.
146
+
147
+ ### Results pinning
148
+
149
+ Inject specific documents at fixed positions in the result set, useful for sponsored or editorial content.
150
+
151
+ ### Persistence
152
+
153
+ Plug in any storage backend through the persistence adapter interface. Three built-in adapters ship with the package:
154
+
155
+ | Adapter | Import | Environment |
156
+ | --- | --- | --- |
157
+ | Memory | `@delali/narsil/adapters/memory` | All |
158
+ | Filesystem | `@delali/narsil/adapters/filesystem` | Node.js, Bun, Deno |
159
+ | IndexedDB | `@delali/narsil/adapters/indexeddb` | Browser |
160
+
161
+ Persistence uses debounced flushing: dirty partitions serialize on a timer or after a mutation count threshold, whichever fires first. The serialization format is `.nrsl`, a 32-byte header followed by a MessagePack payload. The format is cross-language portable, so a Python or Rust implementation can read and write the same files.
162
+
163
+ ### Multi-instance invalidation
164
+
165
+ When multiple instances share the same persistence backend, the invalidation adapter coordinates cache eviction. A filesystem adapter uses marker files for multi-process deployments. A BroadcastChannel adapter handles cross-tab coordination in browsers.
166
+
167
+ ### Plugin system
168
+
169
+ Hook into the document and search lifecycle with plugins. Plugins can run before/after insert, remove, update, and search operations, and respond to index creation, partition splits, and worker promotions.
170
+
171
+ ### Schema validation
172
+
173
+ Define typed schemas with support for `string`, `number`, `boolean`, `enum`, `geopoint`, `vector[N]`, and array variants (`string[]`, `number[]`, `boolean[]`, `enum[]`). Nested objects up to 4 levels deep. Documents are validated against the schema at insertion time.
174
+
175
+ ## Language support
176
+
177
+ Narsil ships with language modules for tokenization, stemming (Snowball algorithm), and stop word removal.
178
+
179
+ ### Full support (tokenizer + stemmer + stop words)
180
+
181
+ Arabic, Armenian, Bulgarian, Danish, Dutch, English, Finnish, French, German, Greek, Hindi, Hungarian, Indonesian, Irish, Italian, Nepali, Norwegian, Portuguese, Romanian, Russian, Sanskrit, Serbian, Slovenian, Spanish, Swahili, Swedish, Tamil, Turkish, Ukrainian
182
+
183
+ ### CJK support (character n-gram tokenizer + stop words)
184
+
185
+ Chinese (Mandarin), Japanese
186
+
187
+ ### African language support
188
+
189
+ Full stemmer support ships for Swahili. Tokenization and stop word support is available for Dagbani, Ewe, Ga, Hausa, Igbo, Twi (Akan), Yoruba, and Zulu.
190
+
191
+ Each language module is a separate entry point, so you only bundle the languages your application needs:
192
+
193
+ ```ts
194
+ import '@delali/narsil/languages/french'
195
+ import '@delali/narsil/languages/swahili'
196
+ import '@delali/narsil/languages/twi'
197
+ ```
198
+
199
+ ## Search modes
200
+
201
+ | Mode | Method | Scoring |
202
+ | --- | --- | --- |
203
+ | Full-text | `mode: 'fulltext'` | BM25 with field boosting |
204
+ | Vector | `mode: 'vector'` | Cosine similarity, dot product, or Euclidean distance |
205
+ | Hybrid | `mode: 'hybrid'` | Weighted combination of BM25 + vector similarity |
206
+
207
+ ## Runtime support (TypeScript)
208
+
209
+ | Runtime | Concurrency | Persistence | Invalidation |
210
+ | --- | --- | --- | --- |
211
+ | Node.js | `worker_threads` | Filesystem | Adapter-based |
212
+ | Bun | `worker_threads` | Filesystem | Adapter-based |
213
+ | Deno | Web Workers | Filesystem | BroadcastChannel |
214
+ | Browser | Web Workers | IndexedDB | BroadcastChannel |
215
+
216
+ ## License
217
+
218
+ Apache-2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delali/narsil",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "A distributed full-text search engine with cross-language portable binary format.",
6
6
  "author": "Delali (https://sondelali.com)",