@bigdreamsweb3/wordbin 1.0.6 → 1.0.7
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/dist/{dictionary-D3gr2Ala.js → builder-e2OwBYJh.js} +16 -8
- package/dist/builder-e2OwBYJh.js.map +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/core.d.ts +2 -2
- package/dist/{dictionary.d.ts → dict/builder.d.ts} +1 -1
- package/dist/{dictionary-loader.d.ts → dict/dictionary-loader.d.ts} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +71 -110
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
- package/dist/dictionary-D3gr2Ala.js.map +0 -1
|
@@ -85,10 +85,19 @@ async function buildDictionary(words, options = {}) {
|
|
|
85
85
|
const normalizedWords = words.map((w) => w.trim().toLowerCase()).filter((w) => w);
|
|
86
86
|
await Promise.all(
|
|
87
87
|
normalizedWords.map(async (word) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
let attempt = 0;
|
|
89
|
+
let key;
|
|
90
|
+
while (true) {
|
|
91
|
+
const id = await generateWordId(
|
|
92
|
+
attempt === 0 ? word : `${word}:${attempt}`
|
|
93
|
+
);
|
|
94
|
+
key = toHex(id);
|
|
95
|
+
if (!map[key]) {
|
|
96
|
+
map[key] = [word];
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
attempt++;
|
|
100
|
+
}
|
|
92
101
|
})
|
|
93
102
|
);
|
|
94
103
|
Object.values(map).forEach((collisions) => {
|
|
@@ -101,14 +110,13 @@ async function buildDictionary(words, options = {}) {
|
|
|
101
110
|
};
|
|
102
111
|
}
|
|
103
112
|
export {
|
|
104
|
-
|
|
113
|
+
utf8Decode as a,
|
|
105
114
|
buildDictionary as b,
|
|
106
|
-
|
|
115
|
+
toHex as c,
|
|
107
116
|
decodeVarint as d,
|
|
108
117
|
encodeVarint as e,
|
|
109
118
|
fromBase64 as f,
|
|
110
|
-
generateWordId as g,
|
|
111
119
|
toBase64 as t,
|
|
112
120
|
utf8Encode as u
|
|
113
121
|
};
|
|
114
|
-
//# sourceMappingURL=
|
|
122
|
+
//# sourceMappingURL=builder-e2OwBYJh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder-e2OwBYJh.js","sources":["../src/core/tiers.ts","../src/core/id.ts","../src/utils/buffer.ts","../src/dict/builder.ts"],"sourcesContent":["export function getIdByteLength(wordLength: number): number {\r\n if (wordLength <= 4) return 2;\r\n if (wordLength <= 9) return 3;\r\n return 4;\r\n}\r\n\r\nexport function getWrapByteLength(wordLength: number): number {\r\n if (wordLength <= 4) return 2;\r\n if (wordLength <= 9) return 3;\r\n return 4;\r\n}\r\n\r\nexport async function getTextEncoder(): Promise<TextEncoder> {\r\n if (typeof TextEncoder !== \"undefined\") return new TextEncoder();\r\n const { TextEncoder: NodeTextEncoder } = await import(\"node:util\");\r\n // @ts-ignore Node typings\r\n return new NodeTextEncoder();\r\n}\r\n\r\nexport async function wrapBase64(data: string): Promise<Uint8Array> {\r\n const normalized = data.trim().toLowerCase();\r\n if (!normalized) throw new Error(\"Cannot generate ID for empty string\");\r\n\r\n const encoder = await getTextEncoder();\r\n const result = encoder.encode(normalized);\r\n\r\n // Browser + Node compatible SHA-256\r\n let hash: ArrayBuffer;\r\n const anyCrypto: any = (globalThis as any).crypto;\r\n if (anyCrypto && anyCrypto.subtle) {\r\n hash = await anyCrypto.subtle.digest(\"SHA-256\", result);\r\n } else {\r\n const { createHash } = await import(\"node:crypto\");\r\n hash = createHash(\"sha256\").update(Buffer.from(result)).digest().buffer;\r\n }\r\n\r\n const hashBytes = new Uint8Array(hash);\r\n const size = getWrapByteLength(normalized.length);\r\n return hashBytes.slice(0, size);\r\n}\r\n","import { getIdByteLength } from './tiers.js'\r\n\r\n/**\r\n * Deterministic word \t ID generator\r\n * Same output on browser and node (when using compatible input)\r\n */\r\nexport async function generateWordId(word: string): Promise<Uint8Array> {\r\n const normalized = word.trim().toLowerCase()\r\n if (!normalized) throw new Error('Cannot generate ID for empty string')\r\n\r\n const encoder = await getTextEncoder()\r\n const data = encoder.encode(normalized)\r\n\r\n // Browser + Node compatible SHA-256\r\n let hash: ArrayBuffer\r\n const anyCrypto: any = (globalThis as any).crypto\r\n if (anyCrypto && anyCrypto.subtle) {\r\n hash = await anyCrypto.subtle.digest('SHA-256', data)\r\n } else {\r\n const { createHash } = await import('node:crypto')\r\n hash = createHash('sha256').update(Buffer.from(data)).digest().buffer\r\n }\r\n\r\n const hashBytes = new Uint8Array(hash)\r\n const size = getIdByteLength(normalized.length)\r\n return hashBytes.slice(0, size)\r\n}\r\n\r\nasync function getTextEncoder(): Promise<TextEncoder> {\r\n if (typeof TextEncoder !== 'undefined') return new TextEncoder()\r\n const { TextEncoder: NodeTextEncoder } = await import('node:util')\r\n // @ts-ignore Node typings\r\n return new NodeTextEncoder()\r\n}\r\n","export function toHex(bytes: Uint8Array): string {\r\n return Array.from(bytes)\r\n .map((b) => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n}\r\n\r\nexport function fromHex(hex: string): Uint8Array {\r\n if (hex.length % 2 !== 0) throw new Error('Invalid hex string length')\r\n const bytes = new Uint8Array(hex.length / 2)\r\n for (let i = 0; i < hex.length; i += 2) {\r\n bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16)\r\n }\r\n return bytes\r\n}\r\n\r\nexport function toBase64(bytes: Uint8Array): string {\r\n const b64 = (globalThis as any).btoa\r\n if (typeof b64 === 'function') {\r\n return b64(String.fromCharCode(...bytes))\r\n }\r\n // Node fallback\r\n return Buffer.from(bytes).toString('base64')\r\n}\r\n\r\nexport function fromBase64(base64: string): Uint8Array {\r\n const at = (globalThis as any).atob\r\n if (typeof at === 'function') {\r\n const binary = at(base64)\r\n const out = new Uint8Array(binary.length)\r\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)\r\n return out\r\n }\r\n // Node fallback\r\n return new Uint8Array(Buffer.from(base64, 'base64'))\r\n}\r\n\r\n// UTF-8 helpers\r\nexport function utf8Encode(str: string): Uint8Array {\r\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(str)\r\n // Node fallback\r\n return new Uint8Array(Buffer.from(str, 'utf8'))\r\n}\r\n\r\nexport function utf8Decode(bytes: Uint8Array): string {\r\n if (typeof TextDecoder !== 'undefined') return new TextDecoder().decode(bytes)\r\n // Node fallback\r\n return Buffer.from(bytes).toString('utf8')\r\n}\r\n\r\n// Varint (LEB128 7-bit groups) helpers\r\nexport function encodeVarint(n: number): Uint8Array {\r\n if (n < 0) throw new Error('Varint cannot encode negative numbers')\r\n const out: number[] = []\r\n do {\r\n let byte = n & 0x7f\r\n n >>>= 7\r\n if (n !== 0) byte |= 0x80\r\n out.push(byte)\r\n } while (n !== 0)\r\n return new Uint8Array(out)\r\n}\r\n\r\nexport function decodeVarint(bytes: Uint8Array, offset: number): { value: number; bytesRead: number } {\r\n let result = 0\r\n let shift = 0\r\n let pos = offset\r\n while (pos < bytes.length) {\r\n const byte = bytes[pos++]\r\n result |= (byte & 0x7f) << shift\r\n if ((byte & 0x80) === 0) {\r\n return { value: result, bytesRead: pos - offset }\r\n }\r\n shift += 7\r\n if (shift > 35) throw new Error('Varint too large')\r\n }\r\n throw new Error('Truncated varint')\r\n}\r\n","// File: src\\dict\\builder.ts\r\n\r\nimport type { WordBinDictionary } from \"../types\";\r\nimport { generateWordId } from \"../core/id.js\";\r\nimport { toHex } from \"../utils/buffer.js\";\r\n\r\nexport interface BuildDictionaryOptions {\r\n /**\r\n * Dictionary version number (used in header and for format compatibility)\r\n * @default 1\r\n */\r\n version?: number;\r\n\r\n /**\r\n * Human-readable description of this dictionary\r\n * @default \"WordBin dictionary v${version}\"\r\n */\r\n description?: string;\r\n\r\n /**\r\n * Optional: custom prefix or identifier for this dictionary build\r\n * (can be used in logs, filenames, etc.)\r\n */\r\n name?: string;\r\n}\r\n\r\nexport async function buildDictionary(\r\n words: string[],\r\n options: BuildDictionaryOptions = {},\r\n): Promise<WordBinDictionary> {\r\n const { version = 1, description = `WordBin dictionary v${version}` } =\r\n options;\r\n\r\n const map: Record<string, string[]> = {};\r\n\r\n const normalizedWords = words\r\n .map((w) => w.trim().toLowerCase())\r\n .filter((w) => w);\r\n\r\n await Promise.all(\r\n normalizedWords.map(async (word) => {\r\n let attempt = 0;\r\n let key: string;\r\n\r\n while (true) {\r\n const id = await generateWordId(\r\n attempt === 0 ? word : `${word}:${attempt}`,\r\n );\r\n\r\n key = toHex(id);\r\n\r\n // If no collision, break\r\n if (!map[key]) {\r\n map[key] = [word];\r\n break;\r\n }\r\n\r\n // Collision detected → try again\r\n attempt++;\r\n }\r\n }),\r\n );\r\n\r\n Object.values(map).forEach((collisions) => {\r\n collisions.sort((a, b) => a.localeCompare(b));\r\n });\r\n\r\n return {\r\n version,\r\n description,\r\n words: map,\r\n };\r\n}\r\n"],"names":[],"mappings":"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"}
|
package/dist/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { resolve } from "node:path";
|
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { stdout, stdin } from "node:process";
|
|
6
6
|
import { wordlists } from "bip39";
|
|
7
|
-
import { b as buildDictionary } from "./
|
|
7
|
+
import { b as buildDictionary } from "./builder-e2OwBYJh.js";
|
|
8
8
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
9
9
|
const help = `
|
|
10
10
|
WordBin CLI – Dictionary Builder
|
package/dist/core.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { EncodeResult, WordBinDictionary } from './types';
|
|
2
2
|
export declare class WordBin {
|
|
3
3
|
private primaryDictVersion;
|
|
4
|
+
private log;
|
|
4
5
|
constructor(initialDict?: WordBinDictionary, options?: {
|
|
5
6
|
debug?: boolean;
|
|
6
7
|
});
|
|
7
|
-
private log;
|
|
8
8
|
static createFromWords(words: string[]): Promise<WordBin>;
|
|
9
9
|
static createFromJson(dictJson: WordBinDictionary): Promise<WordBin>;
|
|
10
10
|
static create(options?: {
|
|
11
11
|
debug?: boolean;
|
|
12
12
|
}): Promise<WordBin>;
|
|
13
|
-
private
|
|
13
|
+
private getMapsForVersion;
|
|
14
14
|
encode(text: string | EncodeResult | Uint8Array, options?: {
|
|
15
15
|
dictVersion?: number;
|
|
16
16
|
}): Promise<EncodeResult>;
|
package/dist/index.d.ts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as buildDictionary, t as toBase64,
|
|
1
|
+
import { b as buildDictionary, t as toBase64, u as utf8Encode, e as encodeVarint, f as fromBase64, d as decodeVarint, a as utf8Decode, c as toHex } from "./builder-e2OwBYJh.js";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
@@ -88,7 +88,9 @@ class WordBin {
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
static async createFromWords(words) {
|
|
91
|
-
console.warn(
|
|
91
|
+
console.warn(
|
|
92
|
+
"Building dictionary from scratch – consider using pre-built files"
|
|
93
|
+
);
|
|
92
94
|
const dict = await buildDictionary(words);
|
|
93
95
|
return new WordBin(dict);
|
|
94
96
|
}
|
|
@@ -99,23 +101,41 @@ class WordBin {
|
|
|
99
101
|
const latestDict = await loadLatestDictionary();
|
|
100
102
|
return new WordBin(latestDict, options);
|
|
101
103
|
}
|
|
102
|
-
async
|
|
104
|
+
async getMapsForVersion(version) {
|
|
103
105
|
const dict = await loadDictionaryByVersion(version);
|
|
104
106
|
const reverseMap = /* @__PURE__ */ new Map();
|
|
107
|
+
const forwardMap = /* @__PURE__ */ new Map();
|
|
108
|
+
const idLengths = /* @__PURE__ */ new Set();
|
|
105
109
|
for (const [hex, words] of Object.entries(dict.words)) {
|
|
106
|
-
if (words.length
|
|
110
|
+
if (!words.length) continue;
|
|
111
|
+
if (words.length > 1) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Dictionary corruption: ID ${hex} maps to multiple words`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const word = words[0];
|
|
117
|
+
const bytes = Buffer.from(hex, "hex");
|
|
118
|
+
idLengths.add(bytes.length);
|
|
119
|
+
reverseMap.set(hex, word);
|
|
120
|
+
forwardMap.set(word, bytes);
|
|
107
121
|
}
|
|
108
|
-
|
|
122
|
+
const sortedIdLengths = Array.from(idLengths).sort((a, b) => b - a);
|
|
123
|
+
return { reverseMap, forwardMap, sortedIdLengths };
|
|
109
124
|
}
|
|
110
|
-
async encode(text, options
|
|
125
|
+
async encode(text, options) {
|
|
111
126
|
let textStr;
|
|
112
|
-
if (typeof text === "string")
|
|
113
|
-
|
|
114
|
-
else
|
|
115
|
-
|
|
127
|
+
if (typeof text === "string") {
|
|
128
|
+
textStr = text;
|
|
129
|
+
} else if (text instanceof Uint8Array) {
|
|
130
|
+
textStr = toBase64(text);
|
|
131
|
+
} else {
|
|
132
|
+
textStr = text.encodedBase64;
|
|
133
|
+
}
|
|
134
|
+
const trimmed = textStr.trim();
|
|
135
|
+
if (!trimmed) {
|
|
116
136
|
return {
|
|
117
137
|
originalText: "",
|
|
118
|
-
dictVersion:
|
|
138
|
+
dictVersion: this.primaryDictVersion,
|
|
119
139
|
encoded: new Uint8Array(0),
|
|
120
140
|
payload: "",
|
|
121
141
|
encodedBase64: "",
|
|
@@ -125,181 +145,122 @@ class WordBin {
|
|
|
125
145
|
ratioPercent: 100
|
|
126
146
|
};
|
|
127
147
|
}
|
|
128
|
-
const words =
|
|
129
|
-
|
|
130
|
-
const useVersion = options.dictVersion ?? this.primaryDictVersion;
|
|
131
|
-
this.log(`[encode] Using dictionary version: ${useVersion}`);
|
|
148
|
+
const words = trimmed.split(/\s+/).filter(Boolean);
|
|
149
|
+
const useVersion = options?.dictVersion ?? this.primaryDictVersion;
|
|
132
150
|
const header = new Uint8Array([useVersion]);
|
|
133
|
-
this.log(`[encode] Header bytes: [${[...header].join(", ")}]`);
|
|
134
|
-
this.log(`[encode] Header hex: ${toHex(header)}`);
|
|
135
151
|
const chunks = [header];
|
|
136
|
-
const
|
|
137
|
-
this.log(`[encode] Reverse map loaded — size: ${reverseMap.size} entries`);
|
|
138
|
-
this.log("[encode] Word → ID mapping:");
|
|
152
|
+
const { forwardMap } = await this.getMapsForVersion(useVersion);
|
|
139
153
|
for (const w of words) {
|
|
140
|
-
const id =
|
|
141
|
-
|
|
142
|
-
this.log(` "${w}" → ID bytes: [${[...id].join(", ")}] | hex: ${key}`);
|
|
143
|
-
if (reverseMap.has(key)) {
|
|
144
|
-
reverseMap.get(key);
|
|
145
|
-
this.log(` → Found in dictionary → using ${id.length}-byte ID`);
|
|
154
|
+
const id = forwardMap.get(w);
|
|
155
|
+
if (id) {
|
|
146
156
|
chunks.push(id);
|
|
147
157
|
} else {
|
|
148
158
|
const utf8 = utf8Encode(w);
|
|
149
159
|
const lenVarint = encodeVarint(utf8.length);
|
|
150
|
-
this.log(` → NOT in dictionary → literal mode`);
|
|
151
|
-
this.log(
|
|
152
|
-
` Literal length varint bytes: [${[...lenVarint].join(", ")}] (value = ${utf8.length})`
|
|
153
|
-
);
|
|
154
|
-
this.log(` Word UTF-8 bytes length: ${utf8.length}`);
|
|
155
160
|
const out = new Uint8Array(1 + lenVarint.length + utf8.length);
|
|
156
161
|
out[0] = LITERAL;
|
|
157
162
|
out.set(lenVarint, 1);
|
|
158
163
|
out.set(utf8, 1 + lenVarint.length);
|
|
159
|
-
this.log(` Literal chunk bytes: [${[...out].join(", ")}]`);
|
|
160
164
|
chunks.push(out);
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
|
-
const totalLength = chunks.reduce((
|
|
167
|
+
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
164
168
|
const result = new Uint8Array(totalLength);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
this.log(
|
|
171
|
-
` Chunk ${i}: ${chunk.length} bytes → offset ${off - chunk.length}`
|
|
172
|
-
);
|
|
173
|
-
});
|
|
174
|
-
this.log(
|
|
175
|
-
`[encode] Final encoded bytes (first 32): [${[...result.subarray(0, Math.min(32, result.length))].join(", ")}]`
|
|
176
|
-
);
|
|
169
|
+
let offset = 0;
|
|
170
|
+
for (const chunk of chunks) {
|
|
171
|
+
result.set(chunk, offset);
|
|
172
|
+
offset += chunk.length;
|
|
173
|
+
}
|
|
177
174
|
const originalBytes = new TextEncoder().encode(textStr).length;
|
|
178
175
|
const base64Result = toBase64(result);
|
|
179
|
-
this.log(`[encode] Base64 starts with: ${base64Result.slice(0, 12)}...`);
|
|
180
176
|
return {
|
|
181
177
|
originalText: textStr,
|
|
182
|
-
dictVersion:
|
|
178
|
+
dictVersion: useVersion,
|
|
183
179
|
encoded: result,
|
|
184
180
|
payload: base64Result,
|
|
185
181
|
encodedBase64: base64Result,
|
|
186
182
|
originalBytes,
|
|
187
183
|
encodedBytes: totalLength,
|
|
188
184
|
bytesSaved: originalBytes - totalLength,
|
|
189
|
-
ratioPercent: totalLength === 0 ? 100 : Math.round(totalLength / originalBytes * 100)
|
|
185
|
+
ratioPercent: totalLength === 0 ? 100 : Math.round(totalLength / originalBytes * 100 * 100) / 100
|
|
190
186
|
};
|
|
191
187
|
}
|
|
192
188
|
async decode(data) {
|
|
193
189
|
let buffer;
|
|
194
190
|
if (typeof data === "string") {
|
|
195
|
-
this.log(
|
|
196
|
-
`[decode] Input is base64 string: "${data.substring(0, 20)}..."`
|
|
197
|
-
);
|
|
198
191
|
buffer = fromBase64(data);
|
|
199
|
-
this.log(`[decode] Decoded to ${buffer.length} bytes`);
|
|
200
192
|
} else {
|
|
201
193
|
buffer = data;
|
|
202
|
-
this.log(`[decode] Input is Uint8Array with ${buffer.length} bytes`);
|
|
203
194
|
}
|
|
204
|
-
this.log(`[decode] Full buffer hex: ${toHex(buffer)}`);
|
|
205
|
-
this.log(
|
|
206
|
-
`[decode] First 16 bytes: [${[...buffer.subarray(0, Math.min(16, buffer.length))].join(", ")}]`
|
|
207
|
-
);
|
|
208
195
|
if (buffer.length < 1) {
|
|
209
|
-
throw new Error("Data too short");
|
|
196
|
+
throw new Error("Data too short to contain version byte");
|
|
210
197
|
}
|
|
211
198
|
const version = buffer[0];
|
|
212
|
-
this.log(`[decode] Dictionary version from header: ${version}`);
|
|
213
|
-
if (version < 1 || version > 100) {
|
|
214
|
-
this.log(`[decode] Warning: unusual dictionary version ${version}`);
|
|
215
|
-
}
|
|
216
199
|
let pos = 1;
|
|
217
|
-
|
|
218
|
-
const reverseMap = await this.getReverseMapForVersion(version);
|
|
219
|
-
this.log(
|
|
220
|
-
`[decode] Reverse map loaded for v${version} — size: ${reverseMap.size} entries`
|
|
221
|
-
);
|
|
222
|
-
this.log(`[decode] ===== STARTING DECODE LOOP =====`);
|
|
200
|
+
const { reverseMap, sortedIdLengths } = await this.getMapsForVersion(version);
|
|
223
201
|
const result = [];
|
|
224
|
-
const decoded = this.tryDecode(
|
|
202
|
+
const decoded = this.tryDecode(
|
|
203
|
+
pos,
|
|
204
|
+
buffer,
|
|
205
|
+
reverseMap,
|
|
206
|
+
result,
|
|
207
|
+
0,
|
|
208
|
+
sortedIdLengths
|
|
209
|
+
);
|
|
225
210
|
if (decoded === null) {
|
|
226
211
|
throw new Error(
|
|
227
|
-
"
|
|
212
|
+
"Decode failed — possible data corruption, wrong dictionary version, or unsupported format"
|
|
228
213
|
);
|
|
229
214
|
}
|
|
230
|
-
this.log(`
|
|
231
|
-
[decode] ===== DECODE COMPLETE =====`);
|
|
232
|
-
this.log(`[decode] Total words decoded: ${result.length}`);
|
|
233
|
-
this.log(`[decode] Final result: "${decoded}"`);
|
|
234
215
|
return decoded;
|
|
235
216
|
}
|
|
236
|
-
tryDecode(pos, buffer, reverseMap, result, depth) {
|
|
237
|
-
const indent = " ".repeat(depth);
|
|
238
|
-
this.log(`${indent}[tryDecode] At position ${pos} (depth ${depth})`);
|
|
217
|
+
tryDecode(pos, buffer, reverseMap, result, depth, sortedIdLengths) {
|
|
239
218
|
if (pos === buffer.length) {
|
|
240
|
-
this.log(`${indent}[tryDecode] Reached end successfully`);
|
|
241
219
|
return result.join(" ");
|
|
242
220
|
}
|
|
243
|
-
const previewLen = Math.min(8, buffer.length - pos);
|
|
244
|
-
const preview = [...buffer.subarray(pos, pos + previewLen)].map((b) => `0x${b.toString(16).padStart(2, "0")}`).join(" ");
|
|
245
|
-
this.log(`${indent}[tryDecode] Next ${previewLen} bytes: ${preview}`);
|
|
246
221
|
if (buffer[pos] === LITERAL) {
|
|
247
|
-
this.log(
|
|
248
|
-
`${indent}[tryDecode] Found LITERAL marker (0x${LITERAL.toString(16)})`
|
|
249
|
-
);
|
|
250
222
|
const { value: byteLen, bytesRead } = decodeVarint(buffer, pos + 1);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
223
|
+
if (byteLen > 1e6 || byteLen < 0) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
254
226
|
const start = pos + 1 + bytesRead;
|
|
255
227
|
const end = start + byteLen;
|
|
256
|
-
this.log(
|
|
257
|
-
`${indent}[tryDecode] Literal range: [${start}..${end}) (${byteLen} bytes)`
|
|
258
|
-
);
|
|
259
228
|
if (end > buffer.length) {
|
|
260
|
-
this.log(`${indent}[tryDecode] Truncated literal — failing path`);
|
|
261
229
|
return null;
|
|
262
230
|
}
|
|
263
231
|
const literalBytes = buffer.subarray(start, end);
|
|
264
232
|
const word = utf8Decode(literalBytes);
|
|
265
|
-
this.log(`${indent}[tryDecode] Decoded literal: "${word}"`);
|
|
266
233
|
result.push(word);
|
|
267
|
-
const res = this.tryDecode(
|
|
234
|
+
const res = this.tryDecode(
|
|
235
|
+
end,
|
|
236
|
+
buffer,
|
|
237
|
+
reverseMap,
|
|
238
|
+
result,
|
|
239
|
+
depth + 1,
|
|
240
|
+
sortedIdLengths
|
|
241
|
+
);
|
|
268
242
|
if (res !== null) return res;
|
|
269
243
|
result.pop();
|
|
270
|
-
this.log(`${indent}[tryDecode] Backtracking from literal`);
|
|
271
|
-
return null;
|
|
272
244
|
}
|
|
273
|
-
for (const len of
|
|
274
|
-
if (pos + len > buffer.length)
|
|
275
|
-
this.log(`${indent}[tryDecode] Skipping ${len}-byte (would exceed)`);
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
245
|
+
for (const len of sortedIdLengths) {
|
|
246
|
+
if (pos + len > buffer.length) continue;
|
|
278
247
|
const slice = buffer.subarray(pos, pos + len);
|
|
279
248
|
const key = toHex(slice);
|
|
280
|
-
const keyBytes = [...slice].map((b) => `0x${b.toString(16).padStart(2, "0")}`).join(" ");
|
|
281
|
-
this.log(
|
|
282
|
-
`${indent}[tryDecode] Trying ${len}-byte: [${keyBytes}] hex=${key}`
|
|
283
|
-
);
|
|
284
249
|
if (reverseMap.has(key)) {
|
|
285
250
|
const word = reverseMap.get(key);
|
|
286
|
-
this.log(`${indent}[tryDecode] Match: "${word}" (ID: ${key})`);
|
|
287
251
|
result.push(word);
|
|
288
252
|
const res = this.tryDecode(
|
|
289
253
|
pos + len,
|
|
290
254
|
buffer,
|
|
291
255
|
reverseMap,
|
|
292
256
|
result,
|
|
293
|
-
depth + 1
|
|
257
|
+
depth + 1,
|
|
258
|
+
sortedIdLengths
|
|
294
259
|
);
|
|
295
260
|
if (res !== null) return res;
|
|
296
261
|
result.pop();
|
|
297
|
-
this.log(`${indent}[tryDecode] Backtracking from "${word}"`);
|
|
298
|
-
} else {
|
|
299
|
-
this.log(`${indent}[tryDecode] No match for ${key}`);
|
|
300
262
|
}
|
|
301
263
|
}
|
|
302
|
-
this.log(`${indent}[tryDecode] No valid branches — failing path`);
|
|
303
264
|
return null;
|
|
304
265
|
}
|
|
305
266
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/constants.ts","../src/dictionary-loader.ts","../src/core.ts"],"sourcesContent":["// wordbin\\src\\constants.ts\r\nexport const MAGIC = new Uint8Array([87, 66]) // 'W''B'\r\nexport const LITERAL = 0xff\r\n","import fs from \"fs/promises\";\r\nimport path from \"path\";\r\nimport { fileURLToPath } from \"url\";\r\nimport type { WordBinDictionary } from \"./types.js\";\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n// 1️⃣ Bundled dictionaries inside the package (dist/data)\r\nconst PACKAGE_DATA_DIR = path.join(__dirname, \"data\");\r\n\r\n// 2️⃣ User project dictionaries (built via CLI)\r\nconst LOCAL_DATA_DIR = path.join(process.cwd(), \"data\");\r\n\r\n/**\r\n * Get all dictionary directories that exist\r\n * Priority: LOCAL first, then PACKAGE fallback\r\n */\r\nasync function getExistingDataDirs(): Promise<string[]> {\r\n const dirs: string[] = [];\r\n\r\n try {\r\n await fs.access(LOCAL_DATA_DIR);\r\n dirs.push(LOCAL_DATA_DIR);\r\n } catch {}\r\n\r\n try {\r\n await fs.access(PACKAGE_DATA_DIR);\r\n dirs.push(PACKAGE_DATA_DIR);\r\n } catch {}\r\n\r\n return dirs;\r\n}\r\n\r\n/**\r\n * Scan all available dictionary versions from all data dirs\r\n */\r\nexport async function getAllAvailableDictionaryVersions(): Promise<number[]> {\r\n const dirs = await getExistingDataDirs();\r\n const versions = new Set<number>();\r\n\r\n for (const dir of dirs) {\r\n try {\r\n const files = await fs.readdir(dir);\r\n\r\n for (const file of files) {\r\n const match = file.match(/wordbin-v(\\d+)/i);\r\n if (match) {\r\n versions.add(parseInt(match[1], 10));\r\n }\r\n }\r\n } catch {\r\n // Ignore invalid dirs silently\r\n }\r\n }\r\n\r\n return Array.from(versions).sort((a, b) => a - b);\r\n}\r\n\r\n/**\r\n * Load a specific dictionary version\r\n * LOCAL dictionaries override PACKAGE ones\r\n */\r\nexport async function loadDictionaryByVersion(\r\n version: number,\r\n): Promise<WordBinDictionary> {\r\n const dirs = await getExistingDataDirs();\r\n\r\n if (dirs.length === 0) {\r\n throw new Error(\r\n `No dictionary directories found. Expected ./data or bundled package data.`,\r\n );\r\n }\r\n\r\n for (const dir of dirs) {\r\n const files = await fs.readdir(dir);\r\n\r\n const versionFile = files.find((f) =>\r\n f.match(new RegExp(`wordbin-v${version}(?:\\\\.|-)`, \"i\")),\r\n );\r\n\r\n if (versionFile) {\r\n const filePath = path.join(dir, versionFile);\r\n const data = await fs.readFile(filePath, \"utf-8\");\r\n const dict = JSON.parse(data) as WordBinDictionary;\r\n\r\n if (dict.version !== version) {\r\n throw new Error(\r\n `Version mismatch: file ${versionFile} claims v${dict.version} but expected v${version}`,\r\n );\r\n }\r\n\r\n return dict;\r\n }\r\n }\r\n\r\n const available = await getAllAvailableDictionaryVersions();\r\n\r\n throw new Error(\r\n `Dictionary version ${version} not found. Available versions: ${available.join(\", \")}`,\r\n );\r\n}\r\n\r\n/**\r\n * Load the latest available dictionary version\r\n */\r\nexport async function loadLatestDictionary(): Promise<WordBinDictionary> {\r\n const versions = await getAllAvailableDictionaryVersions();\r\n\r\n if (versions.length === 0) {\r\n throw new Error(\r\n `No dictionary files found. Run \"npx wordbin build\" or use bundled v1.`,\r\n );\r\n }\r\n\r\n const latestVersion = Math.max(...versions);\r\n\r\n console.log(\r\n `Loading latest dictionary: v${latestVersion} (available: ${versions.join(\", \")})`,\r\n );\r\n\r\n return loadDictionaryByVersion(latestVersion);\r\n}\r\n\r\n/**\r\n * Check if a specific version exists\r\n */\r\nexport async function hasDictionaryVersion(version: number): Promise<boolean> {\r\n const versions = await getAllAvailableDictionaryVersions();\r\n return versions.includes(version);\r\n}\r\n","import { LITERAL } from \"./constants.js\";\r\nimport { generateWordId } from \"./core/id.js\";\r\nimport {\r\n toHex,\r\n toBase64,\r\n fromBase64,\r\n encodeVarint,\r\n decodeVarint,\r\n utf8Encode,\r\n utf8Decode,\r\n} from \"./utils/buffer.js\";\r\nimport type { EncodeResult, WordBinDictionary } from \"./types\";\r\nimport { buildDictionary } from \"./dictionary.js\";\r\nimport {\r\n loadDictionaryByVersion,\r\n loadLatestDictionary,\r\n} from \"./dictionary-loader.js\";\r\n\r\nexport class WordBin {\r\n private primaryDictVersion: number;\r\n\r\n constructor(initialDict?: WordBinDictionary, options?: { debug?: boolean }) {\r\n this.primaryDictVersion = initialDict?.version ?? 2;\r\n this.log = options?.debug\r\n ? (...args) => console.log(\"[WordBin]\", ...args)\r\n : () => {};\r\n }\r\n\r\n private log: (...args: any[]) => void;\r\n\r\n static async createFromWords(words: string[]): Promise<WordBin> {\r\n console.warn(\"Building dictionary from scratch – consider pre-built files\");\r\n const dict = await buildDictionary(words);\r\n return new WordBin(dict);\r\n }\r\n\r\n static async createFromJson(dictJson: WordBinDictionary): Promise<WordBin> {\r\n return new WordBin(dictJson);\r\n }\r\n\r\n static async create(options?: { debug?: boolean }): Promise<WordBin> {\r\n const latestDict = await loadLatestDictionary();\r\n return new WordBin(latestDict, options);\r\n }\r\n\r\n private async getReverseMapForVersion(\r\n version: number,\r\n ): Promise<Map<string, string>> {\r\n const dict = await loadDictionaryByVersion(version);\r\n const reverseMap = new Map<string, string>();\r\n for (const [hex, words] of Object.entries(dict.words)) {\r\n if (words.length > 0) reverseMap.set(hex, words[0]);\r\n }\r\n return reverseMap;\r\n }\r\n\r\n async encode(\r\n text: string | EncodeResult | Uint8Array,\r\n options: { dictVersion?: number } = {},\r\n ): Promise<EncodeResult> {\r\n let textStr: string;\r\n if (typeof text === \"string\") textStr = text;\r\n else if (text instanceof Uint8Array) textStr = toBase64(text);\r\n else textStr = text.encodedBase64;\r\n\r\n if (!textStr.trim()) {\r\n return {\r\n originalText: \"\",\r\n dictVersion: 0,\r\n encoded: new Uint8Array(0),\r\n payload: \"\",\r\n encodedBase64: \"\",\r\n originalBytes: 0,\r\n encodedBytes: 0,\r\n bytesSaved: 0,\r\n ratioPercent: 100,\r\n };\r\n }\r\n\r\n const words = textStr.split(/\\s+/).filter(Boolean);\r\n this.log(`[encode] Input words (${words.length}):`, words);\r\n\r\n const useVersion = options.dictVersion ?? this.primaryDictVersion;\r\n this.log(`[encode] Using dictionary version: ${useVersion}`);\r\n\r\n // ──────────────────────────────────────────────\r\n // Header: just the version byte (no magic bytes)\r\n // ──────────────────────────────────────────────\r\n const header = new Uint8Array([useVersion]);\r\n\r\n this.log(`[encode] Header bytes: [${[...header].join(\", \")}]`);\r\n this.log(`[encode] Header hex: ${toHex(header)}`);\r\n\r\n const chunks: Uint8Array[] = [header];\r\n\r\n const reverseMap = await this.getReverseMapForVersion(useVersion);\r\n this.log(`[encode] Reverse map loaded — size: ${reverseMap.size} entries`);\r\n\r\n // ──────────────────────────────────────────────\r\n // Process each word\r\n // ──────────────────────────────────────────────\r\n this.log(\"[encode] Word → ID mapping:\");\r\n\r\n for (const w of words) {\r\n const id = await generateWordId(w);\r\n const key = toHex(id);\r\n\r\n this.log(` \"${w}\" → ID bytes: [${[...id].join(\", \")}] | hex: ${key}`);\r\n\r\n if (reverseMap.has(key)) {\r\n const mappedWord = reverseMap.get(key);\r\n this.log(` → Found in dictionary → using ${id.length}-byte ID`);\r\n chunks.push(id);\r\n } else {\r\n const utf8 = utf8Encode(w);\r\n const lenVarint = encodeVarint(utf8.length);\r\n this.log(` → NOT in dictionary → literal mode`);\r\n this.log(\r\n ` Literal length varint bytes: [${[...lenVarint].join(\", \")}] (value = ${utf8.length})`,\r\n );\r\n this.log(` Word UTF-8 bytes length: ${utf8.length}`);\r\n\r\n const out = new Uint8Array(1 + lenVarint.length + utf8.length);\r\n out[0] = LITERAL;\r\n out.set(lenVarint, 1);\r\n out.set(utf8, 1 + lenVarint.length);\r\n\r\n this.log(` Literal chunk bytes: [${[...out].join(\", \")}]`);\r\n chunks.push(out);\r\n }\r\n }\r\n\r\n // ──────────────────────────────────────────────\r\n // Final assembly\r\n // ──────────────────────────────────────────────\r\n const totalLength = chunks.reduce((n, c) => n + c.length, 0);\r\n const result = new Uint8Array(totalLength);\r\n\r\n this.log(`[encode] Total encoded length: ${totalLength} bytes`);\r\n\r\n let off = 0;\r\n chunks.forEach((chunk, i) => {\r\n result.set(chunk, off);\r\n off += chunk.length;\r\n this.log(\r\n ` Chunk ${i}: ${chunk.length} bytes → offset ${off - chunk.length}`,\r\n );\r\n });\r\n\r\n this.log(\r\n `[encode] Final encoded bytes (first 32): [${[...result.subarray(0, Math.min(32, result.length))].join(\", \")}]`,\r\n );\r\n\r\n const originalBytes = new TextEncoder().encode(textStr).length;\r\n\r\n const base64Result = toBase64(result);\r\n this.log(`[encode] Base64 starts with: ${base64Result.slice(0, 12)}...`);\r\n\r\n return {\r\n originalText: textStr,\r\n dictVersion: result[0],\r\n encoded: result,\r\n payload: base64Result,\r\n encodedBase64: base64Result,\r\n originalBytes,\r\n encodedBytes: totalLength,\r\n bytesSaved: originalBytes - totalLength,\r\n ratioPercent:\r\n totalLength === 0\r\n ? 100\r\n : Math.round((totalLength / originalBytes) * 100),\r\n };\r\n }\r\n\r\n async decode(data: Uint8Array | string): Promise<string> {\r\n let buffer: Uint8Array;\r\n if (typeof data === \"string\") {\r\n this.log(\r\n `[decode] Input is base64 string: \"${data.substring(0, 20)}...\"`,\r\n );\r\n buffer = fromBase64(data);\r\n this.log(`[decode] Decoded to ${buffer.length} bytes`);\r\n } else {\r\n buffer = data;\r\n this.log(`[decode] Input is Uint8Array with ${buffer.length} bytes`);\r\n }\r\n\r\n this.log(`[decode] Full buffer hex: ${toHex(buffer)}`);\r\n this.log(\r\n `[decode] First 16 bytes: [${[...buffer.subarray(0, Math.min(16, buffer.length))].join(\", \")}]`,\r\n );\r\n\r\n if (buffer.length < 1) {\r\n throw new Error(\"Data too short\");\r\n }\r\n\r\n const version = buffer[0];\r\n this.log(`[decode] Dictionary version from header: ${version}`);\r\n\r\n // Optional: basic sanity check (you can adjust or remove)\r\n if (version < 1 || version > 100) {\r\n this.log(`[decode] Warning: unusual dictionary version ${version}`);\r\n // You can throw here if you want strict validation:\r\n // throw new Error(`Unreasonable dictionary version: ${version}`);\r\n }\r\n\r\n let pos = 1; // payload starts right after the version byte\r\n this.log(`[decode] Starting decode at position ${pos}`);\r\n\r\n const reverseMap = await this.getReverseMapForVersion(version);\r\n this.log(\r\n `[decode] Reverse map loaded for v${version} — size: ${reverseMap.size} entries`,\r\n );\r\n\r\n this.log(`[decode] ===== STARTING DECODE LOOP =====`);\r\n\r\n const result: string[] = [];\r\n const decoded = this.tryDecode(pos, buffer, reverseMap, result, 0);\r\n\r\n if (decoded === null) {\r\n throw new Error(\r\n \"No valid decode path found — possible corruption or dictionary mismatch\",\r\n );\r\n }\r\n\r\n this.log(`\\n[decode] ===== DECODE COMPLETE =====`);\r\n this.log(`[decode] Total words decoded: ${result.length}`);\r\n this.log(`[decode] Final result: \"${decoded}\"`);\r\n\r\n return decoded;\r\n }\r\n\r\n private tryDecode(\r\n pos: number,\r\n buffer: Uint8Array,\r\n reverseMap: Map<string, string>,\r\n result: string[],\r\n depth: number,\r\n ): string | null {\r\n const indent = \" \".repeat(depth);\r\n this.log(`${indent}[tryDecode] At position ${pos} (depth ${depth})`);\r\n\r\n if (pos === buffer.length) {\r\n this.log(`${indent}[tryDecode] Reached end successfully`);\r\n return result.join(\" \");\r\n }\r\n\r\n const previewLen = Math.min(8, buffer.length - pos);\r\n const preview = [...buffer.subarray(pos, pos + previewLen)]\r\n .map((b) => `0x${b.toString(16).padStart(2, \"0\")}`)\r\n .join(\" \");\r\n this.log(`${indent}[tryDecode] Next ${previewLen} bytes: ${preview}`);\r\n\r\n // Try literal first\r\n if (buffer[pos] === LITERAL) {\r\n this.log(\r\n `${indent}[tryDecode] Found LITERAL marker (0x${LITERAL.toString(16)})`,\r\n );\r\n\r\n const { value: byteLen, bytesRead } = decodeVarint(buffer, pos + 1);\r\n this.log(\r\n `${indent}[tryDecode] Varint: value=${byteLen}, bytesRead=${bytesRead}`,\r\n );\r\n\r\n const start = pos + 1 + bytesRead;\r\n const end = start + byteLen;\r\n this.log(\r\n `${indent}[tryDecode] Literal range: [${start}..${end}) (${byteLen} bytes)`,\r\n );\r\n\r\n if (end > buffer.length) {\r\n this.log(`${indent}[tryDecode] Truncated literal — failing path`);\r\n return null;\r\n }\r\n\r\n const literalBytes = buffer.subarray(start, end);\r\n const word = utf8Decode(literalBytes);\r\n this.log(`${indent}[tryDecode] Decoded literal: \"${word}\"`);\r\n\r\n result.push(word);\r\n const res = this.tryDecode(end, buffer, reverseMap, result, depth + 1);\r\n if (res !== null) return res;\r\n result.pop();\r\n this.log(`${indent}[tryDecode] Backtracking from literal`);\r\n return null;\r\n }\r\n\r\n // Try dictionary matches (longest first)\r\n for (const len of [4, 3, 2]) {\r\n if (pos + len > buffer.length) {\r\n this.log(`${indent}[tryDecode] Skipping ${len}-byte (would exceed)`);\r\n continue;\r\n }\r\n\r\n const slice = buffer.subarray(pos, pos + len);\r\n const key = toHex(slice);\r\n const keyBytes = [...slice]\r\n .map((b) => `0x${b.toString(16).padStart(2, \"0\")}`)\r\n .join(\" \");\r\n\r\n this.log(\r\n `${indent}[tryDecode] Trying ${len}-byte: [${keyBytes}] hex=${key}`,\r\n );\r\n\r\n if (reverseMap.has(key)) {\r\n const word = reverseMap.get(key)!;\r\n this.log(`${indent}[tryDecode] Match: \"${word}\" (ID: ${key})`);\r\n\r\n result.push(word);\r\n const res = this.tryDecode(\r\n pos + len,\r\n buffer,\r\n reverseMap,\r\n result,\r\n depth + 1,\r\n );\r\n if (res !== null) return res;\r\n result.pop();\r\n this.log(`${indent}[tryDecode] Backtracking from \"${word}\"`);\r\n } else {\r\n this.log(`${indent}[tryDecode] No match for ${key}`);\r\n }\r\n }\r\n\r\n this.log(`${indent}[tryDecode] No valid branches — failing path`);\r\n return null;\r\n }\r\n}\r\n"],"names":["__filename","__dirname"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/constants.ts","../src/dict/dictionary-loader.ts","../src/core.ts"],"sourcesContent":["// wordbin\\src\\constants.ts\r\nexport const MAGIC = new Uint8Array([87, 66]) // 'W''B'\r\nexport const LITERAL = 0xff\r\n","import fs from \"fs/promises\";\r\nimport path from \"path\";\r\nimport { fileURLToPath } from \"url\";\r\nimport type { WordBinDictionary } from \"../types.js\";\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n// 1️⃣ Bundled dictionaries inside the package (dist/data)\r\nconst PACKAGE_DATA_DIR = path.join(__dirname, \"data\");\r\n\r\n// 2️⃣ User project dictionaries (built via CLI)\r\nconst LOCAL_DATA_DIR = path.join(process.cwd(), \"data\");\r\n\r\n/**\r\n * Get all dictionary directories that exist\r\n * Priority: LOCAL first, then PACKAGE fallback\r\n */\r\nasync function getExistingDataDirs(): Promise<string[]> {\r\n const dirs: string[] = [];\r\n\r\n try {\r\n await fs.access(LOCAL_DATA_DIR);\r\n dirs.push(LOCAL_DATA_DIR);\r\n } catch {}\r\n\r\n try {\r\n await fs.access(PACKAGE_DATA_DIR);\r\n dirs.push(PACKAGE_DATA_DIR);\r\n } catch {}\r\n\r\n return dirs;\r\n}\r\n\r\n/**\r\n * Scan all available dictionary versions from all data dirs\r\n */\r\nexport async function getAllAvailableDictionaryVersions(): Promise<number[]> {\r\n const dirs = await getExistingDataDirs();\r\n const versions = new Set<number>();\r\n\r\n for (const dir of dirs) {\r\n try {\r\n const files = await fs.readdir(dir);\r\n\r\n for (const file of files) {\r\n const match = file.match(/wordbin-v(\\d+)/i);\r\n if (match) {\r\n versions.add(parseInt(match[1], 10));\r\n }\r\n }\r\n } catch {\r\n // Ignore invalid dirs silently\r\n }\r\n }\r\n\r\n return Array.from(versions).sort((a, b) => a - b);\r\n}\r\n\r\n/**\r\n * Load a specific dictionary version\r\n * LOCAL dictionaries override PACKAGE ones\r\n */\r\nexport async function loadDictionaryByVersion(\r\n version: number,\r\n): Promise<WordBinDictionary> {\r\n const dirs = await getExistingDataDirs();\r\n\r\n if (dirs.length === 0) {\r\n throw new Error(\r\n `No dictionary directories found. Expected ./data or bundled package data.`,\r\n );\r\n }\r\n\r\n for (const dir of dirs) {\r\n const files = await fs.readdir(dir);\r\n\r\n const versionFile = files.find((f) =>\r\n f.match(new RegExp(`wordbin-v${version}(?:\\\\.|-)`, \"i\")),\r\n );\r\n\r\n if (versionFile) {\r\n const filePath = path.join(dir, versionFile);\r\n const data = await fs.readFile(filePath, \"utf-8\");\r\n const dict = JSON.parse(data) as WordBinDictionary;\r\n\r\n if (dict.version !== version) {\r\n throw new Error(\r\n `Version mismatch: file ${versionFile} claims v${dict.version} but expected v${version}`,\r\n );\r\n }\r\n\r\n return dict;\r\n }\r\n }\r\n\r\n const available = await getAllAvailableDictionaryVersions();\r\n\r\n throw new Error(\r\n `Dictionary version ${version} not found. Available versions: ${available.join(\", \")}`,\r\n );\r\n}\r\n\r\n/**\r\n * Load the latest available dictionary version\r\n */\r\nexport async function loadLatestDictionary(): Promise<WordBinDictionary> {\r\n const versions = await getAllAvailableDictionaryVersions();\r\n\r\n if (versions.length === 0) {\r\n throw new Error(\r\n `No dictionary files found. Run \"npx wordbin build\" or use bundled v1.`,\r\n );\r\n }\r\n\r\n const latestVersion = Math.max(...versions);\r\n\r\n console.log(\r\n `Loading latest dictionary: v${latestVersion} (available: ${versions.join(\", \")})`,\r\n );\r\n\r\n return loadDictionaryByVersion(latestVersion);\r\n}\r\n\r\n/**\r\n * Check if a specific version exists\r\n */\r\nexport async function hasDictionaryVersion(version: number): Promise<boolean> {\r\n const versions = await getAllAvailableDictionaryVersions();\r\n return versions.includes(version);\r\n}\r\n","import { LITERAL } from \"./constants.js\";\r\nimport {\r\n toHex,\r\n toBase64,\r\n fromBase64,\r\n encodeVarint,\r\n decodeVarint,\r\n utf8Encode,\r\n utf8Decode,\r\n} from \"./utils/buffer.js\";\r\nimport type { EncodeResult, WordBinDictionary } from \"./types\";\r\nimport { buildDictionary } from \"./dict/builder\";\r\nimport {\r\n loadDictionaryByVersion,\r\n loadLatestDictionary,\r\n} from \"./dict/dictionary-loader.js\";\r\n\r\nexport class WordBin {\r\n private primaryDictVersion: number;\r\n private log: (...args: any[]) => void;\r\n\r\n constructor(initialDict?: WordBinDictionary, options?: { debug?: boolean }) {\r\n this.primaryDictVersion = initialDict?.version ?? 2;\r\n this.log = options?.debug\r\n ? (...args) => console.log(\"[WordBin]\", ...args)\r\n : () => {};\r\n }\r\n\r\n static async createFromWords(words: string[]): Promise<WordBin> {\r\n console.warn(\r\n \"Building dictionary from scratch – consider using pre-built files\",\r\n );\r\n const dict = await buildDictionary(words);\r\n return new WordBin(dict);\r\n }\r\n\r\n static async createFromJson(dictJson: WordBinDictionary): Promise<WordBin> {\r\n return new WordBin(dictJson);\r\n }\r\n\r\n static async create(options?: { debug?: boolean }): Promise<WordBin> {\r\n const latestDict = await loadLatestDictionary();\r\n return new WordBin(latestDict, options);\r\n }\r\n\r\n private async getMapsForVersion(version: number): Promise<{\r\n reverseMap: Map<string, string>;\r\n forwardMap: Map<string, Uint8Array>;\r\n sortedIdLengths: number[];\r\n }> {\r\n const dict = await loadDictionaryByVersion(version);\r\n\r\n const reverseMap = new Map<string, string>();\r\n const forwardMap = new Map<string, Uint8Array>();\r\n const idLengths = new Set<number>();\r\n\r\n for (const [hex, words] of Object.entries(dict.words)) {\r\n if (!words.length) continue;\r\n if (words.length > 1) {\r\n throw new Error(\r\n `Dictionary corruption: ID ${hex} maps to multiple words`,\r\n );\r\n }\r\n\r\n const word = words[0];\r\n const bytes = Buffer.from(hex, \"hex\"); // Buffer is a Uint8Array\r\n idLengths.add(bytes.length);\r\n\r\n reverseMap.set(hex, word);\r\n forwardMap.set(word, bytes);\r\n }\r\n\r\n const sortedIdLengths = Array.from(idLengths).sort((a, b) => b - a); // longest first\r\n\r\n return { reverseMap, forwardMap, sortedIdLengths };\r\n }\r\n\r\n async encode(\r\n text: string | EncodeResult | Uint8Array,\r\n options?: { dictVersion?: number },\r\n ): Promise<EncodeResult> {\r\n let textStr: string;\r\n if (typeof text === \"string\") {\r\n textStr = text;\r\n } else if (text instanceof Uint8Array) {\r\n textStr = toBase64(text);\r\n } else {\r\n textStr = text.encodedBase64;\r\n }\r\n\r\n const trimmed = textStr.trim();\r\n if (!trimmed) {\r\n return {\r\n originalText: \"\",\r\n dictVersion: this.primaryDictVersion,\r\n encoded: new Uint8Array(0),\r\n payload: \"\",\r\n encodedBase64: \"\",\r\n originalBytes: 0,\r\n encodedBytes: 0,\r\n bytesSaved: 0,\r\n ratioPercent: 100,\r\n };\r\n }\r\n\r\n const words = trimmed.split(/\\s+/).filter(Boolean);\r\n const useVersion = options?.dictVersion ?? this.primaryDictVersion;\r\n\r\n const header = new Uint8Array([useVersion]);\r\n const chunks: Uint8Array[] = [header];\r\n\r\n const { forwardMap } = await this.getMapsForVersion(useVersion);\r\n\r\n for (const w of words) {\r\n const id = forwardMap.get(w);\r\n if (id) {\r\n chunks.push(id);\r\n } else {\r\n const utf8 = utf8Encode(w);\r\n const lenVarint = encodeVarint(utf8.length);\r\n const out = new Uint8Array(1 + lenVarint.length + utf8.length);\r\n out[0] = LITERAL;\r\n out.set(lenVarint, 1);\r\n out.set(utf8, 1 + lenVarint.length);\r\n chunks.push(out);\r\n }\r\n }\r\n\r\n const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);\r\n const result = new Uint8Array(totalLength);\r\n let offset = 0;\r\n for (const chunk of chunks) {\r\n result.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n\r\n const originalBytes = new TextEncoder().encode(textStr).length;\r\n const base64Result = toBase64(result);\r\n\r\n return {\r\n originalText: textStr,\r\n dictVersion: useVersion,\r\n encoded: result,\r\n payload: base64Result,\r\n encodedBase64: base64Result,\r\n originalBytes,\r\n encodedBytes: totalLength,\r\n bytesSaved: originalBytes - totalLength,\r\n ratioPercent:\r\n totalLength === 0\r\n ? 100\r\n : Math.round((totalLength / originalBytes) * 100 * 100) / 100,\r\n };\r\n }\r\n\r\n async decode(data: Uint8Array | string): Promise<string> {\r\n let buffer: Uint8Array;\r\n if (typeof data === \"string\") {\r\n buffer = fromBase64(data);\r\n } else {\r\n buffer = data;\r\n }\r\n\r\n if (buffer.length < 1) {\r\n throw new Error(\"Data too short to contain version byte\");\r\n }\r\n\r\n const version = buffer[0];\r\n let pos = 1;\r\n\r\n const { reverseMap, sortedIdLengths } =\r\n await this.getMapsForVersion(version);\r\n\r\n const result: string[] = [];\r\n const decoded = this.tryDecode(\r\n pos,\r\n buffer,\r\n reverseMap,\r\n result,\r\n 0,\r\n sortedIdLengths,\r\n );\r\n\r\n if (decoded === null) {\r\n throw new Error(\r\n \"Decode failed — possible data corruption, wrong dictionary version, or unsupported format\",\r\n );\r\n }\r\n\r\n return decoded;\r\n }\r\n\r\n private tryDecode(\r\n pos: number,\r\n buffer: Uint8Array,\r\n reverseMap: Map<string, string>,\r\n result: string[],\r\n depth: number,\r\n sortedIdLengths: number[],\r\n ): string | null {\r\n if (pos === buffer.length) {\r\n return result.join(\" \");\r\n }\r\n\r\n // 1. Try literal block\r\n if (buffer[pos] === LITERAL) {\r\n const { value: byteLen, bytesRead } = decodeVarint(buffer, pos + 1);\r\n\r\n // Basic sanity check — very large literals are suspicious\r\n if (byteLen > 1_000_000 || byteLen < 0) {\r\n return null;\r\n }\r\n\r\n const start = pos + 1 + bytesRead;\r\n const end = start + byteLen;\r\n\r\n if (end > buffer.length) {\r\n return null;\r\n }\r\n\r\n const literalBytes = buffer.subarray(start, end);\r\n const word = utf8Decode(literalBytes);\r\n\r\n result.push(word);\r\n const res = this.tryDecode(\r\n end,\r\n buffer,\r\n reverseMap,\r\n result,\r\n depth + 1,\r\n sortedIdLengths,\r\n );\r\n if (res !== null) return res;\r\n result.pop();\r\n }\r\n\r\n // 2. Try dictionary IDs — longest first\r\n for (const len of sortedIdLengths) {\r\n if (pos + len > buffer.length) continue;\r\n\r\n const slice = buffer.subarray(pos, pos + len);\r\n const key = toHex(slice);\r\n\r\n if (reverseMap.has(key)) {\r\n const word = reverseMap.get(key)!;\r\n result.push(word);\r\n\r\n const res = this.tryDecode(\r\n pos + len,\r\n buffer,\r\n reverseMap,\r\n result,\r\n depth + 1,\r\n sortedIdLengths,\r\n );\r\n if (res !== null) return res;\r\n\r\n result.pop();\r\n }\r\n }\r\n\r\n // No valid continuation found on this path\r\n return null;\r\n }\r\n}\r\n"],"names":["__filename","__dirname"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bigdreamsweb3/wordbin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "WordBin – Encode words & short text into tiny, reversible binary for storage, URLs, IoT, QR codes, metadata, blockchain, or Web3 apps.",
|
|
5
5
|
"author": "Agbaka Daniel Ugonna <99cratson@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,6 +39,9 @@
|
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "vite build",
|
|
42
|
+
"build:dict:v1": "npx tsx scripts/dictionaries/build-v1-bip39.mts",
|
|
43
|
+
"build:dict:v2": "npx tsx scripts/dictionaries/build-v2-dwyl.ts",
|
|
44
|
+
"build:dict:all": "npm run build:dict:v1 && npm run build:dict:v2",
|
|
42
45
|
"clean": "rimraf dist",
|
|
43
46
|
"prepack": "npm run build",
|
|
44
47
|
"dev": "vite",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dictionary-D3gr2Ala.js","sources":["../src/core/tiers.ts","../src/core/id.ts","../src/utils/buffer.ts","../src/dictionary.ts"],"sourcesContent":["export function getIdByteLength(wordLength: number): number {\r\n if (wordLength <= 4) return 2;\r\n if (wordLength <= 9) return 3;\r\n return 4;\r\n}\r\n\r\nexport function getWrapByteLength(wordLength: number): number {\r\n if (wordLength <= 4) return 2;\r\n if (wordLength <= 9) return 3;\r\n return 4;\r\n}\r\n\r\nexport async function getTextEncoder(): Promise<TextEncoder> {\r\n if (typeof TextEncoder !== \"undefined\") return new TextEncoder();\r\n const { TextEncoder: NodeTextEncoder } = await import(\"node:util\");\r\n // @ts-ignore Node typings\r\n return new NodeTextEncoder();\r\n}\r\n\r\nexport async function wrapBase64(data: string): Promise<Uint8Array> {\r\n const normalized = data.trim().toLowerCase();\r\n if (!normalized) throw new Error(\"Cannot generate ID for empty string\");\r\n\r\n const encoder = await getTextEncoder();\r\n const result = encoder.encode(normalized);\r\n\r\n // Browser + Node compatible SHA-256\r\n let hash: ArrayBuffer;\r\n const anyCrypto: any = (globalThis as any).crypto;\r\n if (anyCrypto && anyCrypto.subtle) {\r\n hash = await anyCrypto.subtle.digest(\"SHA-256\", result);\r\n } else {\r\n const { createHash } = await import(\"node:crypto\");\r\n hash = createHash(\"sha256\").update(Buffer.from(result)).digest().buffer;\r\n }\r\n\r\n const hashBytes = new Uint8Array(hash);\r\n const size = getWrapByteLength(normalized.length);\r\n return hashBytes.slice(0, size);\r\n}\r\n","import { getIdByteLength } from './tiers.js'\r\n\r\n/**\r\n * Deterministic word \t ID generator\r\n * Same output on browser and node (when using compatible input)\r\n */\r\nexport async function generateWordId(word: string): Promise<Uint8Array> {\r\n const normalized = word.trim().toLowerCase()\r\n if (!normalized) throw new Error('Cannot generate ID for empty string')\r\n\r\n const encoder = await getTextEncoder()\r\n const data = encoder.encode(normalized)\r\n\r\n // Browser + Node compatible SHA-256\r\n let hash: ArrayBuffer\r\n const anyCrypto: any = (globalThis as any).crypto\r\n if (anyCrypto && anyCrypto.subtle) {\r\n hash = await anyCrypto.subtle.digest('SHA-256', data)\r\n } else {\r\n const { createHash } = await import('node:crypto')\r\n hash = createHash('sha256').update(Buffer.from(data)).digest().buffer\r\n }\r\n\r\n const hashBytes = new Uint8Array(hash)\r\n const size = getIdByteLength(normalized.length)\r\n return hashBytes.slice(0, size)\r\n}\r\n\r\nasync function getTextEncoder(): Promise<TextEncoder> {\r\n if (typeof TextEncoder !== 'undefined') return new TextEncoder()\r\n const { TextEncoder: NodeTextEncoder } = await import('node:util')\r\n // @ts-ignore Node typings\r\n return new NodeTextEncoder()\r\n}\r\n","export function toHex(bytes: Uint8Array): string {\r\n return Array.from(bytes)\r\n .map((b) => b.toString(16).padStart(2, '0'))\r\n .join('')\r\n}\r\n\r\nexport function fromHex(hex: string): Uint8Array {\r\n if (hex.length % 2 !== 0) throw new Error('Invalid hex string length')\r\n const bytes = new Uint8Array(hex.length / 2)\r\n for (let i = 0; i < hex.length; i += 2) {\r\n bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16)\r\n }\r\n return bytes\r\n}\r\n\r\nexport function toBase64(bytes: Uint8Array): string {\r\n const b64 = (globalThis as any).btoa\r\n if (typeof b64 === 'function') {\r\n return b64(String.fromCharCode(...bytes))\r\n }\r\n // Node fallback\r\n return Buffer.from(bytes).toString('base64')\r\n}\r\n\r\nexport function fromBase64(base64: string): Uint8Array {\r\n const at = (globalThis as any).atob\r\n if (typeof at === 'function') {\r\n const binary = at(base64)\r\n const out = new Uint8Array(binary.length)\r\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)\r\n return out\r\n }\r\n // Node fallback\r\n return new Uint8Array(Buffer.from(base64, 'base64'))\r\n}\r\n\r\n// UTF-8 helpers\r\nexport function utf8Encode(str: string): Uint8Array {\r\n if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(str)\r\n // Node fallback\r\n return new Uint8Array(Buffer.from(str, 'utf8'))\r\n}\r\n\r\nexport function utf8Decode(bytes: Uint8Array): string {\r\n if (typeof TextDecoder !== 'undefined') return new TextDecoder().decode(bytes)\r\n // Node fallback\r\n return Buffer.from(bytes).toString('utf8')\r\n}\r\n\r\n// Varint (LEB128 7-bit groups) helpers\r\nexport function encodeVarint(n: number): Uint8Array {\r\n if (n < 0) throw new Error('Varint cannot encode negative numbers')\r\n const out: number[] = []\r\n do {\r\n let byte = n & 0x7f\r\n n >>>= 7\r\n if (n !== 0) byte |= 0x80\r\n out.push(byte)\r\n } while (n !== 0)\r\n return new Uint8Array(out)\r\n}\r\n\r\nexport function decodeVarint(bytes: Uint8Array, offset: number): { value: number; bytesRead: number } {\r\n let result = 0\r\n let shift = 0\r\n let pos = offset\r\n while (pos < bytes.length) {\r\n const byte = bytes[pos++]\r\n result |= (byte & 0x7f) << shift\r\n if ((byte & 0x80) === 0) {\r\n return { value: result, bytesRead: pos - offset }\r\n }\r\n shift += 7\r\n if (shift > 35) throw new Error('Varint too large')\r\n }\r\n throw new Error('Truncated varint')\r\n}\r\n","// File: src\\dictionary.ts\r\n\r\nimport type { WordBinDictionary } from \"./types\";\r\nimport { generateWordId } from \"./core/id.js\";\r\nimport { toHex } from \"./utils/buffer.js\";\r\n\r\nexport interface BuildDictionaryOptions {\r\n /**\r\n * Dictionary version number (used in header and for format compatibility)\r\n * @default 1\r\n */\r\n version?: number;\r\n\r\n /**\r\n * Human-readable description of this dictionary\r\n * @default \"WordBin dictionary v${version}\"\r\n */\r\n description?: string;\r\n\r\n /**\r\n * Optional: custom prefix or identifier for this dictionary build\r\n * (can be used in logs, filenames, etc.)\r\n */\r\n name?: string;\r\n}\r\n\r\nexport async function buildDictionary(\r\n words: string[],\r\n options: BuildDictionaryOptions = {},\r\n): Promise<WordBinDictionary> {\r\n const { version = 1, description = `WordBin dictionary v${version}` } =\r\n options;\r\n\r\n const map: Record<string, string[]> = {};\r\n\r\n const normalizedWords = words\r\n .map((w) => w.trim().toLowerCase())\r\n .filter((w) => w);\r\n\r\n await Promise.all(\r\n normalizedWords.map(async (word) => {\r\n const id = await generateWordId(word);\r\n const key = toHex(id);\r\n if (!map[key]) map[key] = [];\r\n map[key].push(word);\r\n }),\r\n );\r\n\r\n Object.values(map).forEach((collisions) => {\r\n collisions.sort((a, b) => a.localeCompare(b));\r\n });\r\n\r\n return {\r\n version,\r\n description,\r\n words: map,\r\n };\r\n}\r\n"],"names":[],"mappings":"AAAO,SAAS,gBAAgB,YAA4B;AAC1D,MAAI,cAAc,EAAG,QAAO;AAC5B,MAAI,cAAc,EAAG,QAAO;AAC5B,SAAO;AACT;ACEA,eAAsB,eAAe,MAAmC;AACtE,QAAM,aAAa,KAAK,KAAA,EAAO,YAAA;AAC/B,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,qCAAqC;AAEtE,QAAM,UAAU,MAAM,eAAA;AACtB,QAAM,OAAO,QAAQ,OAAO,UAAU;AAGtC,MAAI;AACJ,QAAM,YAAkB,WAAmB;AAC3C,MAAI,aAAa,UAAU,QAAQ;AACjC,WAAO,MAAM,UAAU,OAAO,OAAO,WAAW,IAAI;AAAA,EACtD,OAAO;AACL,UAAM,EAAE,WAAA,IAAe,MAAM,OAAO,aAAa;AACjD,WAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,KAAK,IAAI,CAAC,EAAE,OAAA,EAAS;AAAA,EACjE;AAEA,QAAM,YAAY,IAAI,WAAW,IAAI;AACrC,QAAM,OAAO,gBAAgB,WAAW,MAAM;AAC9C,SAAO,UAAU,MAAM,GAAG,IAAI;AAChC;AAEA,eAAe,iBAAuC;AACpD,MAAI,OAAO,gBAAgB,YAAa,QAAO,IAAI,YAAA;AACnD,QAAM,EAAE,aAAa,oBAAoB,MAAM,OAAO,WAAW;AAEjE,SAAO,IAAI,gBAAA;AACb;ACjCO,SAAS,MAAM,OAA2B;AAC/C,SAAO,MAAM,KAAK,KAAK,EACpB,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AACZ;AAWO,SAAS,SAAS,OAA2B;AAClD,QAAM,MAAO,WAAmB;AAChC,MAAI,OAAO,QAAQ,YAAY;AAC7B,WAAO,IAAI,OAAO,aAAa,GAAG,KAAK,CAAC;AAAA,EAC1C;AAEA,SAAO,OAAO,KAAK,KAAK,EAAE,SAAS,QAAQ;AAC7C;AAEO,SAAS,WAAW,QAA4B;AACrD,QAAM,KAAM,WAAmB;AAC/B,MAAI,OAAO,OAAO,YAAY;AAC5B,UAAM,SAAS,GAAG,MAAM;AACxB,UAAM,MAAM,IAAI,WAAW,OAAO,MAAM;AACxC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,KAAI,CAAC,IAAI,OAAO,WAAW,CAAC;AACpE,WAAO;AAAA,EACT;AAEA,SAAO,IAAI,WAAW,OAAO,KAAK,QAAQ,QAAQ,CAAC;AACrD;AAGO,SAAS,WAAW,KAAyB;AAClD,MAAI,OAAO,gBAAgB,YAAa,QAAO,IAAI,YAAA,EAAc,OAAO,GAAG;AAE3E,SAAO,IAAI,WAAW,OAAO,KAAK,KAAK,MAAM,CAAC;AAChD;AAEO,SAAS,WAAW,OAA2B;AACpD,MAAI,OAAO,gBAAgB,YAAa,QAAO,IAAI,YAAA,EAAc,OAAO,KAAK;AAE7E,SAAO,OAAO,KAAK,KAAK,EAAE,SAAS,MAAM;AAC3C;AAGO,SAAS,aAAa,GAAuB;AAClD,MAAI,IAAI,EAAG,OAAM,IAAI,MAAM,uCAAuC;AAClE,QAAM,MAAgB,CAAA;AACtB,KAAG;AACD,QAAI,OAAO,IAAI;AACf,WAAO;AACP,QAAI,MAAM,EAAG,SAAQ;AACrB,QAAI,KAAK,IAAI;AAAA,EACf,SAAS,MAAM;AACf,SAAO,IAAI,WAAW,GAAG;AAC3B;AAEO,SAAS,aAAa,OAAmB,QAAsD;AACpG,MAAI,SAAS;AACb,MAAI,QAAQ;AACZ,MAAI,MAAM;AACV,SAAO,MAAM,MAAM,QAAQ;AACzB,UAAM,OAAO,MAAM,KAAK;AACxB,eAAW,OAAO,QAAS;AAC3B,SAAK,OAAO,SAAU,GAAG;AACvB,aAAO,EAAE,OAAO,QAAQ,WAAW,MAAM,OAAA;AAAA,IAC3C;AACA,aAAS;AACT,QAAI,QAAQ,GAAI,OAAM,IAAI,MAAM,kBAAkB;AAAA,EACpD;AACA,QAAM,IAAI,MAAM,kBAAkB;AACpC;AClDA,eAAsB,gBACpB,OACA,UAAkC,IACN;AAC5B,QAAM,EAAE,UAAU,GAAG,cAAc,uBAAuB,OAAO,OAC/D;AAEF,QAAM,MAAgC,CAAA;AAEtC,QAAM,kBAAkB,MACrB,IAAI,CAAC,MAAM,EAAE,KAAA,EAAO,YAAA,CAAa,EACjC,OAAO,CAAC,MAAM,CAAC;AAElB,QAAM,QAAQ;AAAA,IACZ,gBAAgB,IAAI,OAAO,SAAS;AAClC,YAAM,KAAK,MAAM,eAAe,IAAI;AACpC,YAAM,MAAM,MAAM,EAAE;AACpB,UAAI,CAAC,IAAI,GAAG,EAAG,KAAI,GAAG,IAAI,CAAA;AAC1B,UAAI,GAAG,EAAE,KAAK,IAAI;AAAA,IACpB,CAAC;AAAA,EAAA;AAGH,SAAO,OAAO,GAAG,EAAE,QAAQ,CAAC,eAAe;AACzC,eAAW,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AAAA,EAC9C,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EAAA;AAEX;"}
|