albex 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/README.md +242 -112
  3. package/dist/albex-worker.d.ts +70 -0
  4. package/dist/albex-worker.d.ts.map +1 -0
  5. package/dist/albex-worker.js +153 -0
  6. package/dist/albex-worker.js.map +1 -0
  7. package/dist/albex.d.ts +368 -6
  8. package/dist/albex.d.ts.map +1 -1
  9. package/dist/albex.js +1692 -95
  10. package/dist/albex.js.map +1 -1
  11. package/dist/errors.d.ts +38 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +63 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/gpu/bloom-runtime.d.ts +60 -0
  16. package/dist/gpu/bloom-runtime.d.ts.map +1 -0
  17. package/dist/gpu/bloom-runtime.js +176 -0
  18. package/dist/gpu/bloom-runtime.js.map +1 -0
  19. package/dist/gpu/bloom-shader.wgsl.d.ts +19 -0
  20. package/dist/gpu/bloom-shader.wgsl.d.ts.map +1 -0
  21. package/dist/gpu/bloom-shader.wgsl.js +49 -0
  22. package/dist/gpu/bloom-shader.wgsl.js.map +1 -0
  23. package/dist/persistence.d.ts +21 -0
  24. package/dist/persistence.d.ts.map +1 -0
  25. package/dist/persistence.js +174 -0
  26. package/dist/persistence.js.map +1 -0
  27. package/dist/pool/coordinator.d.ts +98 -0
  28. package/dist/pool/coordinator.d.ts.map +1 -0
  29. package/dist/pool/coordinator.js +247 -0
  30. package/dist/pool/coordinator.js.map +1 -0
  31. package/dist/profile.d.ts +95 -0
  32. package/dist/profile.d.ts.map +1 -0
  33. package/dist/profile.js +207 -0
  34. package/dist/profile.js.map +1 -0
  35. package/dist/resource-manager.d.ts +56 -0
  36. package/dist/resource-manager.d.ts.map +1 -0
  37. package/dist/resource-manager.js +138 -0
  38. package/dist/resource-manager.js.map +1 -0
  39. package/dist/tiered-store.d.ts +98 -0
  40. package/dist/tiered-store.d.ts.map +1 -0
  41. package/dist/tiered-store.js +238 -0
  42. package/dist/tiered-store.js.map +1 -0
  43. package/dist/wasm-bindings.d.ts +139 -0
  44. package/dist/wasm-bindings.d.ts.map +1 -0
  45. package/dist/wasm-bindings.js +33 -0
  46. package/dist/wasm-bindings.js.map +1 -0
  47. package/dist/worker-protocol.d.ts +86 -0
  48. package/dist/worker-protocol.d.ts.map +1 -0
  49. package/dist/worker-protocol.js +20 -0
  50. package/dist/worker-protocol.js.map +1 -0
  51. package/dist/worker-runtime.d.ts +14 -0
  52. package/dist/worker-runtime.d.ts.map +1 -0
  53. package/dist/worker-runtime.js +100 -0
  54. package/dist/worker-runtime.js.map +1 -0
  55. package/package.json +56 -13
  56. package/src/albex-worker.ts +187 -0
  57. package/src/albex.ts +1845 -130
  58. package/src/errors.ts +60 -0
  59. package/src/gpu/bloom-runtime.ts +229 -0
  60. package/src/gpu/bloom-shader.wgsl.ts +48 -0
  61. package/src/persistence.ts +175 -0
  62. package/src/pool/coordinator.ts +324 -0
  63. package/src/profile.ts +279 -0
  64. package/src/resource-manager.ts +167 -0
  65. package/src/tiered-store.ts +259 -0
  66. package/src/wasm-bindings.ts +200 -0
  67. package/src/worker-protocol.ts +48 -0
  68. package/src/worker-runtime.ts +96 -0
  69. package/wasm/pkg/albex_pdf.wasm +0 -0
  70. package/wasm/pkg/albex_wasm_bg.wasm +0 -0
  71. package/wasm/pkg/albex_wasm_mini.wasm +0 -0
  72. package/wasm/pkg/albex_wasm_mini_simd.wasm +0 -0
  73. package/wasm/pkg/albex_wasm_pro.wasm +0 -0
  74. package/wasm/pkg/albex_wasm_pro_simd.wasm +0 -0
  75. package/wasm/pkg/albex_wasm_std.wasm +0 -0
  76. package/wasm/pkg/albex_wasm_std_simd.wasm +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-runtime.js","sourceRoot":"","sources":["../src/worker-runtime.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "albex",
