@polycode-projects/seonix 0.7.3 → 0.8.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.
@@ -1,120 +0,0 @@
1
- // nlp-bundle.mjs — package wink-nlp + wink-eng-lite-web-model into ONE browser
2
- // IIFE that self-registers `window.__seonixNlp = {lemma, posTags}`, the same
3
- // adapter shape ask-nlp.mjs builds for Node. It is the browser path for the
4
- // viewer's lemma/POS tier (operator override of the earlier "CLI-only" call).
5
- //
6
- // WHY a hand-rolled packager and not a bundler: both packages are pure CJS with
7
- // ONLY static `require('./relative')` calls and NO Node built-ins (verified), so
8
- // a ~40-line CommonJS-in-the-browser shim that walks the require graph, wraps each
9
- // file in a module factory, and emits a tiny `require` closure is enough — no
10
- // esbuild/rollup dependency, in keeping with the repo's lean-deps rule. The model
11
- // data files are JSON `require`s, inlined as `module.exports=<json>`.
12
- //
13
- // BOUNDARY (mirrors ask-nlp.mjs's): this bundle is used ONLY by the SITE build
14
- // (viz --data-out --nlp writes it as a same-origin sibling the page lazy-loads)
15
- // or by an explicit `viz --nlp` on a portable file (inlined). The default local
16
- // single-file viewer never includes it and keeps its no-external-fetch guarantee.
17
- // The wink model self-loads via the browser `atob` global (it is a WEB model);
18
- // nothing here touches the DOM, fs, or the network.
19
- //
20
- // The lemma/posTags bodies below MIRROR ask-nlp.mjs's Node adapter deliberately;
21
- // nlp-bundle.test.mjs pins output parity between the two so they can't drift.
22
-
23
- import { readFile } from "node:fs/promises";
24
- import { createRequire } from "node:module";
25
-
26
- // Every internal require in these two packages is a static string literal (checked
27
- // against the shipped versions); a lexical scan is therefore exact enough to build
28
- // the graph, and any request that fails to resolve throws loudly rather than
29
- // silently dropping a module.
30
- const REQUIRE_RE = /require\(\s*(['"])([^'"]+)\1\s*\)/g;
31
-
32
- /** Walk the CJS require graph from `entries` (bare package specifiers), reading
33
- * every reachable .js/.json file, and return {order, deps, idOf, entryPaths}. */
34
- async function packGraph(entries, fromUrl) {
35
- const rootReq = createRequire(fromUrl);
36
- const idOf = new Map(); // absPath -> integer id (stable, discovery order)
37
- const order = []; // absPath[]
38
- const deps = new Map(); // absPath -> { request -> absPath }
39
- const isJson = (p) => p.endsWith(".json");
40
- const assign = (p) => { if (!idOf.has(p)) { idOf.set(p, idOf.size); order.push(p); } };
41
-
42
- async function walk(absPath) {
43
- if (deps.has(absPath)) return; // visited (also breaks require cycles)
44
- deps.set(absPath, {});
45
- assign(absPath);
46
- if (isJson(absPath)) return; // data leaf, no requires
47
- const src = await readFile(absPath, "utf8");
48
- const localReq = createRequire(absPath);
49
- const map = {};
50
- REQUIRE_RE.lastIndex = 0;
51
- for (let m; (m = REQUIRE_RE.exec(src)); ) {
52
- const request = m[2];
53
- map[request] = localReq.resolve(request); // throws if unresolvable — loud by design
54
- }
55
- deps.set(absPath, map);
56
- for (const target of Object.values(map)) await walk(target);
57
- }
58
-
59
- const entryPaths = entries.map((e) => rootReq.resolve(e));
60
- for (const e of entryPaths) await walk(e);
61
- return { order, deps, idOf, entryPaths };
62
- }
63
-
64
- /** The adapter body — JS source, MIRRORS ask-nlp.mjs's lemma/posTags. `nlp`/`its`
65
- * are in scope from the IIFE. Self-registers on window|self|globalThis. */
66
- const ADAPTER_JS = `
67
- var G=(typeof window!=='undefined')?window:(typeof self!=='undefined')?self:globalThis;
68
- G.__seonixNlp={
69
- lemma:function(word){
70
- var w=String(word||'');
71
- try{var out=nlp.readDoc(w).tokens().out(its.lemma);return String(out[0]||w).toLowerCase();}
72
- catch(e){return w.toLowerCase();}
73
- },
74
- posTags:function(words){
75
- try{
76
- var toks=nlp.readDoc(words.join(' ')).tokens();
77
- var texts=toks.out(),tags=toks.out(its.pos),out=[],k=0;
78
- for(var i=0;i<words.length;i++){
79
- var w=words[i];
80
- if(k>=texts.length){out.push(null);continue;}
81
- out.push(tags[k]);
82
- var acc=texts[k];k++;
83
- while(acc.length<w.length&&k<texts.length){acc+=texts[k];k++;}
84
- }
85
- return out;
86
- }catch(e){return words.map(function(){return null;});}
87
- }
88
- };`;
89
-
90
- /** Build the self-contained browser IIFE (a plain JS string, no <script> wrapper).
91
- * Loading it in a browser sets window.__seonixNlp. Pure w.r.t. installed deps. */
92
- export async function winkBrowserBundle() {
93
- const { order, deps, idOf, entryPaths } = await packGraph(
94
- ["wink-nlp", "wink-eng-lite-web-model"],
95
- import.meta.url,
96
- );
97
- const D = {}; // id -> {request -> id}
98
- for (const [p, map] of deps) {
99
- const from = idOf.get(p);
100
- D[from] = {};
101
- for (const [req, target] of Object.entries(map)) D[from][req] = idOf.get(target);
102
- }
103
- const parts = [];
104
- parts.push("(function(){\nvar M={},C={};");
105
- parts.push("function R(id){if(C[id])return C[id].exports;var m=C[id]={exports:{}};M[id](m,m.exports,function(r){return R(D[id][r]);});return m.exports;}");
106
- parts.push("var D=" + JSON.stringify(D) + ";");
107
- for (const p of order) {
108
- const id = idOf.get(p);
109
- const src = await readFile(p, "utf8");
110
- parts.push(p.endsWith(".json")
111
- ? "M[" + id + "]=function(module,exports,require){module.exports=" + src + "\n};"
112
- : "M[" + id + "]=function(module,exports,require){\n" + src + "\n};");
113
- }
114
- parts.push("var winkNLP=R(" + idOf.get(entryPaths[0]) + ");");
115
- parts.push("var model=R(" + idOf.get(entryPaths[1]) + ");");
116
- parts.push("var nlp=winkNLP(model),its=nlp.its;");
117
- parts.push(ADAPTER_JS);
118
- parts.push("})();");
119
- return parts.join("\n");
120
- }
package/src/prose-nlp.mjs DELETED
@@ -1,52 +0,0 @@
1
- // prose-nlp.mjs — the OPTIONAL wink-nlp lemma loader behind prose.mjs's LEMMA layer.
2
- //
3
- // Deliberately a SEPARATE loader from ask-nlp.mjs (same createRequire pattern, same
4
- // deps) rather than an import of it: ask-nlp.mjs belongs to the ask-engine surface
5
- // and is under active concurrent work — the prose pre-pass must not couple its
6
- // index-build path to that file's export shape. The ~20 duplicated lines are the
7
- // decoupling fee; the wink model itself is loaded lazily and at most once per
8
- // process either way.
9
- //
10
- // BOUNDARY (same as ask-nlp.mjs, hard): Node-only, never inlined into the viewer
11
- // bundle. prose.mjs is itself never inlined by viz.mjs's askSource(), so nothing
12
- // browser-side can reach this module. wink-nlp + wink-eng-lite-web-model are CJS —
13
- // loaded via createRequire, lazily, with failure cached as null: a checkout without
14
- // the optional deps simply builds no lemma layer (honestly absent), it never throws.
15
- //
16
- // Determinism: wink's lemmatiser is a fixed trained model with no sampling — the
17
- // same token always yields the same lemma across runs and processes, which is what
18
- // lets the lemma layer meet the "byte-identical proseIndex across builds" contract.
19
-
20
- import { createRequire } from "node:module";
21
-
22
- let cached; // undefined = not tried yet; null = unavailable (tried once, honestly off)
23
-
24
- /** Lazily build a `lemma(word) -> string` function, or null when wink isn't loadable.
25
- * Results are memoized per token: the layer build lemmatises each unique vocabulary
26
- * token once, not once per posting. */
27
- export function proseLemma() {
28
- if (cached !== undefined) return cached;
29
- try {
30
- const require = createRequire(import.meta.url);
31
- const winkNLP = require("wink-nlp");
32
- const model = require("wink-eng-lite-web-model");
33
- const nlp = winkNLP(model);
34
- const its = nlp.its;
35
- const memo = new Map();
36
- cached = (word) => {
37
- const w = String(word || "");
38
- if (memo.has(w)) return memo.get(w);
39
- let out;
40
- try {
41
- out = String(nlp.readDoc(w).tokens().out(its.lemma)[0] || w).toLowerCase();
42
- } catch {
43
- out = w.toLowerCase();
44
- }
45
- memo.set(w, out);
46
- return out;
47
- };
48
- } catch {
49
- cached = null;
50
- }
51
- return cached;
52
- }