mcard-js 2.1.48 → 2.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/AbstractSqlEngine-DKka6XjT.d.cts +451 -0
  2. package/dist/AbstractSqlEngine-DKka6XjT.d.ts +451 -0
  3. package/dist/CardCollection-ZQ3G3Q3A.js +10 -0
  4. package/dist/IndexedDBEngine-BWXAB46W.js +12 -0
  5. package/dist/LLMRuntime-PH3MOQ2Y.js +17 -0
  6. package/dist/LambdaRuntime-YH74FHIW.js +19 -0
  7. package/dist/Loader-WZXYG4GE.js +12 -0
  8. package/dist/NetworkRuntime-S4DZCGVN.js +1598 -0
  9. package/dist/OllamaProvider-SPGO5Z5E.js +9 -0
  10. package/dist/chunk-3FFEA2XK.js +149 -0
  11. package/dist/chunk-7AXRV7NS.js +112 -0
  12. package/dist/chunk-HIVVDGE5.js +497 -0
  13. package/dist/chunk-KVZYFZJ5.js +427 -0
  14. package/dist/chunk-NGTY4P6A.js +275 -0
  15. package/dist/chunk-OUW2SUGM.js +368 -0
  16. package/dist/chunk-QKH3N62B.js +2360 -0
  17. package/dist/chunk-QPVEUPMU.js +299 -0
  18. package/dist/chunk-VYDZR4ZD.js +364 -0
  19. package/dist/chunk-XJZOEM5F.js +903 -0
  20. package/dist/chunk-Z7EFXSTO.js +217 -0
  21. package/dist/index.browser.cjs +37 -1
  22. package/dist/index.browser.d.cts +20 -2
  23. package/dist/index.browser.d.ts +20 -2
  24. package/dist/index.browser.js +10 -6
  25. package/dist/index.cjs +618 -146
  26. package/dist/index.d.cts +723 -4
  27. package/dist/index.d.ts +723 -4
  28. package/dist/index.js +527 -89
  29. package/dist/storage/SqliteNodeEngine.cjs +7 -1
  30. package/dist/storage/SqliteNodeEngine.d.cts +1 -1
  31. package/dist/storage/SqliteNodeEngine.d.ts +1 -1
  32. package/dist/storage/SqliteNodeEngine.js +3 -3
  33. package/dist/storage/SqliteWasmEngine.cjs +7 -1
  34. package/dist/storage/SqliteWasmEngine.d.cts +1 -1
  35. package/dist/storage/SqliteWasmEngine.d.ts +1 -1
  36. package/dist/storage/SqliteWasmEngine.js +3 -3
  37. package/package.json +1 -1