3
- "version": "0.1.0",
4
- "description": "Local full-text search enginedocuments never leave the browser",
3
+ "version": "0.3.0",
4
+ "description": "Zero-config local full-text search for documents runs entirely in the browser, no server, no upload.",
5
5
  "type": "module",
6
6
  "main": "./dist/albex.js",
7
7
  "module": "./dist/albex.js",
@@ -10,21 +10,50 @@
10
10
  ".": {
11
11
  "import": "./dist/albex.js",
12
12
  "types": "./dist/albex.d.ts"
13
+ },
14
+ "./worker": {
15
+ "import": "./dist/albex-worker.js",
16
+ "types": "./dist/albex-worker.d.ts"
17
+ },
18
+ "./worker-runtime": {
19
+ "import": "./dist/worker-runtime.js",
20
+ "types": "./dist/worker-runtime.d.ts"
21
+ },
22
+ "./pool": {
23
+ "import": "./dist/pool/coordinator.js",
24
+ "types": "./dist/pool/coordinator.d.ts"
25
+ },
26
+ "./gpu": {
27
+ "import": "./dist/gpu/bloom-runtime.js",
28
+ "types": "./dist/gpu/bloom-runtime.d.ts"
29
+ },
30
+ "./tiered": {
31
+ "import": "./dist/tiered-store.js",
32
+ "types": "./dist/tiered-store.d.ts"
13
33
  }
14
34
  },
15
35
  "files": [
16
36
  "dist/",
17
37
  "src/",
18
- "wasm/pkg/albex_wasm_bg.wasm",
19
- "wasm/pkg/albex_pdf.wasm"
38
+ "wasm/pkg/*.wasm",
39
+ "README.md",
40
+ "CHANGELOG.md",
41
+ "LICENSE"
20
42
  ],
21
43
  "scripts": {
22
- "build": "tsc",
44
+ "build": "tsc && node scripts/banner.mjs",
23
45
  "prepublishOnly": "npm run build",
24
- "build:wasm": "cd wasm && cargo build --target wasm32-unknown-unknown --release && cp ../target/wasm32-unknown-unknown/release/albex_wasm.wasm pkg/albex_wasm_bg.wasm",
25
- "build:pdf-wasm": "cd pdf-wasm && cargo build --target wasm32-unknown-unknown --release && cp ../target/wasm32-unknown-unknown/release/albex_pdf.wasm ../wasm/pkg/albex_pdf.wasm",
26
- "build:all": "npm run build:wasm && npm run build:pdf-wasm && npm run build",
27
- "typecheck": "tsc --noEmit"
46
+ "build:wasm": "node scripts/build-wasm.mjs std && node scripts/wasm-banner.mjs",
47
+ "build:wasm:mini": "node scripts/build-wasm.mjs mini && node scripts/wasm-banner.mjs",
48
+ "build:wasm:pro": "node scripts/build-wasm.mjs pro && node scripts/wasm-banner.mjs",
49
+ "build:wasm:tiers": "node scripts/build-wasm.mjs && node scripts/wasm-banner.mjs",
50
+ "build:pdf-wasm": "cd pdf-wasm && cargo build --target wasm32-unknown-unknown --release && cp ../target/wasm32-unknown-unknown/release/albex_pdf.wasm ../wasm/pkg/albex_pdf.wasm && node scripts/wasm-banner.mjs",
51
+ "build:all": "npm run build:wasm:tiers && npm run build:pdf-wasm && npm run build",
52
+ "typecheck": "tsc --noEmit",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "bench": "vitest bench --run",
56
+ "serve": "npx --yes serve@14 -p 5173 -L ."
28
57
  },
29
58
  "repository": {
30
59
  "type": "git",
@@ -38,13 +67,26 @@
38
67
  "search",
39
68
  "full-text",
40
69
  "wasm",
70
+ "webassembly",
41
71
  "browser",
72
+ "client-side",
42
73
  "offline",
74
+ "privacy",
75
+ "fuzzy",
76
+ "bitap",
77
+ "bloom-filter",
43
78
  "docx",
44
- "pdf",
45
79
  "xlsx",
46
- "fuzzy",
47
- "privacy"
80
+ "pdf",
81
+ "markdown",
82
+ "html",
83
+ "json",
84
+ "csv",
85
+ "eml",
86
+ "rtf",
87
+ "zero-config",
88
+ "opfs",
89
+ "webgpu"
48
90
  ],
49
91
  "author": "RafaCalRob",
50
92
  "license": "MIT",
@@ -52,6 +94,7 @@
52
94
  "node": ">=18"
53
95
  },
