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.
- package/CHANGELOG.md +141 -0
- package/README.md +242 -112
- package/dist/albex-worker.d.ts +70 -0
- package/dist/albex-worker.d.ts.map +1 -0
- package/dist/albex-worker.js +153 -0
- package/dist/albex-worker.js.map +1 -0
- package/dist/albex.d.ts +368 -6
- package/dist/albex.d.ts.map +1 -1
- package/dist/albex.js +1692 -95
- package/dist/albex.js.map +1 -1
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +1 -0
- package/dist/gpu/bloom-runtime.d.ts +60 -0
- package/dist/gpu/bloom-runtime.d.ts.map +1 -0
- package/dist/gpu/bloom-runtime.js +176 -0
- package/dist/gpu/bloom-runtime.js.map +1 -0
- package/dist/gpu/bloom-shader.wgsl.d.ts +19 -0
- package/dist/gpu/bloom-shader.wgsl.d.ts.map +1 -0
- package/dist/gpu/bloom-shader.wgsl.js +49 -0
- package/dist/gpu/bloom-shader.wgsl.js.map +1 -0
- package/dist/persistence.d.ts +21 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +174 -0
- package/dist/persistence.js.map +1 -0
- package/dist/pool/coordinator.d.ts +98 -0
- package/dist/pool/coordinator.d.ts.map +1 -0
- package/dist/pool/coordinator.js +247 -0
- package/dist/pool/coordinator.js.map +1 -0
- package/dist/profile.d.ts +95 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +207 -0
- package/dist/profile.js.map +1 -0
- package/dist/resource-manager.d.ts +56 -0
- package/dist/resource-manager.d.ts.map +1 -0
- package/dist/resource-manager.js +138 -0
- package/dist/resource-manager.js.map +1 -0
- package/dist/tiered-store.d.ts +98 -0
- package/dist/tiered-store.d.ts.map +1 -0
- package/dist/tiered-store.js +238 -0
- package/dist/tiered-store.js.map +1 -0
- package/dist/wasm-bindings.d.ts +139 -0
- package/dist/wasm-bindings.d.ts.map +1 -0
- package/dist/wasm-bindings.js +33 -0
- package/dist/wasm-bindings.js.map +1 -0
- package/dist/worker-protocol.d.ts +86 -0
- package/dist/worker-protocol.d.ts.map +1 -0
- package/dist/worker-protocol.js +20 -0
- package/dist/worker-protocol.js.map +1 -0
- package/dist/worker-runtime.d.ts +14 -0
- package/dist/worker-runtime.d.ts.map +1 -0
- package/dist/worker-runtime.js +100 -0
- package/dist/worker-runtime.js.map +1 -0
- package/package.json +56 -13
- package/src/albex-worker.ts +187 -0
- package/src/albex.ts +1845 -130
- package/src/errors.ts +60 -0
- package/src/gpu/bloom-runtime.ts +229 -0
- package/src/gpu/bloom-shader.wgsl.ts +48 -0
- package/src/persistence.ts +175 -0
- package/src/pool/coordinator.ts +324 -0
- package/src/profile.ts +279 -0
- package/src/resource-manager.ts +167 -0
- package/src/tiered-store.ts +259 -0
- package/src/wasm-bindings.ts +200 -0
- package/src/worker-protocol.ts +48 -0
- package/src/worker-runtime.ts +96 -0
- package/wasm/pkg/albex_pdf.wasm +0 -0
- package/wasm/pkg/albex_wasm_bg.wasm +0 -0
- package/wasm/pkg/albex_wasm_mini.wasm +0 -0
- package/wasm/pkg/albex_wasm_mini_simd.wasm +0 -0
- package/wasm/pkg/albex_wasm_pro.wasm +0 -0
- package/wasm/pkg/albex_wasm_pro_simd.wasm +0 -0
- package/wasm/pkg/albex_wasm_std.wasm +0 -0
- package/wasm/pkg/albex_wasm_std_simd.wasm +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability profile detection.
|
|
3
|
+
*
|
|
4
|
+
* Probes the host environment ONCE per session and returns a structured
|
|
5
|
+
* snapshot. Used by `Albex.create()` to choose the optimal binary tier
|
|
6
|
+
* (mini/std/pro), enable SIMD when available, decide whether to spin up a
|
|
7
|
+
* worker pool, allocate WebGPU resources, etc.
|
|
8
|
+
*
|
|
9
|
+
* The function is intentionally side-effect-free except for the optional
|
|
10
|
+
* `sessionStorage` cache — every property is a synchronous-or-async probe
|
|
11
|
+
* with a sensible default if it throws.
|
|
12
|
+
*/
|
|
13
|
+
export interface DeviceProfile {
|
|
14
|
+
/** Number of logical cores reported by the browser (1-128 typical). */
|
|
15
|
+
cores: number;
|
|
16
|
+
/**
|
|
17
|
+
* Approximate device memory in GB, capped at 8 by the spec for privacy.
|
|
18
|
+
* `null` if `navigator.deviceMemory` is unavailable (Safari, Firefox).
|
|
19
|
+
*/
|
|
20
|
+
memoryGB: number | null;
|
|
21
|
+
wasm: {
|
|
22
|
+
/** WebAssembly SIMD (v128) instructions supported. */
|
|
23
|
+
simd: boolean;
|
|
24
|
+
/** Bulk memory ops (memory.copy, memory.fill) supported. */
|
|
25
|
+
bulkMemory: boolean;
|
|
26
|
+
/** Threads (Atomics + SharedArrayBuffer) supported AND page is cross-origin isolated. */
|
|
27
|
+
threads: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** `true` if `navigator.gpu` is available. Does NOT mean a device was acquired yet. */
|
|
30
|
+
webgpu: boolean;
|
|
31
|
+
/** Cross-Origin Isolation (required for SAB-based shared mode). */
|
|
32
|
+
coopCoep: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* OPFS / IndexedDB free space, if reported. Some browsers gate this behind
|
|
35
|
+
* permissions or return overly conservative values. `null` if unknown.
|
|
36
|
+
*/
|
|
37
|
+
storage: {
|
|
38
|
+
quotaBytes: number | null;
|
|
39
|
+
usageBytes: number | null;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Network conditions if reported via Network Information API (Chrome only
|
|
43
|
+
* at time of writing). Used to throttle PDF wasm prefetch on slow links.
|
|
44
|
+
*/
|
|
45
|
+
net: {
|
|
46
|
+
effectiveType: 'slow-2g' | '2g' | '3g' | '4g' | 'unknown';
|
|
47
|
+
saveData: boolean;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Battery state if Battery Status API is available (Chrome on mobile).
|
|
51
|
+
* `null` everywhere else. Used to opt into low-power mode.
|
|
52
|
+
*/
|
|
53
|
+
battery: {
|
|
54
|
+
level: number | null;
|
|
55
|
+
charging: boolean | null;
|
|
56
|
+
} | null;
|
|
57
|
+
/** Page visibility at probe time. Engines should re-listen after this. */
|
|
58
|
+
visible: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build a fresh `DeviceProfile`. The result is cached in `sessionStorage`
|
|
62
|
+
* (key `albex.profile.v1`) so subsequent calls in the same tab are free.
|
|
63
|
+
*
|
|
64
|
+
* Pass `{ fresh: true }` to bypass the cache (useful in tests or after a
|
|
65
|
+
* known capability change such as connecting to power).
|
|
66
|
+
*/
|
|
67
|
+
export declare function detectProfile(opts?: {
|
|
68
|
+
fresh?: boolean;
|
|
69
|
+
}): Promise<DeviceProfile>;
|
|
70
|
+
export type Tier = 'mini' | 'std' | 'pro';
|
|
71
|
+
/**
|
|
72
|
+
* Choose the optimal binary tier from a profile.
|
|
73
|
+
*
|
|
74
|
+
* The thresholds are conservative: a device with `deviceMemory === null`
|
|
75
|
+
* (Safari) defaults to `std` to avoid both over- and under-provisioning.
|
|
76
|
+
*/
|
|
77
|
+
export declare function pickTier(profile: DeviceProfile): Tier;
|
|
78
|
+
/**
|
|
79
|
+
* Suggested number of search/index workers given a profile.
|
|
80
|
+
*
|
|
81
|
+
* Heuristic: half of logical cores, clamped to [1, 8]. The clamp is
|
|
82
|
+
* deliberate — beyond 8 workers the postMessage coordination overhead
|
|
83
|
+
* starts eating the parallelism gain for typical Albex query shapes.
|
|
84
|
+
*
|
|
85
|
+
* Battery awareness: when battery is reported and below 20% and discharging,
|
|
86
|
+
* we shrink the pool to 1 to preserve the user's session.
|
|
87
|
+
*/
|
|
88
|
+
export declare function pickWorkerCount(profile: DeviceProfile): number;
|
|
89
|
+
/**
|
|
90
|
+
* Whether WebGPU is worth using for the current workload.
|
|
91
|
+
*
|
|
92
|
+
* Threshold is exposed so callers can tweak per index size.
|
|
93
|
+
*/
|
|
94
|
+
export declare function shouldUseGpu(profile: DeviceProfile, chunkCount: number, threshold?: number): boolean;
|
|
95
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,IAAI,EAAE;QACJ,sDAAsD;QACtD,IAAI,EAAE,OAAO,CAAC;QACd,4DAA4D;QAC5D,UAAU,EAAE,OAAO,CAAC;QACpB,yFAAyF;QACzF,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IAEF,uFAAuF;IACvF,MAAM,EAAE,OAAO,CAAC;IAEhB,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;IAEF;;;OAGG;IACH,GAAG,EAAE;QACH,aAAa,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;QAC1D,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;IAEF;;;OAGG;IACH,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;KAC1B,GAAG,IAAI,CAAC;IAET,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;CAClB;AAsGD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAqD1F;AAID,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAMrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAQ9D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAEpG"}
|
package/dist/profile.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* albex v0.3.0
|
|
3
|
+
* Zero-config local full-text search for documents — runs entirely in the browser, no server, no upload.
|
|
4
|
+
* (c) 2026 RafaCalRob
|
|
5
|
+
* @license MIT
|
|
6
|
+
* https://github.com/RafaCalRob/Albex#readme
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Capability profile detection.
|
|
10
|
+
*
|
|
11
|
+
* Probes the host environment ONCE per session and returns a structured
|
|
12
|
+
* snapshot. Used by `Albex.create()` to choose the optimal binary tier
|
|
13
|
+
* (mini/std/pro), enable SIMD when available, decide whether to spin up a
|
|
14
|
+
* worker pool, allocate WebGPU resources, etc.
|
|
15
|
+
*
|
|
16
|
+
* The function is intentionally side-effect-free except for the optional
|
|
17
|
+
* `sessionStorage` cache — every property is a synchronous-or-async probe
|
|
18
|
+
* with a sensible default if it throws.
|
|
19
|
+
*/
|
|
20
|
+
const CACHE_KEY = 'albex.profile.v1';
|
|
21
|
+
// ── WASM feature probes ──────────────────────────────────────────────────────
|
|
22
|
+
//
|
|
23
|
+
// Each probe is a minimal valid module that uses exactly one feature. If
|
|
24
|
+
// `WebAssembly.validate` returns true, the host supports it. We hand-coded
|
|
25
|
+
// the byte sequences instead of pulling in a builder; the modules are
|
|
26
|
+
// tiny and stable across spec revisions.
|
|
27
|
+
const PROBE_SIMD = new Uint8Array([
|
|
28
|
+
// magic + version
|
|
29
|
+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
|
|
30
|
+
// type section: (func (result v128))
|
|
31
|
+
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b,
|
|
32
|
+
// function section
|
|
33
|
+
0x03, 0x02, 0x01, 0x00,
|
|
34
|
+
// code section: v128.const 0
|
|
35
|
+
0x0a, 0x16, 0x01, 0x14, 0x00,
|
|
36
|
+
0xfd, 0x0c,
|
|
37
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
38
|
+
0x0b,
|
|
39
|
+
]);
|
|
40
|
+
const PROBE_BULK_MEMORY = new Uint8Array([
|
|
41
|
+
// magic + version
|
|
42
|
+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
|
|
43
|
+
// type section: (func)
|
|
44
|
+
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
|
|
45
|
+
// function section: one function of type 0
|
|
46
|
+
0x03, 0x02, 0x01, 0x00,
|
|
47
|
+
// memory section: min=1 page
|
|
48
|
+
0x05, 0x03, 0x01, 0x00, 0x01,
|
|
49
|
+
// code section
|
|
50
|
+
// section id 0x0a, section size 13
|
|
51
|
+
// func count = 1
|
|
52
|
+
// body size = 11
|
|
53
|
+
// locals = 0
|
|
54
|
+
// i32.const 0, i32.const 0, i32.const 0
|
|
55
|
+
// memory.fill (0xfc 0x0b 0x00)
|
|
56
|
+
// end (0x0b)
|
|
57
|
+
0x0a, 0x0d, 0x01, 0x0b, 0x00,
|
|
58
|
+
0x41, 0x00, 0x41, 0x00, 0x41, 0x00,
|
|
59
|
+
0xfc, 0x0b, 0x00,
|
|
60
|
+
0x0b,
|
|
61
|
+
]);
|
|
62
|
+
const PROBE_THREADS = new Uint8Array([
|
|
63
|
+
// magic + version
|
|
64
|
+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
|
|
65
|
+
// memory: shared, min=1, max=1
|
|
66
|
+
0x05, 0x04, 0x01, 0x03, 0x01, 0x01,
|
|
67
|
+
]);
|
|
68
|
+
function safeValidate(bytes) {
|
|
69
|
+
try {
|
|
70
|
+
if (typeof WebAssembly === 'undefined')
|
|
71
|
+
return false;
|
|
72
|
+
// WebAssembly.validate types insist on ArrayBuffer-backed BufferSource;
|
|
73
|
+
// copying through a fresh Uint8Array satisfies that.
|
|
74
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
75
|
+
copy.set(bytes);
|
|
76
|
+
return WebAssembly.validate(copy);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// ── Storage probe (async) ────────────────────────────────────────────────────
|
|
83
|
+
async function probeStorage() {
|
|
84
|
+
try {
|
|
85
|
+
if (typeof navigator !== 'undefined' && navigator.storage?.estimate) {
|
|
86
|
+
const est = await navigator.storage.estimate();
|
|
87
|
+
return {
|
|
88
|
+
quotaBytes: typeof est.quota === 'number' ? est.quota : null,
|
|
89
|
+
usageBytes: typeof est.usage === 'number' ? est.usage : null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch { /* fall through */ }
|
|
94
|
+
return { quotaBytes: null, usageBytes: null };
|
|
95
|
+
}
|
|
96
|
+
async function probeBattery() {
|
|
97
|
+
try {
|
|
98
|
+
// @ts-expect-error Battery API typing is sparse
|
|
99
|
+
const getBat = navigator?.getBattery?.bind(navigator);
|
|
100
|
+
if (!getBat)
|
|
101
|
+
return null;
|
|
102
|
+
const b = await getBat();
|
|
103
|
+
return { level: b.level, charging: b.charging };
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Build a fresh `DeviceProfile`. The result is cached in `sessionStorage`
|
|
112
|
+
* (key `albex.profile.v1`) so subsequent calls in the same tab are free.
|
|
113
|
+
*
|
|
114
|
+
* Pass `{ fresh: true }` to bypass the cache (useful in tests or after a
|
|
115
|
+
* known capability change such as connecting to power).
|
|
116
|
+
*/
|
|
117
|
+
export async function detectProfile(opts = {}) {
|
|
118
|
+
if (!opts.fresh) {
|
|
119
|
+
try {
|
|
120
|
+
const cached = sessionStorage.getItem(CACHE_KEY);
|
|
121
|
+
if (cached)
|
|
122
|
+
return JSON.parse(cached);
|
|
123
|
+
}
|
|
124
|
+
catch { /* ignore */ }
|
|
125
|
+
}
|
|
126
|
+
const nav = typeof navigator !== 'undefined' ? navigator : undefined;
|
|
127
|
+
const simd = safeValidate(PROBE_SIMD);
|
|
128
|
+
const bulkMemory = safeValidate(PROBE_BULK_MEMORY);
|
|
129
|
+
// Threads need both validate (shared memory) AND cross-origin isolation.
|
|
130
|
+
const sabAvailable = typeof SharedArrayBuffer !== 'undefined';
|
|
131
|
+
const coopCoep = typeof self !== 'undefined' &&
|
|
132
|
+
typeof self.crossOriginIsolated === 'boolean' &&
|
|
133
|
+
self.crossOriginIsolated === true;
|
|
134
|
+
const threadsValidated = safeValidate(PROBE_THREADS);
|
|
135
|
+
const threads = sabAvailable && coopCoep && threadsValidated;
|
|
136
|
+
const webgpu = !!(nav && 'gpu' in nav);
|
|
137
|
+
const [storage, battery] = await Promise.all([
|
|
138
|
+
probeStorage(),
|
|
139
|
+
probeBattery(),
|
|
140
|
+
]);
|
|
141
|
+
const connection = nav?.connection;
|
|
142
|
+
const profile = {
|
|
143
|
+
cores: nav?.hardwareConcurrency ?? 1,
|
|
144
|
+
memoryGB: nav?.deviceMemory ?? null,
|
|
145
|
+
wasm: { simd, bulkMemory, threads },
|
|
146
|
+
webgpu,
|
|
147
|
+
coopCoep,
|
|
148
|
+
storage,
|
|
149
|
+
net: {
|
|
150
|
+
effectiveType: connection?.effectiveType ?? 'unknown',
|
|
151
|
+
saveData: connection?.saveData ?? false,
|
|
152
|
+
},
|
|
153
|
+
battery,
|
|
154
|
+
visible: typeof document !== 'undefined'
|
|
155
|
+
? document.visibilityState === 'visible'
|
|
156
|
+
: true,
|
|
157
|
+
};
|
|
158
|
+
try {
|
|
159
|
+
sessionStorage.setItem(CACHE_KEY, JSON.stringify(profile));
|
|
160
|
+
}
|
|
161
|
+
catch { /* sessionStorage may be full or disabled */ }
|
|
162
|
+
return profile;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Choose the optimal binary tier from a profile.
|
|
166
|
+
*
|
|
167
|
+
* The thresholds are conservative: a device with `deviceMemory === null`
|
|
168
|
+
* (Safari) defaults to `std` to avoid both over- and under-provisioning.
|
|
169
|
+
*/
|
|
170
|
+
export function pickTier(profile) {
|
|
171
|
+
const m = profile.memoryGB;
|
|
172
|
+
if (m === null)
|
|
173
|
+
return 'std';
|
|
174
|
+
if (m <= 1)
|
|
175
|
+
return 'mini';
|
|
176
|
+
if (m >= 8)
|
|
177
|
+
return 'pro';
|
|
178
|
+
return 'std';
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Suggested number of search/index workers given a profile.
|
|
182
|
+
*
|
|
183
|
+
* Heuristic: half of logical cores, clamped to [1, 8]. The clamp is
|
|
184
|
+
* deliberate — beyond 8 workers the postMessage coordination overhead
|
|
185
|
+
* starts eating the parallelism gain for typical Albex query shapes.
|
|
186
|
+
*
|
|
187
|
+
* Battery awareness: when battery is reported and below 20% and discharging,
|
|
188
|
+
* we shrink the pool to 1 to preserve the user's session.
|
|
189
|
+
*/
|
|
190
|
+
export function pickWorkerCount(profile) {
|
|
191
|
+
if (profile.battery && profile.battery.level !== null
|
|
192
|
+
&& profile.battery.level < 0.2
|
|
193
|
+
&& profile.battery.charging === false) {
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
const half = Math.floor(profile.cores / 2);
|
|
197
|
+
return Math.max(1, Math.min(8, half));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Whether WebGPU is worth using for the current workload.
|
|
201
|
+
*
|
|
202
|
+
* Threshold is exposed so callers can tweak per index size.
|
|
203
|
+
*/
|
|
204
|
+
export function shouldUseGpu(profile, chunkCount, threshold = 20_000) {
|
|
205
|
+
return profile.webgpu && chunkCount > threshold;
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource manager — listens to environmental signals and exposes them as
|
|
3
|
+
* a small event API consumed by `AlbexEngine` (and `AlbexEngineWorker`).
|
|
4
|
+
*
|
|
5
|
+
* The signals tracked:
|
|
6
|
+
*
|
|
7
|
+
* - **Visibility** — `document.visibilitychange`. When the tab is hidden
|
|
8
|
+
* the engine should pause speculative work (background indexing,
|
|
9
|
+
* prefetch of optional binaries) but must still answer in-flight queries.
|
|
10
|
+
*
|
|
11
|
+
* - **Battery** — `navigator.getBattery()`. When level <20% AND not
|
|
12
|
+
* charging, switch to low-power mode (smaller worker pool, longer
|
|
13
|
+
* frame budget yields, no GPU acceleration).
|
|
14
|
+
*
|
|
15
|
+
* - **Connection** — `navigator.connection.effectiveType` + `saveData`.
|
|
16
|
+
* On `'slow-2g'/'2g'` or `saveData === true`, defer optional downloads
|
|
17
|
+
* (PDF wasm, embedding model) until the user explicitly needs them.
|
|
18
|
+
*
|
|
19
|
+
* The manager is *passive*: it does not call into the engine. Instead it
|
|
20
|
+
* exposes a `state` snapshot and an `on(event, callback)` subscription so
|
|
21
|
+
* the engine can react with its own policy. This keeps the dependency
|
|
22
|
+
* direction one-way and lets the engine be tested without the DOM.
|
|
23
|
+
*/
|
|
24
|
+
export type ResourceMode = 'normal' | 'low-power' | 'background' | 'constrained-network';
|
|
25
|
+
export interface ResourceState {
|
|
26
|
+
visible: boolean;
|
|
27
|
+
lowPower: boolean;
|
|
28
|
+
constrainedNetwork: boolean;
|
|
29
|
+
/** Composite mode derived from the three signals above. */
|
|
30
|
+
mode: ResourceMode;
|
|
31
|
+
}
|
|
32
|
+
type Listener = (state: ResourceState) => void;
|
|
33
|
+
export declare class ResourceManager {
|
|
34
|
+
private _state;
|
|
35
|
+
private _listeners;
|
|
36
|
+
private _battery;
|
|
37
|
+
private _connection;
|
|
38
|
+
private _onVisibility;
|
|
39
|
+
private _onBatteryChange;
|
|
40
|
+
private _onConnChange;
|
|
41
|
+
private _started;
|
|
42
|
+
get state(): ResourceState;
|
|
43
|
+
/** Subscribe to changes. Returns an unsubscribe function. */
|
|
44
|
+
on(cb: Listener): () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Start listening. Idempotent. Safe to call from non-browser environments
|
|
47
|
+
* (Node tests, Workers without DOM access) — missing APIs are tolerated.
|
|
48
|
+
*/
|
|
49
|
+
start(): Promise<void>;
|
|
50
|
+
/** Tear down listeners. */
|
|
51
|
+
stop(): void;
|
|
52
|
+
private _refresh;
|
|
53
|
+
}
|
|
54
|
+
export declare function getResourceManager(): ResourceManager;
|
|
55
|
+
export {};
|
|
56
|
+
//# sourceMappingURL=resource-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-manager.d.ts","sourceRoot":"","sources":["../src/resource-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,GAAG,qBAAqB,CAAC;AAEzF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,2DAA2D;IAC3D,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAgB/C,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAKZ;IACF,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,QAAQ,CAAS;IAEzB,IAAI,KAAK,IAAI,aAAa,CAAwB;IAElD,6DAA6D;IAC7D,EAAE,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,IAAI;IAK5B;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B5B,2BAA2B;IAC3B,IAAI,IAAI,IAAI;IAcZ,OAAO,CAAC,QAAQ;CAkCjB;AASD,wBAAgB,kBAAkB,IAAI,eAAe,CAGpD"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* albex v0.3.0
|
|
3
|
+
* Zero-config local full-text search for documents — runs entirely in the browser, no server, no upload.
|
|
4
|
+
* (c) 2026 RafaCalRob
|
|
5
|
+
* @license MIT
|
|
6
|
+
* https://github.com/RafaCalRob/Albex#readme
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Resource manager — listens to environmental signals and exposes them as
|
|
10
|
+
* a small event API consumed by `AlbexEngine` (and `AlbexEngineWorker`).
|
|
11
|
+
*
|
|
12
|
+
* The signals tracked:
|
|
13
|
+
*
|
|
14
|
+
* - **Visibility** — `document.visibilitychange`. When the tab is hidden
|
|
15
|
+
* the engine should pause speculative work (background indexing,
|
|
16
|
+
* prefetch of optional binaries) but must still answer in-flight queries.
|
|
17
|
+
*
|
|
18
|
+
* - **Battery** — `navigator.getBattery()`. When level <20% AND not
|
|
19
|
+
* charging, switch to low-power mode (smaller worker pool, longer
|
|
20
|
+
* frame budget yields, no GPU acceleration).
|
|
21
|
+
*
|
|
22
|
+
* - **Connection** — `navigator.connection.effectiveType` + `saveData`.
|
|
23
|
+
* On `'slow-2g'/'2g'` or `saveData === true`, defer optional downloads
|
|
24
|
+
* (PDF wasm, embedding model) until the user explicitly needs them.
|
|
25
|
+
*
|
|
26
|
+
* The manager is *passive*: it does not call into the engine. Instead it
|
|
27
|
+
* exposes a `state` snapshot and an `on(event, callback)` subscription so
|
|
28
|
+
* the engine can react with its own policy. This keeps the dependency
|
|
29
|
+
* direction one-way and lets the engine be tested without the DOM.
|
|
30
|
+
*/
|
|
31
|
+
export class ResourceManager {
|
|
32
|
+
_state = {
|
|
33
|
+
visible: true,
|
|
34
|
+
lowPower: false,
|
|
35
|
+
constrainedNetwork: false,
|
|
36
|
+
mode: 'normal',
|
|
37
|
+
};
|
|
38
|
+
_listeners = new Set();
|
|
39
|
+
_battery = null;
|
|
40
|
+
_connection = null;
|
|
41
|
+
_onVisibility = () => this._refresh();
|
|
42
|
+
_onBatteryChange = () => this._refresh();
|
|
43
|
+
_onConnChange = () => this._refresh();
|
|
44
|
+
_started = false;
|
|
45
|
+
get state() { return this._state; }
|
|
46
|
+
/** Subscribe to changes. Returns an unsubscribe function. */
|
|
47
|
+
on(cb) {
|
|
48
|
+
this._listeners.add(cb);
|
|
49
|
+
return () => this._listeners.delete(cb);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Start listening. Idempotent. Safe to call from non-browser environments
|
|
53
|
+
* (Node tests, Workers without DOM access) — missing APIs are tolerated.
|
|
54
|
+
*/
|
|
55
|
+
async start() {
|
|
56
|
+
if (this._started)
|
|
57
|
+
return;
|
|
58
|
+
this._started = true;
|
|
59
|
+
if (typeof document !== 'undefined' && document.addEventListener) {
|
|
60
|
+
document.addEventListener('visibilitychange', this._onVisibility);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
// @ts-expect-error Battery API is non-standard in TS typings
|
|
64
|
+
const getBat = navigator?.getBattery?.bind(navigator);
|
|
65
|
+
if (getBat) {
|
|
66
|
+
const b = await getBat();
|
|
67
|
+
this._battery = b;
|
|
68
|
+
b.addEventListener?.('levelchange', this._onBatteryChange);
|
|
69
|
+
b.addEventListener?.('chargingchange', this._onBatteryChange);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch { /* unavailable; tolerate */ }
|
|
73
|
+
const conn = navigator?.connection;
|
|
74
|
+
if (conn) {
|
|
75
|
+
this._connection = conn;
|
|
76
|
+
conn.addEventListener?.('change', this._onConnChange);
|
|
77
|
+
}
|
|
78
|
+
this._refresh();
|
|
79
|
+
}
|
|
80
|
+
/** Tear down listeners. */
|
|
81
|
+
stop() {
|
|
82
|
+
if (!this._started)
|
|
83
|
+
return;
|
|
84
|
+
this._started = false;
|
|
85
|
+
if (typeof document !== 'undefined' && document.removeEventListener) {
|
|
86
|
+
document.removeEventListener('visibilitychange', this._onVisibility);
|
|
87
|
+
}
|
|
88
|
+
this._battery?.removeEventListener?.('levelchange', this._onBatteryChange);
|
|
89
|
+
this._battery?.removeEventListener?.('chargingchange', this._onBatteryChange);
|
|
90
|
+
this._connection?.removeEventListener?.('change', this._onConnChange);
|
|
91
|
+
this._battery = null;
|
|
92
|
+
this._connection = null;
|
|
93
|
+
}
|
|
94
|
+
_refresh() {
|
|
95
|
+
const visible = typeof document !== 'undefined'
|
|
96
|
+
? document.visibilityState === 'visible'
|
|
97
|
+
: true;
|
|
98
|
+
const lowPower = !!(this._battery
|
|
99
|
+
&& this._battery.level < 0.2
|
|
100
|
+
&& this._battery.charging === false);
|
|
101
|
+
const conn = this._connection;
|
|
102
|
+
const constrainedNetwork = !!conn && (conn.saveData === true ||
|
|
103
|
+
conn.effectiveType === 'slow-2g' ||
|
|
104
|
+
conn.effectiveType === '2g');
|
|
105
|
+
let mode = 'normal';
|
|
106
|
+
if (!visible)
|
|
107
|
+
mode = 'background';
|
|
108
|
+
else if (lowPower)
|
|
109
|
+
mode = 'low-power';
|
|
110
|
+
else if (constrainedNetwork)
|
|
111
|
+
mode = 'constrained-network';
|
|
112
|
+
const next = { visible, lowPower, constrainedNetwork, mode };
|
|
113
|
+
if (next.visible === this._state.visible &&
|
|
114
|
+
next.lowPower === this._state.lowPower &&
|
|
115
|
+
next.constrainedNetwork === this._state.constrainedNetwork &&
|
|
116
|
+
next.mode === this._state.mode)
|
|
117
|
+
return;
|
|
118
|
+
this._state = next;
|
|
119
|
+
for (const cb of this._listeners) {
|
|
120
|
+
try {
|
|
121
|
+
cb(next);
|
|
122
|
+
}
|
|
123
|
+
catch { /* swallow listener errors */ }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Singleton accessor. Multiple engines in the same realm share the same
|
|
129
|
+
* manager — there is no benefit to running the listeners more than once,
|
|
130
|
+
* and the signal is global to the page anyway.
|
|
131
|
+
*/
|
|
132
|
+
let _instance = null;
|
|
133
|
+
export function getResourceManager() {
|
|
134
|
+
if (!_instance)
|
|
135
|
+
_instance = new ResourceManager();
|
|
136
|
+
return _instance;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=resource-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-manager.js","sourceRoot":"","sources":["../src/resource-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiered storage layer for Albex.
|
|
3
|
+
*
|
|
4
|
+
* The base engine keeps every indexed document in memory inside the BSS
|
|
5
|
+
* arrays. That works beautifully up to the tier's capacity (4 MB to 128 MB
|
|
6
|
+
* of indexed text) but breaks when the user wants to search across more.
|
|
7
|
+
*
|
|
8
|
+
* `TieredStore` adds two memory tiers behind the engine:
|
|
9
|
+
*
|
|
10
|
+
* HOT — already-indexed documents living in the WASM arrays.
|
|
11
|
+
* WARM — original file blobs serialised in OPFS, NOT in the engine.
|
|
12
|
+
*
|
|
13
|
+
* Eviction happens when text capacity climbs above a configurable
|
|
14
|
+
* threshold (default 85 %). The least-recently-accessed HOT document is
|
|
15
|
+
* removed from the engine; its blob stays in OPFS so we can promote it
|
|
16
|
+
* back later without asking the user to re-pick the file.
|
|
17
|
+
*
|
|
18
|
+
* Promotion happens explicitly: callers tell the store "I want these
|
|
19
|
+
* names searchable again" and we re-feed them through `engine.indexFile`.
|
|
20
|
+
*
|
|
21
|
+
* **Trade-off vs storing the internal representation in OPFS:** promoting
|
|
22
|
+
* a doc means re-parsing it (DOCX XML decode, etc.). For typical document
|
|
23
|
+
* sizes this is 20-200 ms — negligible for an explicit "search across the
|
|
24
|
+
* archive" action. The win is huge: the persistence format is just the
|
|
25
|
+
* source files, so it survives engine version bumps without any migration.
|
|
26
|
+
*/
|
|
27
|
+
import type { AlbexEngine, IndexedDocument } from './albex.js';
|
|
28
|
+
export interface TieredStoreOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Evict when textUsed exceeds `evictThreshold * textCapacity`.
|
|
31
|
+
* Default 0.85. Set 1.0 to disable.
|
|
32
|
+
*/
|
|
33
|
+
evictThreshold?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Keep this many documents in the hot tier at minimum. Default 1.
|
|
36
|
+
* Useful when you want to ensure the most recently added file is
|
|
37
|
+
* always available without an explicit promote step.
|
|
38
|
+
*/
|
|
39
|
+
hotFloor?: number;
|
|
40
|
+
}
|
|
41
|
+
export declare class TieredStore {
|
|
42
|
+
private readonly _engine;
|
|
43
|
+
private _entries;
|
|
44
|
+
private _dir;
|
|
45
|
+
private readonly _opts;
|
|
46
|
+
constructor(engine: AlbexEngine, opts?: TieredStoreOptions);
|
|
47
|
+
/** Ensure the OPFS directory exists. Idempotent. Tolerated when OPFS is unavailable. */
|
|
48
|
+
init(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Index a file AND register it in the warm tier so it survives across
|
|
51
|
+
* sessions. Equivalent to `engine.indexFile(file)` but with the extra
|
|
52
|
+
* persistence guarantee.
|
|
53
|
+
*/
|
|
54
|
+
indexFile(file: File): Promise<IndexedDocument>;
|
|
55
|
+
/** Touch an entry to mark it recently used (for LRU). */
|
|
56
|
+
touch(name: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Evict the least-recently-used HOT documents until `textUsed` falls
|
|
59
|
+
* below the configured threshold. Respects `hotFloor` (never evicts
|
|
60
|
+
* the last N docs).
|
|
61
|
+
*/
|
|
62
|
+
_enforceCapacity(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Bring a warm document back into the engine. No-op if already hot.
|
|
65
|
+
* Returns the resulting `IndexedDocument` or `null` if the doc isn't known.
|
|
66
|
+
*/
|
|
67
|
+
promote(name: string): Promise<IndexedDocument | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Forget a document entirely: remove from engine and delete its OPFS blob.
|
|
70
|
+
* Returns whether the entry existed.
|
|
71
|
+
*/
|
|
72
|
+
forget(name: string): Promise<boolean>;
|
|
73
|
+
/** Names of all known documents, hot or warm. */
|
|
74
|
+
list(): {
|
|
75
|
+
name: string;
|
|
76
|
+
hot: boolean;
|
|
77
|
+
byteSize: number;
|
|
78
|
+
}[];
|
|
79
|
+
/** Aggregate storage stats. */
|
|
80
|
+
getTierStats(): {
|
|
81
|
+
hot: number;
|
|
82
|
+
warm: number;
|
|
83
|
+
totalBytes: number;
|
|
84
|
+
};
|
|
85
|
+
private _safeName;
|
|
86
|
+
private _writeBlob;
|
|
87
|
+
private _readBlob;
|
|
88
|
+
private _deleteBlob;
|
|
89
|
+
/**
|
|
90
|
+
* TC39 explicit-resource-management hook. Drops the in-memory index and
|
|
91
|
+
* the OPFS directory handle. The underlying OPFS blobs are NOT deleted —
|
|
92
|
+
* disposal only frees JS-side state. Use `forget()` per-doc or
|
|
93
|
+
* `deleteOpfsAll()` if you want to wipe persisted data.
|
|
94
|
+
*/
|
|
95
|
+
[Symbol.dispose](): void;
|
|
96
|
+
private _rehydrateIndex;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=tiered-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tiered-store.d.ts","sourceRoot":"","sources":["../src/tiered-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAc/D,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,IAAI,CAA0C;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;gBAEzC,MAAM,EAAE,WAAW,EAAE,IAAI,GAAE,kBAAuB;IAQ9D,wFAAwF;IAClF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3B;;;;OAIG;IACG,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC;IAmBrD,yDAAyD;IACzD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKzB;;;;OAIG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBvC;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAqB5D;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ5C,iDAAiD;IACjD,IAAI,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE;IAM1D,+BAA+B;IAC/B,YAAY,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAWjE,OAAO,CAAC,SAAS;YAKH,UAAU;YAeV,SAAS;YAUT,WAAW;IAKzB;;;;;OAKG;IACH,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;YAKV,eAAe;CAoB9B"}
|