@@ -0,0 +1,427 @@
1
+ import {
2
+ BaseLLMProvider,
3
+ DEFAULT_PROVIDER,
4
+ LLMConfig,
5
+ LLM_PROVIDERS,
6
+ OllamaProvider
7
+ } from "./chunk-OUW2SUGM.js";
8
+ import {
9
+ IO
10
+ } from "./chunk-MPMRBT5R.js";
11
+ import {
12
+ Either
13
+ } from "./chunk-2KADE3SE.js";
14
+ import {
15
+ LLM_DEFAULT_TIMEOUT_SECS
16
+ } from "./chunk-3FFEA2XK.js";
17
+
18
+ // src/ptr/llm/providers/WebLLMProvider.ts
19
+ function asOptionalNumber(value) {
20
+ return typeof value === "number" ? value : void 0;
21
+ }
22
+ var WebLLMProvider = class extends BaseLLMProvider {
23
+ provider_name = "webllm";
24
+ config;
25
+ engine = null;
26
+ current_model = null;
27
+ initialization_promise = null;
28
+ constructor() {
29
+ super();
30
+ this.config = LLM_PROVIDERS["webllm"];
31
+ }
32
+ async _get_engine(model_id) {
33
+ if (this.engine && this.current_model === model_id) {
34
+ return Either.right(this.engine);
35
+ }
36
+ if (this.initialization_promise) {
37
+ await this.initialization_promise;
38
+ if (this.engine && this.current_model === model_id) {
39
+ return Either.right(this.engine);
40
+ }
41
+ }
42
+ this.initialization_promise = (async () => {
43
+ try {
44
+ if (typeof window === "undefined") {
45
+ throw new Error("WebLLM only supports browser environments.");
46
+ }
47
+ const windowWithWebLLM = window;
48
+ let webllm = windowWithWebLLM.webllm;
49
+ if (!webllm) {
50
+ try {
51
+ webllm = await import("@mlc-ai/web-llm");
52
+ } catch (e) {
53
+ void e;
54
+ }
55
+ }
56
+ if (!webllm) {
57
+ throw new Error("WebLLM library not found. Please include @mlc-ai/web-llm or add script tag.");
58
+ }
59
+ if (!this.engine) {
60
+ this.engine = await webllm.CreateMLCEngine(model_id, {
61
+ initProgressCallback: (report) => {
62
+ console.debug(`[WebLLM] ${report.text}`);
63
+ }
64
+ });
65
+ } else {
66
+ await this.engine.reload(model_id);
67
+ }
68
+ this.current_model = model_id;
69
+ } catch (e) {
70
+ this.engine = null;
71
+ this.current_model = null;
72
+ throw e;
73
+ }
74
+ })();
75
+ try {
76
+ await this.initialization_promise;
77
+ return Either.right(this.engine);
78
+ } catch (e) {
79
+ this.initialization_promise = null;
80
+ const error = e;
81
+ return Either.left(`WebLLM init failed: ${error.message || String(e)}`);
82
+ }
83
+ }
84
+ async complete(prompt, params, images) {
85
+ const model = typeof params.model === "string" ? params.model : this.config.default_model;
86
+ const engineResult = await this._get_engine(model);
87
+ if (engineResult.isLeft) return Either.left(engineResult.left);
88
+ const engine = engineResult.right;
89
+ try {
90
+ const completion = await engine.chat.completions.create({
91
+ messages: [{ role: "user", content: prompt }],
92
+ temperature: asOptionalNumber(params.temperature),
93
+ max_tokens: asOptionalNumber(params.max_tokens),
94
+ top_p: asOptionalNumber(params.top_p),
95
+ stream: false
96
+ });
97
+ const content = completion.choices?.[0]?.message?.content || "";
98
+ return Either.right(content);
99
+ } catch (e) {
100
+ const error = e;
101
+ return Either.left(`WebLLM completion error: ${error.message || String(e)}`);
102
+ }
103
+ }
104
+ async chat(messages, params) {
105
+ const model = typeof params.model === "string" ? params.model : this.config.default_model;
106
+ const engineResult = await this._get_engine(model);
107
+ if (engineResult.isLeft) return Either.left(engineResult.left);
108
+ const engine = engineResult.right;
109
+ try {
110
+ const completion = await engine.chat.completions.create({
111
+ messages,
112
+ temperature: asOptionalNumber(params.temperature),
113
+ max_tokens: asOptionalNumber(params.max_tokens),
114
+ top_p: asOptionalNumber(params.top_p),
115
+ stream: false
116
+ });
117
+ const choice = completion.choices?.[0];
118
+ return Either.right({
119
+ content: choice?.message?.content || "",
120
+ role: choice?.message?.role || "assistant",
121
+ model,
122
+ usage: completion.usage
123
+ });
124
+ } catch (e) {
125
+ const error = e;
126
+ return Either.left(`WebLLM chat error: ${error.message || String(e)}`);
127
+ }
128
+ }
129
+ async validate_connection() {
130
+ if (typeof window === "undefined") return false;
131
+ if (window.webllm) return true;
132
+ try {
133
+ await import("@mlc-ai/web-llm");
134
+ return true;
135
+ } catch {
136
+ return false;
137
+ }
138
+ }
139
+ async list_models() {
140
+ return Either.right(this.config.available_models);
141
+ }
142
+ };
143
+
144
+ // src/ptr/llm/providers/MLCLLMProvider.ts
145
+ import * as http from "http";
146
+ import * as https from "https";
147
+ var MLCLLMProvider = class extends BaseLLMProvider {
148
+ provider_name = "mlc-llm";
149
+ base_url;
150
+ timeout;
151
+ config;
152
+ constructor(base_url = null, timeout = LLM_DEFAULT_TIMEOUT_SECS) {
153
+ super();
154
+ this.config = LLM_PROVIDERS["mlc-llm"];
155
+ this.base_url = (base_url || this.config.base_url).replace(/\/$/, "");
156
+ this.timeout = timeout * 1e3;
157
+ }
158
+ async _fetch_json(endpoint, options) {
159
+ if (typeof globalThis.fetch === "function") {
160
+ try {
161
+ const controller = new AbortController();
162
+ const id = setTimeout(() => controller.abort(), this.timeout);
163
+ const response = await fetch(`${this.base_url}${endpoint}`, {
164
+ ...options,
165
+ signal: controller.signal
166
+ });
167
+ clearTimeout(id);
168
+ if (!response.ok) {
169
+ return Either.left(`HTTP error ${response.status}: ${await response.text()}`);
170
+ }
171
+ const data = await response.json();
172
+ return Either.right(data);
173
+ } catch (e) {
174
+ const error = e;
175
+ return Either.left(`Connection error: ${error.message || String(e)}`);
176
+ }
177
+ }
178
+ return this._node_request(endpoint, options);
179
+ }
180
+ _node_request(endpoint, options) {
181
+ const urlStr = `${this.base_url}${endpoint}`;
182
+ const url = new URL(urlStr);
183
+ const isHttps = url.protocol === "https:";
184
+ const client = isHttps ? https : http;
185
+ const reqOptions = {
186
+ method: options.method || "GET",
187
+ headers: options.headers || {},
188
+ timeout: this.timeout
189
+ };
190
+ return new Promise((resolve) => {
191
+ const req = client.request(url, reqOptions, (res) => {
192
+ let body = "";
193
+ res.on("data", (chunk) => body += chunk);
194
+ res.on("end", () => {
195
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
196
+ try {
197
+ resolve(Either.right(JSON.parse(body)));
198
+ } catch (e) {
199
+ resolve(Either.left(`Parse error: ${e}`));
200
+ }
201
+ } else {
202
+ resolve(Either.left(`HTTP Error ${res.statusCode}: ${body}`));
203
+ }
204
+ });
205
+ });
206
+ req.on("error", (e) => resolve(Either.left(e.message)));
207
+ req.on("timeout", () => {
208
+ req.destroy();
209
+ resolve(Either.left("Request timed out"));
210
+ });
211
+ if (options.body) {
212
+ req.write(options.body);
213
+ }
214
+ req.end();
215
+ });
216
+ }
217
+ async complete(prompt, params, images) {
218
+ const data = {
219
+ model: typeof params.model === "string" ? params.model : this.config.default_model,
220
+ messages: [{ role: "user", content: prompt }],
221
+ max_tokens: params.max_tokens,
222
+ temperature: params.temperature,
223
+ top_p: params.top_p,
224
+ stream: false
225
+ };
226
+ const result = await this._fetch_json(this.config.api_path, {
227
+ method: "POST",
228
+ headers: { "Content-Type": "application/json" },
229
+ body: JSON.stringify(data)
230
+ });
231
+ if (result.isLeft) return Either.left(result.left);
232
+ const response = result.right;
233
+ if (response.choices && response.choices.length > 0) {
234
+ return Either.right(response.choices[0].text || "");
235
+ }
236
+ return Either.left(`Unexpected response format: ${JSON.stringify(response)}`);
237
+ }
238
+ async chat(messages, params) {
239
+ const data = {
240
+ model: params.model || this.config.default_model,
241
+ messages,
242
+ max_tokens: params.max_tokens,
243
+ temperature: params.temperature,
244
+ top_p: params.top_p,
245
+ stream: false
246
+ };
247
+ const result = await this._fetch_json(this.config.chat_path, {
248
+ method: "POST",
249
+ headers: { "Content-Type": "application/json" },
250
+ body: JSON.stringify(data)
251
+ });
252
+ if (result.isLeft) return Either.left(result.left);
253
+ const response = result.right;
254
+ if (response.choices && response.choices.length > 0) {
255
+ const message = response.choices[0].message;
256
+ return Either.right({
257
+ content: message?.content || "",
258
+ role: message?.role || "assistant",
259
+ model: response.model,
260
+ usage: response.usage
261
+ });
262
+ }
263
+ return Either.left(`Unexpected response format: ${JSON.stringify(response)}`);
264
+ }
265
+ async validate_connection() {
266
+ const result = await this._fetch_json(this.config.models_path, { method: "GET" });
267
+ return result.isRight;
268
+ }
269
+ async list_models() {
270
+ const result = await this._fetch_json(this.config.models_path, { method: "GET" });
271
+ if (result.isLeft) return Either.left(result.left);
272
+ const response = result.right;
273
+ if (response.data && Array.isArray(response.data)) {
274
+ return Either.right(response.data.map((m) => m.id).filter((id) => typeof id === "string"));
275
+ }
276
+ return Either.left("Invalid models response");
277
+ }
278
+ };
279
+
280
+ // src/ptr/llm/LLMRuntime.ts
281
+ function get_provider(provider_name = DEFAULT_PROVIDER, base_url = null, timeout = LLM_DEFAULT_TIMEOUT_SECS) {
282
+ if (provider_name === "ollama") {
283
+ return new OllamaProvider(base_url, timeout);
284
+ }
285
+ if (provider_name === "webllm") {
286
+ return new WebLLMProvider();
287
+ }
288
+ if (provider_name === "mlc-llm") {
289
+ return new MLCLLMProvider(base_url, timeout);
290
+ }
291
+ throw new Error(`Unknown provider: ${provider_name}`);
292
+ }
293
+ var LLMRuntime = class {
294
+ provider_name;
295
+ _provider = null;
296
+ constructor(provider_name = DEFAULT_PROVIDER) {
297
+ this.provider_name = provider_name;
298
+ }
299
+ get provider() {
300
+ if (!this._provider) {
301
+ this._provider = get_provider(this.provider_name);
302
+ }
303
+ return this._provider;
304
+ }
305
+ async execute(codeOrPath, context, config, chapterDir) {
306
+ let configCtx = {};
307
+ if (typeof context === "object" && context !== null) {
308
+ configCtx = context;
309
+ }
310
+ const concrete = config;
311
+ const llmConfig = LLMConfig.from_concrete(concrete, configCtx);
312
+ if (llmConfig.provider !== this.provider_name) {
313
+ this._provider = get_provider(llmConfig.provider, llmConfig.endpoint_url, llmConfig.timeout);
314
+ }
315
+ let prompt = "";
316
+ let images;
317
+ if (typeof context === "string") {
318
+ prompt = context;
319
+ } else if (context && typeof context === "object") {
320
+ const ctx = context;
321
+ if (typeof ctx.prompt === "string") {
322
+ prompt = ctx.prompt;
323
+ if (Array.isArray(ctx.images)) {
324
+ images = ctx.images;
325
+ }
326
+ } else {
327
+ prompt = JSON.stringify(context);
328
+ }
329
+ }
330
+ let result;
331
+ if (llmConfig.system_prompt) {
332
+ result = await this._execute_chat(prompt, llmConfig, images);
333
+ } else {
334
+ result = await this._execute_completion(prompt, llmConfig, images);
335
+ }
336
+ if (result.isLeft) {
337
+ return `Error: ${result.left}`;
338
+ }
339
+ return this._format_response(result.right, llmConfig);
340
+ }
341
+ async _execute_completion(prompt, config, images) {
342
+ const params = config.to_provider_params();
343
+ return this.provider.complete(prompt, params, images);
344
+ }
345
+ async _execute_chat(prompt, config, images) {
346
+ const messages = [];
347
+ if (config.system_prompt) {
348
+ messages.push({ role: "system", content: config.system_prompt });
349
+ }
350
+ const userMsg = { role: "user", content: prompt };
351
+ if (images && images.length > 0) {
352
+ userMsg.images = images;
353
+ }
354
+ messages.push(userMsg);
355
+ if (config.assistant_instruction) {
356
+ messages.push({ role: "assistant", content: config.assistant_instruction });
357
+ }
358
+ const params = config.to_provider_params();
359
+ return this.provider.chat(messages, params);
360
+ }
361
+ _format_response(response, config) {
362
+ let content = response;
363
+ if (response && typeof response === "object" && "content" in response) {
364
+ content = response.content;
365
+ }
366
+ if (config.response_format === "json") {
367
+ try {
368
+ if (typeof content === "string") {
369
+ const start = content.indexOf("{");
370
+ const end = content.lastIndexOf("}") + 1;
371
+ if (start >= 0 && end > start) {
372
+ return JSON.parse(content.substring(start, end));
373
+ }
374
+ }
375
+ return content;
376
+ } catch (e) {
377
+ return content;
378
+ }
379
+ }
380
+ return content;
381
+ }
382
+ };
383
+ function promptMonad(prompt, config = {}) {
384
+ return IO.of(async () => {
385
+ try {
386
+ const llmConfig = new LLMConfig(config);
387
+ const runtime = new LLMRuntime(llmConfig.provider);
388
+ const params = llmConfig.to_provider_params();
389
+ return runtime.provider.complete(prompt, params);
390
+ } catch (e) {
391
+ return Either.left(`LLM execution failed: ${e}`);
392
+ }
393
+ });
394
+ }
395
+ function chatMonad(messages = null, prompt = null, system_prompt = "", config = {}) {
396
+ return IO.of(async () => {
397
+ try {
398
+ const configData = { ...config };
399
+ if (system_prompt) configData.system_prompt = system_prompt;
400
+ const llmConfig = new LLMConfig(configData);
401
+ const runtime = new LLMRuntime(llmConfig.provider);
402
+ const msgs = messages ? [...messages] : [];
403
+ if (msgs.length === 0) {
404
+ if (llmConfig.system_prompt) {
405
+ msgs.push({ role: "system", content: llmConfig.system_prompt });
406
+ }
407
+ if (prompt) {
408
+ msgs.push({ role: "user", content: prompt });
409
+ }
410
+ if (llmConfig.assistant_instruction) {
411
+ msgs.push({ role: "assistant", content: llmConfig.assistant_instruction });
412
+ }
413
+ }
414
+ const params = llmConfig.to_provider_params();
415
+ return runtime.provider.chat(msgs, params);
416
+ } catch (e) {
417
+ return Either.left(`LLM chat failed: ${e}`);
418
+ }
419
+ });
420
+ }
421
+
422
+ export {
423
+ get_provider,
424
+ LLMRuntime,
425
+ promptMonad,
426
+ chatMonad
427
+ };
@@ -0,0 +1,275 @@
1
+ import {
2
+ createPage
3
+ } from "./chunk-3EIBJPNF.js";
4
+ import {
5
+ init_Handle,
6
+ validateHandle
7
+ } from "./chunk-ADV52544.js";
8
+ import {
9
+ DEFAULT_PAGE_SIZE,
10
+ INDEXEDDB_DEFAULT_DB_NAME,
11
+ INDEXEDDB_DEFAULT_DB_VERSION
12
+ } from "./chunk-3FFEA2XK.js";
13
+ import {
14
+ MCard
15
+ } from "./chunk-GGQCF7ZK.js";
16
+
17
+ // src/storage/engines/IndexedDBEngine.ts
18
+ import { openDB } from "idb";
19
+ init_Handle();
20
+ var IndexedDBEngine = class {
21
+ db = null;
22
+ dbName;
23
+ constructor(dbName = INDEXEDDB_DEFAULT_DB_NAME) {
24
+ this.dbName = dbName;
25
+ }
26
+ /**
27
+ * Initialize the database connection
28
+ */
29
+ async init() {
30
+ this.db = await openDB(this.dbName, INDEXEDDB_DEFAULT_DB_VERSION, {
31
+ upgrade(db) {
32
+ if (!db.objectStoreNames.contains("cards")) {
33
+ db.createObjectStore("cards", { keyPath: "hash" });
34
+ }
35
+ if (!db.objectStoreNames.contains("handles")) {
36
+ const handleStore = db.createObjectStore("handles", { keyPath: "handle" });
37
+ handleStore.createIndex("by-hash", "currentHash");
38
+ }
39
+ if (!db.objectStoreNames.contains("handleHistory")) {
40
+ const historyStore = db.createObjectStore("handleHistory", {
41
+ keyPath: "id",
42
+ autoIncrement: true
43
+ });
44
+ historyStore.createIndex("by-handle", "handle");
45
+ }
46
+ }
47
+ });
48
+ }
49
+ ensureDb() {
50
+ if (!this.db) {
51
+ throw new Error("Database not initialized. Call init() first.");
52
+ }
53
+ return this.db;
54
+ }
55
+ // =========== Card Operations ===========
56
+ async add(card) {
57
+ const db = this.ensureDb();
58
+ await db.put("cards", {
59
+ hash: card.hash,
60
+ content: card.content,
61
+ g_time: card.g_time
62
+ });
63
+ return card.hash;
64
+ }
65
+ async get(hash) {
66
+ const db = this.ensureDb();
67
+ const record = await db.get("cards", hash);
68
+ if (!record) return null;
69
+ return MCard.fromData(record.content, record.hash, record.g_time);
70
+ }
71
+ async delete(hash) {
72
+ const db = this.ensureDb();
73
+ await db.delete("cards", hash);
74
+ }
75
+ async getPage(pageNumber, pageSize = DEFAULT_PAGE_SIZE) {
76
+ const db = this.ensureDb();
77
+ const totalItems = await db.count("cards");
78
+ const allCards = await db.getAll("cards");
79
+ const start = (pageNumber - 1) * pageSize;
80
+ const pageRecords = allCards.slice(start, start + pageSize);
81
+ const items = pageRecords.map((r) => MCard.fromData(r.content, r.hash, r.g_time));
82
+ return createPage(items, totalItems, pageNumber, pageSize);
83
+ }
84
+ async count() {
85
+ const db = this.ensureDb();
86
+ return db.count("cards");
87
+ }
88
+ async searchByHash(hashPrefix) {
89
+ const db = this.ensureDb();
90
+ const start = hashPrefix;
91
+ const end = hashPrefix + "\uFFFF";
92
+ const range = IDBKeyRange.bound(start, end);
93
+ const records = await db.getAll("cards", range);
94
+ return records.map((r) => MCard.fromData(r.content, r.hash, r.g_time));
95
+ }
96
+ async search(query, pageNumber, pageSize = DEFAULT_PAGE_SIZE) {
97
+ const db = this.ensureDb();
98
+ const records = await db.getAll("cards");
99
+ const decoder = new TextDecoder();
100
+ const filtered = records.filter((r) => {
101
+ try {
102
+ const text = decoder.decode(r.content);
103
+ return text.includes(query);
104
+ } catch {
105
+ return false;
106
+ }
107
+ });
108
+ const totalItems = filtered.length;
109
+ const start = (pageNumber - 1) * pageSize;
110
+ const pageItems = filtered.slice(start, start + pageSize).map((r) => MCard.fromData(r.content, r.hash, r.g_time));
111
+ return createPage(pageItems, totalItems, pageNumber, pageSize);
112
+ }
113
+ async getAll() {
114
+ const db = this.ensureDb();
115
+ const records = await db.getAll("cards");
116
+ return records.map((r) => MCard.fromData(r.content, r.hash, r.g_time));
117
+ }
118
+ async clear() {
119
+ const db = this.ensureDb();
120
+ await db.clear("cards");
121
+ await db.clear("handles");
122
+ await db.clear("handleHistory");
123
+ }
124
+ // =========== Handle Operations ===========
125
+ async registerHandle(handle, hash) {
126
+ const db = this.ensureDb();
127
+ const normalized = validateHandle(handle);
128
+ const existing = await db.get("handles", normalized);
129
+ if (existing) {
130
+ throw new Error(`Handle '${handle}' already exists.`);
131
+ }
132
+ const now = (/* @__PURE__ */ new Date()).toISOString();
133
+ await db.put("handles", {
134
+ handle: normalized,
135
+ currentHash: hash,
136
+ createdAt: now,
137
+ updatedAt: now
138
+ });
139
+ }
140
+ async resolveHandle(handle) {
141
+ const db = this.ensureDb();
142
+ const normalized = validateHandle(handle);
143
+ const record = await db.get("handles", normalized);
144
+ return record?.currentHash ?? null;
145
+ }
146
+ async getByHandle(handle) {
147
+ const hash = await this.resolveHandle(handle);
148
+ if (!hash) return null;
149
+ return this.get(hash);
150
+ }
151
+ async updateHandle(handle, newHash) {
152
+ const db = this.ensureDb();
153
+ const normalized = validateHandle(handle);
154
+ const existing = await db.get("handles", normalized);
155
+ if (!existing) {
156
+ throw new Error(`Handle '${handle}' not found.`);
157
+ }
158
+ const previousHash = existing.currentHash;
159
+ const now = (/* @__PURE__ */ new Date()).toISOString();
160
+ await db.add("handleHistory", {
161
+ handle: normalized,
162
+ previousHash,
163
+ changedAt: now
164
+ });
165
+ await db.put("handles", {
166
+ ...existing,
167
+ currentHash: newHash,
168
+ updatedAt: now
169
+ });
170
+ return previousHash;
171
+ }
172
+ async getHandleHistory(handle) {
173
+ const db = this.ensureDb();
174
+ const normalized = validateHandle(handle);
175
+ const records = await db.getAllFromIndex("handleHistory", "by-handle", normalized);
176
+ return records.map((r) => ({ previousHash: r.previousHash, changedAt: r.changedAt })).reverse();
177
+ }
178
+ async pruneHandleHistory(handle, options = {}) {
179
+ const db = this.ensureDb();
180
+ const normalized = validateHandle(handle);
181
+ const records = await db.getAllFromIndex("handleHistory", "by-handle", normalized);
182
+ let toDelete;
183
+ if (options.deleteAll) {
184
+ toDelete = records;
185
+ } else if (options.olderThan) {
186
+ toDelete = records.filter((r) => r.changedAt < options.olderThan);
187
+ } else {
188
+ return 0;
189
+ }
190
+ const tx = db.transaction("handleHistory", "readwrite");
191
+ for (const record of toDelete) {
192
+ await tx.store.delete(record.id);
193
+ }
194
+ await tx.done;
195
+ return toDelete.length;
196
+ }
197
+ // =========== Handle Management Operations ===========
198
+ async getAllHandles() {
199
+ const db = this.ensureDb();
200
+ const records = await db.getAll("handles");
201
+ return records.map((r) => ({ handle: r.handle, hash: r.currentHash }));
202
+ }
203
+ async removeHandle(handle) {
204
+ const db = this.ensureDb();
205
+ const normalized = validateHandle(handle);
206
+ const keysToTry = normalized !== handle.trim() ? [normalized, handle.trim()] : [normalized];
207
+ const tx = db.transaction(["handles", "handleHistory"], "readwrite");
208
+ const handleStore = tx.objectStore("handles");
209
+ const historyStore = tx.objectStore("handleHistory");
210
+ for (const key of keysToTry) {
211
+ await handleStore.delete(key);
212
+ }
213
+ const historyIndex = historyStore.index("by-handle");
214
+ for (const key of keysToTry) {
215
+ let cursor = await historyIndex.openCursor(key);
216
+ while (cursor) {
217
+ await cursor.delete();
218
+ cursor = await cursor.continue();
219
+ }
220
+ }
221
+ await tx.done;
222
+ }
223
+ async renameHandle(oldHandle, newHandle) {
224
+ const db = this.ensureDb();
225
+ const normalizedOld = validateHandle(oldHandle);
226
+ const normalizedNew = validateHandle(newHandle);
227
+ if (normalizedOld === normalizedNew) return;
228
+ const oldEntry = await db.get("handles", normalizedOld);
229
+ if (!oldEntry) throw new Error(`Handle '${oldHandle}' not found.`);
230
+ const newEntry = await db.get("handles", normalizedNew);
231
+ if (newEntry) throw new Error(`Handle '${newHandle}' already exists.`);
232
+ const tx = db.transaction(["handles", "handleHistory"], "readwrite");
233
+ const handleStore = tx.objectStore("handles");
234
+ const historyStore = tx.objectStore("handleHistory");
235
+ const now = (/* @__PURE__ */ new Date()).toISOString();
236
+ await handleStore.put({
237
+ ...oldEntry,
238
+ handle: normalizedNew,
239
+ updatedAt: now
240
+ });
241
+ await handleStore.delete(normalizedOld);
242
+ const historyIndex = historyStore.index("by-handle");
243
+ let cursor = await historyIndex.openCursor(normalizedOld);
244
+ while (cursor) {
245
+ const record = cursor.value;
246
+ record.handle = normalizedNew;
247
+ await cursor.update(record);
248
+ cursor = await cursor.continue();
249
+ }
250
+ await tx.done;
251
+ }
252
+ async deleteHistoryEntry(handle, previousHash) {
253
+ const db = this.ensureDb();
254
+ const normalized = validateHandle(handle);
255
+ const records = await db.getAllFromIndex("handleHistory", "by-handle", normalized);
256
+ const entries = records.sort((a, b) => a.id - b.id);
257
+ const targetIdx = entries.findIndex((e) => e.previousHash === previousHash);
258
+ if (targetIdx === -1) return;
259
+ const target = entries[targetIdx];
260
+ const successor = entries[targetIdx + 1];
261
+ const tx = db.transaction("handleHistory", "readwrite");
262
+ const store = tx.objectStore("handleHistory");
263
+ if (successor && targetIdx > 0) {
264
+ const predecessor = entries[targetIdx - 1];
265
+ successor.previousHash = predecessor.previousHash;
266
+ await store.put(successor);
267
+ }
268
+ await store.delete(target.id);
269
+ await tx.done;
270
+ }
271
+ };
272
+
273
+ export {
274
+ IndexedDBEngine
275
+ };