54
96
  "devDependencies": {
55
- "typescript": "^5.4.0"
97
+ "typescript": "^5.4.0",
98
+ "vitest": "^2.0.0"
56
99
  }
57
100
  }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * `AlbexEngineWorker` — a main-thread wrapper that runs the engine inside a
3
+ * Web Worker. Mirrors the surface of `AlbexEngine` so it can be swapped in
4
+ * without code changes.
5
+ *
6
+ * Usage:
7
+ *
8
+ * const engine = new AlbexEngineWorker({
9
+ * wasmUrl: '/assets/albex_wasm_bg.wasm',
10
+ * pdfWasmUrl: '/assets/albex_pdf.wasm',
11
+ * // Provide the URL to the bundled worker runtime.
12
+ * workerUrl: new URL('./worker-runtime.js', import.meta.url),
13
+ * });
14
+ * await engine.init();
15
+ *
16
+ * Why: a `search()` over 100k chunks can take 10–50 ms. On main thread that
17
+ * is visible jank for every keystroke. Off-main-thread keeps the UI at 60 fps.
18
+ *
19
+ * The runtime is single-threaded WASM, so requests are serialised: only one
20
+ * call is in flight at a time. This matches the actual `static mut` model
21
+ * inside the .wasm and is fine for an interactive search UI (each keystroke
22
+ * replaces the previous query).
23
+ */
24
+
25
+ import type {
26
+ AlbexOptions,
27
+ IndexedDocument,
28
+ SearchOptions,
29
+ SearchResult,
30
+ EngineStats,
31
+ SearchStats,
32
+ } from './albex.js';
33
+ import type {
34
+ WorkerRequest,
35
+ WorkerResponse,
36
+ WorkerOp,
37
+ } from './worker-protocol.js';
38
+ import {
39
+ AlbexError,
40
+ AlbexInitError,
41
+ AlbexUnsupportedFormatError,
42
+ AlbexParseError,
43
+ AlbexCapacityError,
44
+ } from './errors.js';
45
+
46
+ export interface AlbexWorkerOptions extends AlbexOptions {
47
+ /** URL to the bundled worker runtime script (worker-runtime.js). */
48
+ workerUrl: string | URL;
49
+ }
50
+
51
+ interface Pending {
52
+ resolve: (v: unknown) => void;
53
+ reject: (e: unknown) => void;
54
+ }
55
+
56
+ let _workerSearchStreamWarned = false;
57
+
58
+ export class AlbexEngineWorker {
59
+ private readonly _opts: AlbexWorkerOptions;
60
+ private _worker!: Worker;
61
+ private _nextId = 1;
62
+ private _pending = new Map<number, Pending>();
63
+ private _docsCache: IndexedDocument[] = [];
64
+
65
+ constructor(opts: AlbexWorkerOptions) {
66
+ this._opts = opts;
67
+ }
68
+
69
+ async init(): Promise<void> {
70
+ this._worker = new Worker(this._opts.workerUrl, { type: 'module' });
71
+ this._worker.onmessage = (ev: MessageEvent<WorkerResponse>) => {
72
+ const { id } = ev.data;
73
+ const p = this._pending.get(id);
74
+ if (!p) return;
75
+ this._pending.delete(id);
76
+ if (ev.data.ok) p.resolve(ev.data.result);
77
+ else p.reject(rehydrateError(ev.data.error));
78
+ };
79
+ this._worker.onerror = (e) => {
80
+ // Surface the error to every in-flight call.
81
+ const err = new AlbexInitError(`Worker crashed: ${e.message}`);
82
+ for (const [, p] of this._pending) p.reject(err);
83
+ this._pending.clear();
84
+ };
85
+ await this._send({ kind: 'init', opts: {
86
+ wasmUrl: this._opts.wasmUrl,
87
+ pdfWasmUrl: this._opts.pdfWasmUrl,
88
+ } });
89
+ }
90
+
91
+ private _send<T = unknown>(op: WorkerOp, transfer: Transferable[] = []): Promise<T> {
92
+ const id = this._nextId++;
93
+ const req: WorkerRequest = { id, op };
94
+ return new Promise<T>((resolve, reject) => {
95
+ this._pending.set(id, { resolve: resolve as (v: unknown) => void, reject });
96
+ this._worker.postMessage(req, transfer);
97
+ });
98
+ }
99
+
100
+ async indexFile(file: File): Promise<IndexedDocument> {
101
+ const buffer = await file.arrayBuffer();
102
+ // Transfer the buffer to avoid a copy.
103
+ const doc = await this._send<IndexedDocument>(
104
+ { kind: 'indexFile', name: file.name, buffer },
105
+ [buffer],
106
+ );
107
+ this._docsCache.push(doc);
108
+ return doc;
109
+ }
110
+
111
+ search(query: string, opts: SearchOptions = {}): Promise<SearchResult[]> {
112
+ return this._send<SearchResult[]>({ kind: 'search', query, options: opts });
113
+ }
114
+
115
+ /**
116
+ * Cooperative variant of `search`. Today the wire still sends a single
117
+ * batch — the result array is fetched in one round-trip from the worker
118
+ * and then exposed as an async iterator so callers can `break` early.
119
+ * A future iteration may use a `MessagePort` to stream individual results
120
+ * from the worker side; the iterator shape is preserved across that
121
+ * transition.
122
+ */
123
+ async *searchCooperative(query: string, opts: SearchOptions = {}): AsyncIterable<SearchResult> {
124
+ const results = await this.search(query, opts);
125
+ for (const r of results) yield r;
126
+ }
127
+
128
+ /**
129
+ * @deprecated Renamed to `searchCooperative` in 0.3.0. Alias removed in 0.4.0.
130
+ */
131
+ async *searchStream(query: string, opts: SearchOptions = {}): AsyncIterable<SearchResult> {
132
+ if (!_workerSearchStreamWarned) {
133
+ _workerSearchStreamWarned = true;
134
+ console.warn('[albex] `AlbexEngineWorker.searchStream` is deprecated; rename to `searchCooperative`. Alias removed in 0.4.0.');
135
+ }
136
+ yield* this.searchCooperative(query, opts);
137
+ }
138
+
139
+ async removeDocument(id: string): Promise<boolean> {
140
+ const ok = await this._send<boolean>({ kind: 'removeDocument', id });
141
+ if (ok) this._docsCache = this._docsCache.filter(d => d.name !== id && d.contentHash !== id);
142
+ return ok;
143
+ }
144
+
145
+ async compact(): Promise<void> { await this._send({ kind: 'compact' }); }
146
+ async reset(): Promise<void> {
147
+ await this._send({ kind: 'reset' });
148
+ this._docsCache = [];
149
+ }
150
+
151
+ getStats(): Promise<EngineStats> { return this._send({ kind: 'getStats' }); }
152
+ getLastSearchStats(): Promise<SearchStats | null> { return this._send({ kind: 'getLastSearchStats' }); }
153
+ getDocuments(): Promise<readonly IndexedDocument[]> { return this._send({ kind: 'getDocuments' }); }
154
+
155
+ async setMaxErrors(n: 0 | 1 | 2 | 3): Promise<void> { await this._send({ kind: 'setMaxErrors', n }); }
156
+ async setThreshold(n: number): Promise<void> { await this._send({ kind: 'setThreshold', n }); }
157
+ async setMaxResults(n: number): Promise<void> { await this._send({ kind: 'setMaxResults', n }); }
158
+ async setLanguage(lang: 'off' | 'es'): Promise<void> { await this._send({ kind: 'setLanguage', lang }); }
159
+
160
+ // Persistence — mirror of AlbexEngine.
161
+ async save(name: string): Promise<void> { await this._send({ kind: 'save', name }); }
162
+ async load(name: string): Promise<boolean> { return this._send({ kind: 'load', name }); }
163
+ async loadOrInit(name: string): Promise<boolean> { return this._send({ kind: 'loadOrInit', name }); }
164
+ async deleteSnapshot(name: string): Promise<void> { await this._send({ kind: 'deleteSnapshot', name }); }
165
+ async listSnapshots(): Promise<string[]>{ return this._send({ kind: 'listSnapshots' }); }
166
+
167
+ [Symbol.dispose](): void {
168
+ for (const [, p] of this._pending) p.reject(new AlbexError('disposed', 'Engine disposed'));
169
+ this._pending.clear();
170
+ this._worker?.terminate();
171
+ this._docsCache = [];
172
+ }
173
+ }
174
+
175
+ function rehydrateError(e: { name: string; kind?: string; message: string }): Error {
176
+ switch (e.kind) {
177
+ case 'init': return new AlbexInitError(e.message);
178
+ case 'unsupported_format': return new AlbexUnsupportedFormatError(e.message.replace(/^Unsupported format: \./, ''));
179
+ case 'parse': return new AlbexParseError('unknown', e.message);
180
+ case 'capacity': return new AlbexCapacityError(e.message);
181
+ default: {
182
+ const err = new Error(e.message);
183
+ err.name = e.name;
184
+ return err;
185
+ }
186
+ }
187
+ }