albex 0.1.0 → 0.6.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 +416 -0
- package/README.md +244 -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 +508 -6
- package/dist/albex.d.ts.map +1 -1
- package/dist/albex.js +1911 -141
- package/dist/albex.js.map +1 -1
- package/dist/errors.d.ts +52 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +66 -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 +100 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +200 -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 +180 -0
- package/dist/wasm-bindings.d.ts.map +1 -0
- package/dist/wasm-bindings.js +128 -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 +109 -0
- package/dist/worker-runtime.js.map +1 -0
- package/package.json +60 -13
- package/src/albex-worker.ts +187 -0
- package/src/albex.ts +2136 -189
- package/src/errors.ts +76 -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 +280 -0
- package/src/resource-manager.ts +167 -0
- package/src/tiered-store.ts +259 -0
- package/src/wasm-bindings.ts +349 -0
- package/src/worker-protocol.ts +48 -0
- package/src/worker-runtime.ts +106 -0
- package/wasm/pkg/albex_pdf.wasm +0 -0
- package/wasm/pkg/albex_wasm.wasm +0 -0
- package/wasm/pkg/albex_wasm_bg.wasm +0 -0
- package/wasm/pkg/albex_wasm_simd.wasm +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Albex are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and Albex follows [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [0.6.0] — 2026-05-31
|
|
9
|
+
|
|
10
|
+
Release de auditoría algorítmica + robustez. Cierra los hallazgos #1–#8 de
|
|
11
|
+
la revisión externa en profundidad: sube la selectividad del pre-filtro de
|
|
12
|
+
forma drástica, elimina dos clases de corrupción/pérdida silenciosa de
|
|
13
|
+
datos, y de paso corrige una falta de _soundness_ del filtro Bloom bajo
|
|
14
|
+
matching difuso que existía desde el principio. El binario crece ~2 KB
|
|
15
|
+
(33 KB baseline · 37 KB SIMD) por el código del pre-filtro de trigramas.
|
|
16
|
+
|
|
17
|
+
### Breaking changes
|
|
18
|
+
|
|
19
|
+
- **ABI WASM v2 → v3** (hallazgo #7). El binario principal exporta ahora
|
|
20
|
+
`getLastIndexOverflow` y el host fija el rango ABI aceptado a `[3, 3]`
|
|
21
|
+
(antes `[1, 2]`, con un mínimo inalcanzable que la lista de exports
|
|
22
|
+
requeridos ya hacía imposible). Un `.wasm` cacheado de 0.5.x falla con
|
|
23
|
+
`AlbexAbiMismatchError` claro en `init()` en vez de petar más tarde.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **Pre-filtro de trigramas q-gram** (hallazgo #1). El Bloom de 64 bits
|
|
28
|
+
(un bit por `c & 0x3F`) está saturado en chunks de prosa de 512 bytes —
|
|
29
|
+
cada chunk contiene casi todo el alfabeto, así que no poda y el Bitap
|
|
30
|
+
acababa ejecutándose sobre todo el corpus. Se añade una firma de 256
|
|
31
|
+
bits de los **trigramas** de cada chunk (`CHUNK_SIG`, BSS, no infla el
|
|
32
|
+
`.wasm`) como segundo pre-filtro mucho más selectivo. Es **sound bajo
|
|
33
|
+
matching difuso**: una ocurrencia con `e` errores conserva ≥ `N − 3e`
|
|
34
|
+
de los trigramas exactos del token (lema q-gram), y la firma no tiene
|
|
35
|
+
falsos negativos, así que nunca descarta un match real — solo poda
|
|
36
|
+
chunks que demostrablemente no pueden contenerlo. El Bitap confirma
|
|
37
|
+
cada superviviente: la corrección no cambia, solo se encoge el conjunto
|
|
38
|
+
candidato.
|
|
39
|
+
- **`AlbexCapacityError` con campo `limit`** (`'chunks' | 'text' | 'docs'
|
|
40
|
+
| 'names'`). El nuevo export `getLastIndexOverflow()` señaliza qué pool
|
|
41
|
+
se llenó durante el último `begin..endDocument`.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **Pérdida silenciosa de datos al agotar capacidad** (hallazgo #3). Los
|
|
46
|
+
pools del WASM se llenaban en silencio y `indexFile` devolvía un
|
|
47
|
+
documento a medio indexar indistinguible de uno completo (`getStats()`
|
|
48
|
+
mentía). Ahora `indexFile` lee el overflow y lanza `AlbexCapacityError`.
|
|
49
|
+
El documento que desborda se **revierte atómicamente** en el WASM
|
|
50
|
+
(`endDocument` restaura `chunk_count`/`text_used`/`name_used` al inicio
|
|
51
|
+
del doc): indexación all-or-nothing por fichero, sin chunks huérfanos
|
|
52
|
+
sin nombre.
|
|
53
|
+
- **Re-entrancy en el path async** (hallazgo #2). Una única instancia WASM
|
|
54
|
+
con estado global y ops async que ceden al scheduler entre slices: dos
|
|
55
|
+
operaciones solapadas se corrompían (un `searchBegin` nuevo reseteaba el
|
|
56
|
+
cursor de una búsqueda cooperativa en vuelo). Las ops async se serializan
|
|
57
|
+
ahora con una cola interna; los mutadores/búsquedas síncronos rechazan
|
|
58
|
+
ejecutarse a media operación (`AlbexError` kind `'busy'`) en vez de
|
|
59
|
+
corromper. El worker-runtime procesa mensajes estrictamente en orden.
|
|
60
|
+
- **Bloom no-sound bajo fuzzy** (corolario del #1). El filtro Bloom de
|
|
61
|
+
caracteres rechazaba un match aproximado cuando el carácter sustituido
|
|
62
|
+
no estaba en el chunk (bug latente pre-0.6.0). Ahora el Bloom solo se
|
|
63
|
+
aplica a tokens exactos (`eff_errors == 0`); los tokens difusos se
|
|
64
|
+
filtran solo con el conteo de trigramas, que sí es sound.
|
|
65
|
+
- **Frase + `windowed`** (hallazgo #7). El post-filtro de frase corría
|
|
66
|
+
contra el snippet recortado, así que `{ windowed: true }` podía descartar
|
|
67
|
+
un match válido cuyo segundo término caía fuera de la ventana. Ahora la
|
|
68
|
+
comprobación de adyacencia corre contra el **texto completo** del chunk;
|
|
69
|
+
el windowing solo afecta a la visualización.
|
|
70
|
+
- **GPU + OR**: el hash del pre-filtro GPU usaba siempre el patrón de la
|
|
71
|
+
rama 0, generando una máscara de candidatos errónea para las ramas i≠0 de
|
|
72
|
+
una query OR y descartando sus hits en silencio (hallazgo #6).
|
|
73
|
+
- **Corte de chunk a mitad de codepoint UTF-8** (hallazgo #7): el corte duro
|
|
74
|
+
a 512 bytes podía partir una secuencia multibyte; ahora retrocede a la
|
|
75
|
+
frontera de codepoint y el snippet del borde no renderiza `�`.
|
|
76
|
+
- **`replaceDocument` sin reclamar espacio** (hallazgo #7): repetidos
|
|
77
|
+
replaces dejaban tombstones en el text pool; ahora se compacta de forma
|
|
78
|
+
oportunista bajo presión.
|
|
79
|
+
|
|
80
|
+
### Performance
|
|
81
|
+
|
|
82
|
+
- `prepareQuery` ya no hace un `memset` de 64 KB en pila por query (hallazgo
|
|
83
|
+
#4). La query de trabajo se acota a `MAX_QUERY_BYTES` (1 KB).
|
|
84
|
+
|
|
85
|
+
### Tooling
|
|
86
|
+
|
|
87
|
+
- `npm run relaunch`: limpia artefactos, recompila WASM (baseline + SIMD) +
|
|
88
|
+
PDF + TS + OCR, corre los tests, empaqueta la librería (`npm pack`) y
|
|
89
|
+
levanta el demo en `http://localhost:5173/demo/`. Scripts auxiliares
|
|
90
|
+
`clean` / `clean:all` / `build:ocr`.
|
|
91
|
+
- Cobertura: +16 tests (110 en total) — selectividad del trigram, soundness
|
|
92
|
+
fuzzy, supervivencia de firmas tras compact/restore, error de capacidad,
|
|
93
|
+
guard de concurrencia, frase+windowed.
|
|
94
|
+
|
|
95
|
+
## [0.5.0] — 2026-05-30
|
|
96
|
+
|
|
97
|
+
Release de endurecimiento — cierra las cinco clases de bugs accionables
|
|
98
|
+
de la auditoría externa de código. Sin nuevas features que pidan datos
|
|
99
|
+
reales para ser correctas. El binario crece ~2 KB respecto a 0.4.0 por
|
|
100
|
+
la lógica de query parsing en Rust + ABI version.
|
|
101
|
+
|
|
102
|
+
### Breaking changes
|
|
103
|
+
|
|
104
|
+
- **`@albex/ocr` ahora requiere `engine.attachOcr()`** (audit 3.8). El
|
|
105
|
+
patrón anterior de mutar `engine.ocrImage = ...` y
|
|
106
|
+
`engine.ocrConfig = ...` directamente queda eliminado. Para
|
|
107
|
+
integradores que usaban `@albex/ocr`, **el cambio es transparente** —
|
|
108
|
+
`enableOcr(engine)` sigue siendo la misma llamada. Para quien tuviera
|
|
109
|
+
un adaptador manual: usar `engine.attachOcr({ recognize, options })`.
|
|
110
|
+
- **Tiers eliminados** (audit 4.1). Mini/std/pro × baseline/SIMD (6
|
|
111
|
+
binarios) consolidados a **baseline + SIMD** (2 binarios). El
|
|
112
|
+
parámetro `tier` de `AlbexOptions` queda como noop deprecado. El alias
|
|
113
|
+
`albex_wasm_bg.wasm` se mantiene para compatibilidad con `0.4.x`.
|
|
114
|
+
- `pickTier(profile)` siempre devuelve `'std'` ahora. La función queda
|
|
115
|
+
exportada para compatibilidad de código fuente, no de comportamiento.
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- **Validación runtime de la ABI WASM** (audit 3.2). `asAlbexExports` y
|
|
120
|
+
`asAlbexPdfExports` ya no son `as unknown as` ceremoniales — verifican
|
|
121
|
+
que `memory` sea una `WebAssembly.Memory`, que cada export requerido
|
|
122
|
+
exista, y que `abiVersion()` esté en el rango soportado. Lanzan
|
|
123
|
+
`AlbexAbiMismatchError` con la lista de exports faltantes cuando algo
|
|
124
|
+
no encaja. Antes, un binario incompatible instanciaba en silencio y
|
|
125
|
+
petaba en el primer call site que tocaba la función ausente.
|
|
126
|
+
- **`makePdfWasmImports` falla rápido** ante imports desconocidos
|
|
127
|
+
(compatibilidad con el cambio anterior de 0.3.1; ya estaba pero ahora
|
|
128
|
+
alineado con la ABI version del módulo).
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- **Canal de diagnósticos estructurado** (audit 3.6). Los `console.warn`
|
|
133
|
+
diseminados por el path de indexación desaparecen — los reemplaza un
|
|
134
|
+
buffer interno de `AlbexDiagnostic[]` consultable con
|
|
135
|
+
`engine.takeDiagnostics()`. Cada entrada es `{kind, stage, message, file?,
|
|
136
|
+
page?}` con `kind` en `'recovered' | 'skipped' | 'fallback' | 'info'`.
|
|
137
|
+
Cubre: fallback PDF→OCR, OCR fail por imagen, GPU caída a CPU, descarga
|
|
138
|
+
PDF WASM en red restringida. Capped a 256 entradas para no explotar en
|
|
139
|
+
corpus muy corruptos. La API de "best-effort" se mantiene, pero el
|
|
140
|
+
caller ahora puede inspeccionar qué se perdió.
|
|
141
|
+
- **`engine.attachOcr(adapter)`** — extension point formal. Devuelve un
|
|
142
|
+
`OcrHandle` con `dispose()`. El motor valida el contrato y rechaza
|
|
143
|
+
un segundo `attachOcr` mientras haya uno activo. La propiedad pública
|
|
144
|
+
`engine.ocrImage` se mantiene como getter de feature-detect pero no es
|
|
145
|
+
asignable — para evitar el patrón anti-encapsulación del audit.
|
|
146
|
+
- **`abiVersion()` exportada por ambos módulos WASM**. Main = v2 (incluye
|
|
147
|
+
query parser nuevo); PDF = v3 (incluye image extraction). El validador
|
|
148
|
+
TS rechaza binarios fuera de rango.
|
|
149
|
+
|
|
150
|
+
### Architecture — query parsing moves to WASM (audit "two truths")
|
|
151
|
+
|
|
152
|
+
Pre-0.5.0 el TypeScript dueño de `parseQuery`, `tokenize`,
|
|
153
|
+
`tokensToWasmQuery`, mientras Rust tokenizaba al indexar. Dos verdades
|
|
154
|
+
sobre qué era un "token".
|
|
155
|
+
|
|
156
|
+
- Nuevos exports WASM: `prepareQuery`, `getQueryKind`,
|
|
157
|
+
`getQueryBranchCount`, `getQueryBranchPattern`, `selectQueryBranch`.
|
|
158
|
+
- Hasta **8 branches OR** soportadas, **4 tokens por branch**, **256
|
|
159
|
+
bytes por pattern compilado** — todo en static BSS, sin alocación.
|
|
160
|
+
- `containsPhrase` queda en TS porque opera sobre snippets (output del
|
|
161
|
+
WASM), no sobre la query — no es divergencia de tokenizer.
|
|
162
|
+
- `parseQuery`, `tokenize`, `tokensToWasmQuery` eliminados del TS.
|
|
163
|
+
- Un único algoritmo de "qué es un token" entre indexación y querying.
|
|
164
|
+
|
|
165
|
+
### Build & maintenance
|
|
166
|
+
|
|
167
|
+
- **`prepublishOnly` rebuildea WASM + corre tests** desde 0.3.1, ya
|
|
168
|
+
garantizado en 0.5.0.
|
|
169
|
+
- Build pipeline simplificada: `scripts/build-wasm.mjs` produce solo
|
|
170
|
+
dos binarios. `npm pack --dry-run` muestra 4 archivos `.wasm` en lugar
|
|
171
|
+
de 8.
|
|
172
|
+
- `wasm/Cargo.toml` añade `wee_alloc` (~1 KB) para el staging Vec del
|
|
173
|
+
restore atómico de 0.4.0.
|
|
174
|
+
|
|
175
|
+
### Tests
|
|
176
|
+
|
|
177
|
+
- 94 vitest cases verdes (era 88 en 0.4.0). Cinco tests del tier matrix
|
|
178
|
+
eliminados (mini/std/pro ya no existen). Tests nuevos:
|
|
179
|
+
- `tests/abi-validation.test.ts` (5): valida que `AlbexAbiMismatchError`
|
|
180
|
+
se lanza ante exports faltantes, abiVersion fuera de rango, memory
|
|
181
|
+
inválida.
|
|
182
|
+
- `tests/diagnostics.test.ts` (4): valida `takeDiagnostics()` drena,
|
|
183
|
+
cap a 256, reset limpia.
|
|
184
|
+
|
|
185
|
+
### Postponed con razón
|
|
186
|
+
|
|
187
|
+
Cosas del audit que NO se cierran en 0.5.0 porque cerrarlas sin datos
|
|
188
|
+
reales sería adivinar:
|
|
189
|
+
|
|
190
|
+
- **3.5 OCR paralelización**: optimización sin profiling no es ingeniería.
|
|
191
|
+
- **3.9 Adaptive runtime con métricas reales**: requiere corpus y uso
|
|
192
|
+
reales para validar decisiones.
|
|
193
|
+
- **4.3 GPU equivalence test**: requiere corpus >20k chunks que aún no
|
|
194
|
+
está checked in.
|
|
195
|
+
- **7 parsers lite a WASM**: ~3 semanas serias. Separable. No es bug
|
|
196
|
+
fix, es mejora arquitectural más limpia con tiempo dedicado.
|
|
197
|
+
|
|
198
|
+
## [0.4.0] — 2026-05-30
|
|
199
|
+
|
|
200
|
+
Cierre de dos clases enteras de bugs identificadas por la auditoría externa
|
|
201
|
+
de código. Sin cambios cosméticos — el binario crece ~4 KB por la lógica
|
|
202
|
+
de atomicidad y el allocator necesario para el staging buffer.
|
|
203
|
+
|
|
204
|
+
### Fixed — atomic snapshot restore (audit 3.4)
|
|
205
|
+
|
|
206
|
+
- **Snapshot v3 con formato por campos**. Reemplaza la copia byte a byte
|
|
207
|
+
de los structs internos `Chunk`/`DocEntry` (`from_raw_parts`) por un
|
|
208
|
+
encoding explícito little-endian. El formato deja de depender del
|
|
209
|
+
layout en memoria de Rust, del target, del padding o de cambios en
|
|
210
|
+
los tipos. Lo que va al disco es un contrato.
|
|
211
|
+
- **`restoreCommit()` — protocolo de 3 fases atómico**. El antiguo
|
|
212
|
+
`restoreBegin` reseteaba el estado y escribía los counters antes de
|
|
213
|
+
recibir un solo byte del payload. Si `restoreFeed` fallaba a mitad,
|
|
214
|
+
el corpus previo quedaba destruido. v3 acumula todo el payload en un
|
|
215
|
+
staging buffer y solo aplica al estado vivo cuando `restoreCommit`
|
|
216
|
+
valida que el tamaño completo coincide con el header. Un commit
|
|
217
|
+
fallido deja el motor con el corpus previo intacto.
|
|
218
|
+
- **Compatibilidad backwards**. v1 y v2 siguen cargando — para ellos
|
|
219
|
+
`restoreBegin` mantiene la semántica vieja (no-atómica) y
|
|
220
|
+
`restoreCommit` es no-op que devuelve 1. El primer `save()` tras
|
|
221
|
+
cargar un snapshot viejo lo reescribe como v3.
|
|
222
|
+
- Binarios crecen ~4 KB por la lógica nueva y por `wee_alloc` (única
|
|
223
|
+
fuente de alocación en el módulo, usada por el staging Vec).
|
|
224
|
+
|
|
225
|
+
### Fixed — single source of truth for content hash (audit "two truths")
|
|
226
|
+
|
|
227
|
+
- **FNV-1a 64-bit ahora vive en Rust**. La implementación TypeScript que
|
|
228
|
+
duplicaba el algoritmo desaparece. Tres nuevos exports
|
|
229
|
+
(`hashBegin`/`hashFeed`/`hashFinish`) implementan el hash en streaming
|
|
230
|
+
para archivos mayores que el scratchpad. El método privado del engine
|
|
231
|
+
`_contentHash` produce exactamente el mismo string hex de 16
|
|
232
|
+
caracteres que devolvía la versión TS — ningún caller cambia.
|
|
233
|
+
|
|
234
|
+
### Added — tests
|
|
235
|
+
|
|
236
|
+
- `tests/load-restores-docs.test.ts`: nuevo test "a v3 restore that
|
|
237
|
+
never commits leaves the previous index intact". Verifica
|
|
238
|
+
explícitamente la atomicidad: trunca el payload de un snapshot al
|
|
239
|
+
75 %, intenta cargarlo, verifica que `load()` devuelve `false` y que
|
|
240
|
+
el corpus previo sigue indexado y consultable.
|
|
241
|
+
- `tests/hash.test.ts`: reescrito para validar el hash WASM contra el
|
|
242
|
+
engine real (la versión vieja era una re-implementación TS standalone
|
|
243
|
+
comparándose consigo misma). Cubre shape, determinismo, sensibilidad
|
|
244
|
+
a un byte, FNV offset basis, streaming sobre 96 KB (> scratchpad).
|
|
245
|
+
- 88 tests verdes (era 85 en 0.3.1).
|
|
246
|
+
|
|
247
|
+
### Postponed
|
|
248
|
+
|
|
249
|
+
- Mover el tokenizador y query parser a WASM (audit "wrapper TS hace
|
|
250
|
+
demasiado") se traslada a 0.5.0. Es mejora arquitectural, no cierre
|
|
251
|
+
de bug — y tiene suficientes trade-offs de diseño (semánticas de OR,
|
|
252
|
+
post-filter de phrase) como para no publicar una API a medio cocer.
|
|
253
|
+
|
|
254
|
+
## [0.3.1] — 2026-05-30
|
|
255
|
+
|
|
256
|
+
Hardening pass after an external code audit. No new features; three
|
|
257
|
+
specific issues addressed.
|
|
258
|
+
|
|
259
|
+
### Fixed
|
|
260
|
+
|
|
261
|
+
- **Debug logs removed from the indexing hot path.** Three `console.log`
|
|
262
|
+
statements added during the OCR-worker-abort diagnostic session were
|
|
263
|
+
firing on every PDF (hybrid OCR decision) and every embedded image
|
|
264
|
+
(kind / len / magic-byte trace). They are gone; the legitimate
|
|
265
|
+
`console.warn` messages for actual failures stay.
|
|
266
|
+
|
|
267
|
+
- **`makePdfWasmImports` now fails fast on unknown imports.** Previously
|
|
268
|
+
any unrecognised import was satisfied with a `console.warn` stub,
|
|
269
|
+
which let the module instantiate and defer the real failure to an
|
|
270
|
+
arbitrary call inside `extractPdf`. The loader now throws
|
|
271
|
+
`AlbexInitError` at boot with a clear "rebuild your binary" message.
|
|
272
|
+
An unknown import means the wasm-bindgen / lopdf / getrandom graph
|
|
273
|
+
drifted from what this loader was written for; better to surface that
|
|
274
|
+
immediately than to hang or crash mid-extraction.
|
|
275
|
+
|
|
276
|
+
- **`prepublishOnly` now rebuilds every WASM artifact and runs the
|
|
277
|
+
entire test suite.** It was running only `tsc + banner.mjs`, which
|
|
278
|
+
meant the WASM binaries published to npm could be out of sync with
|
|
279
|
+
the current Rust source. The script is now `npm run build:all && npm
|
|
280
|
+
test`. Publishing takes longer, but the package is guaranteed to
|
|
281
|
+
contain binaries reproducible from the source it ships.
|
|
282
|
+
|
|
283
|
+
## [0.3.0] — 2026-05-30
|
|
284
|
+
|
|
285
|
+
### Hybrid PDF OCR (opt-in)
|
|
286
|
+
|
|
287
|
+
- New `@albex/ocr` option `alwaysExtractEmbeddedImages: boolean` (default
|
|
288
|
+
`false`). When enabled, the engine OCRs the embedded images of EVERY
|
|
289
|
+
PDF on top of the normal text extraction — catching text that lives
|
|
290
|
+
only inside scanned annexes, stamps, signatures, or screenshots inside
|
|
291
|
+
otherwise-native PDFs.
|
|
292
|
+
- Demo exposes the flag as a checkbox in the OCR panel; status shows
|
|
293
|
+
`ready (spa, hybrid)` when active.
|
|
294
|
+
|
|
295
|
+
### PDF parse-crash → OCR fallback
|
|
296
|
+
|
|
297
|
+
- When `extractPdf` traps (pdf-extract crashes on a PDF that other tools
|
|
298
|
+
read fine), the engine now re-instantiates the WASM and tries the
|
|
299
|
+
lopdf-only image-extraction path before throwing. With OCR wired, many
|
|
300
|
+
formerly "unsupported encoding" PDFs become searchable.
|
|
301
|
+
- Error message updated: instead of misleading "the file may be
|
|
302
|
+
malformed", users see clear guidance pointing at OCR as the recovery
|
|
303
|
+
path.
|
|
304
|
+
|
|
305
|
+
### Demo sandbox
|
|
306
|
+
|
|
307
|
+
- Importmap to jsDelivr for `tesseract.js` (only loaded when the user
|
|
308
|
+
enables OCR).
|
|
309
|
+
- Full OCR panel: language select, hybrid-mode checkbox, lifecycle
|
|
310
|
+
status.
|
|
311
|
+
- Two fixture PDFs in `demo/fixtures/` for end-to-end testing:
|
|
312
|
+
`hybrid-test.pdf` (vector text + embedded image with text) and
|
|
313
|
+
`scanned-only-test.pdf` (100% image, no vector text).
|
|
314
|
+
- Global `window.onerror` + `unhandledrejection` handlers so Tesseract
|
|
315
|
+
worker aborts surface as Log entries instead of crashing the page.
|
|
316
|
+
- New `npm run serve` script wraps `npx serve -p 5173` for reproducible
|
|
317
|
+
local testing.
|
|
318
|
+
|
|
319
|
+
### Breaking changes
|
|
320
|
+
|
|
321
|
+
- **`searchStream` renamed to `searchCooperative`.** The original name
|
|
322
|
+
implied incremental streaming, which the method never provided — it
|
|
323
|
+
yields to the scheduler between slices and then returns a batch.
|
|
324
|
+
The new name is honest. `searchStream` is kept as a deprecated alias
|
|
325
|
+
on `AlbexEngine`, `AlbexEngineWorker` and `AlbexPool`; it logs a
|
|
326
|
+
one-time `console.warn` on first call and will be removed in 0.4.0.
|
|
327
|
+
|
|
328
|
+
- **Snapshot format bumped to v2.** Existing v1 snapshots still load —
|
|
329
|
+
their documents come back with empty `contentHash` strings, same as
|
|
330
|
+
before. On the next `save()` they are rewritten as v2. No data loss;
|
|
331
|
+
no migration step required.
|
|
332
|
+
|
|
333
|
+
### Added
|
|
334
|
+
|
|
335
|
+
- **Scanned-PDF OCR fallback.** When `extractPdf` returns `-2` (image-
|
|
336
|
+
only PDF) AND `@albex/ocr` has been wired via `enableOcr(engine)`,
|
|
337
|
+
the engine now extracts embedded JPEG / JPEG2000 image XObjects from
|
|
338
|
+
the PDF and runs them through Tesseract.js to recover text. Covers the
|
|
339
|
+
great majority of real-world scanned PDFs. Other compression filters
|
|
340
|
+
(FlateDecode, CCITTFaxDecode, JBIG2Decode) are not yet supported; pages
|
|
341
|
+
using them register with zero chunks (same behaviour as before).
|
|
342
|
+
|
|
343
|
+
- **`getPageCount`, `extractPageImages`, `getPageImage{Len,Ptr,Kind}`**
|
|
344
|
+
added to `albex_pdf.wasm` to support the scanned-PDF path. The PDF
|
|
345
|
+
binary grew from ~1.04 MB to ~1.19 MB.
|
|
346
|
+
|
|
347
|
+
- **Snapshot v2 persists per-document content hashes.** `load()` now
|
|
348
|
+
repopulates the in-memory `_docs` list correctly: `getStats().documents`
|
|
349
|
+
is right after a restore, and content-hash de-duplication survives the
|
|
350
|
+
round-trip (re-indexing the same file does not create a fresh slot).
|
|
351
|
+
|
|
352
|
+
- **New WASM exports**: `setDocumentContentHash`, `getDocContentHashPtr`,
|
|
353
|
+
`getDocContentHashLen`. Used by the host to round-trip the FNV-1a 64-bit
|
|
354
|
+
hash through the snapshot format.
|
|
355
|
+
|
|
356
|
+
- **OCR sandbox in the demo.** `demo/index.html` now ships an "Enable
|
|
357
|
+
OCR" panel that lazy-loads Tesseract.js through an importmap and
|
|
358
|
+
exposes per-document OCR status. Drop a scanned PDF and the demo OCR's
|
|
359
|
+
it automatically.
|
|
360
|
+
|
|
361
|
+
### Fixed
|
|
362
|
+
|
|
363
|
+
- **`load()` repopulates `_docs` from the WASM tables.** Previously it
|
|
364
|
+
left `_docs = []` after a successful restore, which made
|
|
365
|
+
`engine.getStats().documents` return `0` even though searches against
|
|
366
|
+
the restored corpus worked. The README advertised "snapshot the index
|
|
367
|
+
and restore it" without that caveat.
|
|
368
|
+
|
|
369
|
+
- **CSV parser strips the UTF-8 BOM.** Files exported as "CSV UTF-8"
|
|
370
|
+
by Excel kept the BOM glued to the first field of the first row,
|
|
371
|
+
breaking column alignment and search hits on the first header
|
|
372
|
+
("Subject", "Asunto", etc.).
|
|
373
|
+
|
|
374
|
+
- **EML parser decodes `base64` and `quoted-printable` bodies.** Real
|
|
375
|
+
emails almost always use one of these transfer encodings; before the
|
|
376
|
+
fix the body surfaced as opaque encoded blobs that searches could
|
|
377
|
+
never hit. Nested multipart (`multipart/alternative` inside
|
|
378
|
+
`multipart/mixed`) is now also unwrapped recursively.
|
|
379
|
+
|
|
380
|
+
- **RTF parser decodes `\'XX` hex bytes (via Windows-1252) and `\uN ?`
|
|
381
|
+
Unicode escapes.** Spanish/French/German content stored as cp1252
|
|
382
|
+
used to lose every accent; Word's modern `\u` escapes used to eat
|
|
383
|
+
the fallback ASCII character. Also added `\emdash`, `\endash`,
|
|
384
|
+
`\bullet`, `\lquote`, `\rquote`, `\ldblquote`, `\rdblquote`, `\tab`,
|
|
385
|
+
and soft-hyphen/non-breaking-space handling.
|
|
386
|
+
|
|
387
|
+
### Documentation
|
|
388
|
+
|
|
389
|
+
- **README claims grounded.** Removed "every modern bundler",
|
|
390
|
+
"60 fps even on huge corpora", "5–10× speedup", "works for 99 % of
|
|
391
|
+
users", "11 formats" (without the `lite` qualifier). The matrix of
|
|
392
|
+
what is tested vs what is expected to work is now explicit. Bench
|
|
393
|
+
results are flagged as synthetic.
|
|
394
|
+
|
|
395
|
+
- **Persistence caveats documented.** The `Persistence` feature bullet
|
|
396
|
+
now describes the v2 / v1 difference and what survives the round trip.
|
|
397
|
+
|
|
398
|
+
### Tests
|
|
399
|
+
|
|
400
|
+
- 71 → 83 vitest tests, all green. New suites:
|
|
401
|
+
- `tests/scanned-pdf.test.ts` (4 tests) — scanned-PDF OCR fallback
|
|
402
|
+
with a hand-rolled `FakePdfWasm`.
|
|
403
|
+
- `tests/load-restores-docs.test.ts` (4 tests) — verifies `load()`
|
|
404
|
+
repopulates `_docs` and that content-hash dedup survives v2.
|
|
405
|
+
- `tests/lite-parsers.test.ts` (11 tests) — adversarial fixtures for
|
|
406
|
+
CSV BOM, EML base64 / QP / nested multipart, RTF cp1252 / Unicode.
|
|
407
|
+
|
|
408
|
+
## [0.2.0] — earlier
|
|
409
|
+
|
|
410
|
+
Initial public release. See git history for details: the surface was
|
|
411
|
+
the `AlbexEngine` class, the `albex_wasm_bg.wasm` and `albex_pdf.wasm`
|
|
412
|
+
binaries, lite parsers for the 11 formats, OPFS/IndexedDB persistence,
|
|
413
|
+
worker pool, tiered storage and optional WebGPU pre-filter.
|
|
414
|
+
|
|
415
|
+
[0.3.0]: https://github.com/RafaCalRob/Albex/releases/tag/v0.3.0
|
|
416
|
+
[0.2.0]: https://github.com/RafaCalRob/Albex/releases/tag/v0.2.0
|