@fileverse/api 0.0.1

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/worker.js ADDED
@@ -0,0 +1,2846 @@
1
+ // src/config/index.ts
2
+ import dotenv from "dotenv";
3
+ import path from "path";
4
+ import fs from "fs";
5
+ import os from "os";
6
+
7
+ // src/cli/constants.generated.ts
8
+ var STATIC_CONFIG = {
9
+ API_URL: "https://prod-apps-storage-5cdacc06ff79.herokuapp.com/",
10
+ SERVER_DID: "did:key:z6Mkroj9bxTin6Z5S9qwx2G2b87NPrCX7S85FhCpmBGPcDCz",
11
+ PROXY_SERVER_DID: "did:key:z6MkrZSmq8D6vQG87YbjUQatXeptaCCXWdTx8fYaWxWbRUHB",
12
+ NETWORK_NAME: "gnosis",
13
+ DEFAULT_PORT: "8001",
14
+ DEFAULT_RPC_URL: "https://rpc.gnosischain.com",
15
+ PIMLICO_PROXY_URL: "https://pimlico-proxy-0a326da116f8.herokuapp.com/",
16
+ SERVICE_NAME: "fileverse-api",
17
+ LOG_LEVEL: "info",
18
+ FRONTEND_URL: "https://docs.fileverse.io"
19
+ };
20
+
21
+ // src/config/index.ts
22
+ var projectEnvPath = path.join(process.cwd(), "config", ".env");
23
+ var userEnvPath = path.join(os.homedir(), ".fileverse", ".env");
24
+ function getEnvPath() {
25
+ if (fs.existsSync(projectEnvPath)) {
26
+ return projectEnvPath;
27
+ }
28
+ return userEnvPath;
29
+ }
30
+ function loadConfig(override = true) {
31
+ const envPath = getEnvPath();
32
+ dotenv.config({ path: envPath, override });
33
+ }
34
+ loadConfig(false);
35
+ function getRuntimeConfig() {
36
+ return {
37
+ get API_KEY() {
38
+ return process.env.API_KEY;
39
+ },
40
+ get RPC_URL() {
41
+ return process.env.RPC_URL || STATIC_CONFIG.DEFAULT_RPC_URL;
42
+ },
43
+ get DB_PATH() {
44
+ return process.env.DB_PATH;
45
+ },
46
+ get PORT() {
47
+ return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;
48
+ },
49
+ get NODE_ENV() {
50
+ return process.env.NODE_ENV || "production";
51
+ },
52
+ get FRONTEND_URL() {
53
+ return process.env.FRONTEND_URL || STATIC_CONFIG.FRONTEND_URL;
54
+ }
55
+ };
56
+ }
57
+ function validateDbPath() {
58
+ const dbPath = process.env.DB_PATH;
59
+ if (!dbPath) {
60
+ console.error("Error: DB_PATH environment variable is required");
61
+ console.error("Please set DB_PATH in your .env file (config/.env or ~/.fileverse/.env) or run the CLI first");
62
+ process.exit(1);
63
+ }
64
+ const dbDir = path.dirname(dbPath.trim());
65
+ if (!fs.existsSync(dbDir)) {
66
+ fs.mkdirSync(dbDir, { recursive: true });
67
+ }
68
+ }
69
+ var config = {
70
+ ...STATIC_CONFIG,
71
+ get SERVICE_NAME() {
72
+ return STATIC_CONFIG.SERVICE_NAME;
73
+ },
74
+ get LOG_LEVEL() {
75
+ return STATIC_CONFIG.LOG_LEVEL;
76
+ },
77
+ get NETWORK_NAME() {
78
+ return STATIC_CONFIG.NETWORK_NAME;
79
+ },
80
+ get UPLOAD_SERVER_URL() {
81
+ return STATIC_CONFIG.API_URL;
82
+ },
83
+ get UPLOAD_SERVER_DID() {
84
+ return STATIC_CONFIG.SERVER_DID;
85
+ },
86
+ get API_KEY() {
87
+ return process.env.API_KEY;
88
+ },
89
+ get RPC_URL() {
90
+ return process.env.RPC_URL || STATIC_CONFIG.DEFAULT_RPC_URL;
91
+ },
92
+ get DB_PATH() {
93
+ return process.env.DB_PATH;
94
+ },
95
+ get PORT() {
96
+ return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;
97
+ },
98
+ get NODE_ENV() {
99
+ return process.env.NODE_ENV || "production";
100
+ },
101
+ get IP() {
102
+ return process.env.IP || "0.0.0.0";
103
+ },
104
+ get FRONTEND_URL() {
105
+ return process.env.FRONTEND_URL || STATIC_CONFIG.FRONTEND_URL;
106
+ }
107
+ };
108
+
109
+ // src/infra/logger.ts
110
+ import pino from "pino";
111
+ var isProduction = config.NODE_ENV === "production";
112
+ var pinoInstance = pino({
113
+ name: STATIC_CONFIG.SERVICE_NAME,
114
+ level: STATIC_CONFIG.LOG_LEVEL,
115
+ formatters: {
116
+ bindings: (bindings) => ({ name: bindings.name }),
117
+ level: (label) => ({ level: label })
118
+ },
119
+ serializers: {
120
+ err(err) {
121
+ if (!err) return err;
122
+ if (isProduction) {
123
+ return { type: err.name, message: err.message };
124
+ }
125
+ return {
126
+ type: err.name,
127
+ message: err.message,
128
+ stack: err.stack
129
+ };
130
+ }
131
+ },
132
+ transport: config.NODE_ENV !== "production" ? {
133
+ target: "pino-pretty",
134
+ options: {
135
+ colorize: true,
136
+ translateTime: "SYS:standard",
137
+ ignore: "pid,hostname",
138
+ errorProps: "*",
139
+ errorLikeObjectKeys: ["err", "error"]
140
+ }
141
+ } : void 0
142
+ });
143
+ var createLogMethod = (level) => {
144
+ return (...args) => {
145
+ const [first, ...rest] = args;
146
+ const log = pinoInstance[level].bind(pinoInstance);
147
+ if (typeof first === "object" && first !== null && !(first instanceof Error)) {
148
+ log(first, ...rest);
149
+ return;
150
+ }
151
+ if (rest.length > 0) {
152
+ const last = rest[rest.length - 1];
153
+ if (last instanceof Error) {
154
+ log({ err: last }, first, ...rest.slice(0, -1));
155
+ return;
156
+ }
157
+ }
158
+ if (first instanceof Error) {
159
+ log({ err: first }, first.message);
160
+ return;
161
+ }
162
+ log(first, ...rest);
163
+ };
164
+ };
165
+ var logger = {
166
+ trace: createLogMethod("trace"),
167
+ debug: createLogMethod("debug"),
168
+ info: createLogMethod("info"),
169
+ warn: createLogMethod("warn"),
170
+ error: createLogMethod("error"),
171
+ fatal: createLogMethod("fatal"),
172
+ get level() {
173
+ return pinoInstance.level;
174
+ },
175
+ set level(lvl) {
176
+ pinoInstance.level = lvl;
177
+ },
178
+ child: pinoInstance.child.bind(pinoInstance)
179
+ };
180
+
181
+ // src/infra/database/connection.ts
182
+ import Database from "better-sqlite3";
183
+ var DatabaseConnectionManager = class _DatabaseConnectionManager {
184
+ static instance;
185
+ db = null;
186
+ constructor() {
187
+ }
188
+ static getInstance() {
189
+ if (!_DatabaseConnectionManager.instance) {
190
+ _DatabaseConnectionManager.instance = new _DatabaseConnectionManager();
191
+ }
192
+ return _DatabaseConnectionManager.instance;
193
+ }
194
+ getConnection() {
195
+ if (!this.db) {
196
+ const dbPath = config.DB_PATH;
197
+ this.db = new Database(dbPath, {
198
+ verbose: config.NODE_ENV === "development" ? (msg) => logger.debug(String(msg)) : void 0
199
+ });
200
+ this.db.pragma("journal_mode = WAL");
201
+ this.db.pragma("foreign_keys = ON");
202
+ this.db.prepare("SELECT 1").get();
203
+ logger.info(`SQLite database connected: ${dbPath}`);
204
+ }
205
+ return this.db;
206
+ }
207
+ async close() {
208
+ if (this.db) {
209
+ this.db.close();
210
+ this.db = null;
211
+ logger.info("Database connection closed");
212
+ }
213
+ }
214
+ isConnected() {
215
+ return this.db !== null && this.db.open;
216
+ }
217
+ };
218
+ var databaseConnectionManager = DatabaseConnectionManager.getInstance();
219
+
220
+ // src/domain/file/constants.ts
221
+ var DEFAULT_LIST_LIMIT = 10;
222
+
223
+ // src/infra/database/query-builder.ts
224
+ function getDb() {
225
+ return databaseConnectionManager.getConnection();
226
+ }
227
+ var QueryBuilder = class {
228
+ static select(sql, params = []) {
229
+ const stmt = getDb().prepare(sql);
230
+ return stmt.all(params);
231
+ }
232
+ static selectOne(sql, params = []) {
233
+ const stmt = getDb().prepare(sql);
234
+ return stmt.get(params);
235
+ }
236
+ static execute(sql, params = []) {
237
+ const stmt = getDb().prepare(sql);
238
+ const result = stmt.run(params);
239
+ return {
240
+ changes: result.changes,
241
+ lastInsertRowid: result.lastInsertRowid
242
+ };
243
+ }
244
+ static transaction(callback) {
245
+ return getDb().transaction(callback)();
246
+ }
247
+ static paginate(sql, options = {}) {
248
+ let query = sql;
249
+ if (options.orderBy) {
250
+ query += ` ORDER BY ${options.orderBy} ${options.orderDirection || "ASC"}`;
251
+ }
252
+ const hasOffset = (options.offset ?? 0) > 0;
253
+ const limit = options.limit ?? (hasOffset ? DEFAULT_LIST_LIMIT : void 0);
254
+ if (limit) {
255
+ query += ` LIMIT ${limit}`;
256
+ }
257
+ if (hasOffset) {
258
+ query += ` OFFSET ${options.offset}`;
259
+ }
260
+ return query;
261
+ }
262
+ };
263
+
264
+ // src/infra/database/index.ts
265
+ function getDb2() {
266
+ return databaseConnectionManager.getConnection();
267
+ }
268
+ var database_default = getDb2;
269
+
270
+ // src/infra/database/models/files.model.ts
271
+ import { uuidv7 } from "uuidv7";
272
+ var FilesModel = class {
273
+ static TABLE = "files";
274
+ static parseFile(fileRaw) {
275
+ let metadata = {};
276
+ try {
277
+ if (fileRaw.metadata) {
278
+ metadata = typeof fileRaw.metadata === "string" ? JSON.parse(fileRaw.metadata) : fileRaw.metadata;
279
+ }
280
+ } catch (e) {
281
+ metadata = {};
282
+ }
283
+ return {
284
+ _id: fileRaw._id,
285
+ ddocId: fileRaw.ddocId,
286
+ title: fileRaw.title,
287
+ content: fileRaw.content,
288
+ localVersion: fileRaw.localVersion,
289
+ onchainVersion: fileRaw.onchainVersion,
290
+ syncStatus: fileRaw.syncStatus,
291
+ isDeleted: fileRaw.isDeleted,
292
+ onChainFileId: fileRaw.onChainFileId ?? null,
293
+ portalAddress: fileRaw.portalAddress,
294
+ metadata: metadata || {},
295
+ createdAt: fileRaw.createdAt,
296
+ updatedAt: fileRaw.updatedAt,
297
+ linkKey: fileRaw.linkKey,
298
+ linkKeyNonce: fileRaw.linkKeyNonce,
299
+ commentKey: fileRaw.commentKey,
300
+ link: fileRaw.link
301
+ };
302
+ }
303
+ static findAll(portalAddress, limit, skip) {
304
+ const whereClause = "isDeleted = 0 AND portalAddress = ?";
305
+ const params = [portalAddress];
306
+ const countSql = `
307
+ SELECT COUNT(*) as count
308
+ FROM ${this.TABLE}
309
+ WHERE ${whereClause}
310
+ `;
311
+ const totalResult = QueryBuilder.selectOne(countSql, params);
312
+ const total = totalResult?.count || 0;
313
+ const sql = `
314
+ SELECT *
315
+ FROM ${this.TABLE}
316
+ WHERE ${whereClause}
317
+ `;
318
+ const completeSql = QueryBuilder.paginate(sql, {
319
+ limit,
320
+ offset: skip,
321
+ orderBy: "createdAt",
322
+ orderDirection: "DESC"
323
+ });
324
+ const filesRaw = QueryBuilder.select(completeSql, params);
325
+ const files = filesRaw.map(this.parseFile);
326
+ const hasNext = skip !== void 0 && limit !== void 0 ? skip + limit < total : false;
327
+ return { files, total, hasNext };
328
+ }
329
+ static findById(_id, portalAddress) {
330
+ const sql = `
331
+ SELECT *
332
+ FROM ${this.TABLE}
333
+ WHERE _id = ? AND isDeleted = 0 AND portalAddress = ?
334
+ `;
335
+ const result = QueryBuilder.selectOne(sql, [_id, portalAddress]);
336
+ return result ? this.parseFile(result) : void 0;
337
+ }
338
+ static findByIdIncludingDeleted(_id) {
339
+ const sql = `
340
+ SELECT *
341
+ FROM ${this.TABLE}
342
+ WHERE _id = ?
343
+ `;
344
+ const result = QueryBuilder.selectOne(sql, [_id]);
345
+ return result ? this.parseFile(result) : void 0;
346
+ }
347
+ static findByIdExcludingDeleted(_id) {
348
+ const sql = `
349
+ SELECT *
350
+ FROM ${this.TABLE}
351
+ WHERE _id = ? AND isDeleted = 0
352
+ `;
353
+ const result = QueryBuilder.selectOne(sql, [_id]);
354
+ return result ? this.parseFile(result) : void 0;
355
+ }
356
+ static findByDDocId(ddocId, portalAddress) {
357
+ const sql = `
358
+ SELECT *
359
+ FROM ${this.TABLE}
360
+ WHERE ddocId = ? AND isDeleted = 0 AND portalAddress = ?
361
+ `;
362
+ const result = QueryBuilder.selectOne(sql, [ddocId, portalAddress]);
363
+ return result ? this.parseFile(result) : void 0;
364
+ }
365
+ static searchByTitle(searchTerm, portalAddress, limit, skip) {
366
+ const sql = `
367
+ SELECT *
368
+ FROM ${this.TABLE}
369
+ WHERE LOWER(title) LIKE LOWER(?) AND isDeleted = 0 AND portalAddress = ?
370
+ `;
371
+ const completeSql = QueryBuilder.paginate(sql, {
372
+ limit,
373
+ offset: skip,
374
+ orderBy: "createdAt",
375
+ orderDirection: "DESC"
376
+ });
377
+ const filesRaw = QueryBuilder.select(completeSql, [`%${searchTerm}%`, portalAddress]);
378
+ return filesRaw.map(this.parseFile);
379
+ }
380
+ static create(input) {
381
+ const _id = uuidv7();
382
+ const sql = `
383
+ INSERT INTO ${this.TABLE}
384
+ (_id, title, content, ddocId, portalAddress)
385
+ VALUES (?, ?, ?, ?, ?)
386
+ `;
387
+ QueryBuilder.execute(sql, [_id, input.title, input.content, input.ddocId, input.portalAddress]);
388
+ const created = this.findById(_id, input.portalAddress);
389
+ if (!created) {
390
+ throw new Error("Failed to create file");
391
+ }
392
+ return created;
393
+ }
394
+ static update(_id, payload, portalAddress) {
395
+ const now = (/* @__PURE__ */ new Date()).toISOString();
396
+ const keys = [];
397
+ const values = [];
398
+ for (const [k, v] of Object.entries(payload)) {
399
+ if (v !== void 0) {
400
+ if (k === "metadata" && typeof v === "object") {
401
+ keys.push(`${k} = ?`);
402
+ values.push(JSON.stringify(v));
403
+ } else {
404
+ keys.push(`${k} = ?`);
405
+ values.push(v);
406
+ }
407
+ }
408
+ }
409
+ keys.push("updatedAt = ?");
410
+ values.push(now, _id, portalAddress);
411
+ const updateChain = keys.join(", ");
412
+ const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE _id = ? AND portalAddress = ?`;
413
+ QueryBuilder.execute(sql, values);
414
+ const updated = this.findById(_id, portalAddress);
415
+ if (!updated) {
416
+ throw new Error("Failed to update file");
417
+ }
418
+ return updated;
419
+ }
420
+ static softDelete(_id) {
421
+ const now = (/* @__PURE__ */ new Date()).toISOString();
422
+ const sql = `
423
+ UPDATE ${this.TABLE}
424
+ SET isDeleted = 1, syncStatus = 'pending', updatedAt = ?
425
+ WHERE _id = ?
426
+ `;
427
+ QueryBuilder.execute(sql, [now, _id]);
428
+ const deleted = this.findByIdIncludingDeleted(_id);
429
+ if (!deleted) {
430
+ throw new Error("Failed to delete file");
431
+ }
432
+ return deleted;
433
+ }
434
+ };
435
+
436
+ // src/infra/database/models/portals.model.ts
437
+ import { uuidv7 as uuidv72 } from "uuidv7";
438
+ var PortalsModel = class {
439
+ static TABLE = "portals";
440
+ static findByPortalAddress(portalAddress) {
441
+ const sql = `SELECT _id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt FROM ${this.TABLE} WHERE portalAddress = ?`;
442
+ return QueryBuilder.selectOne(sql, [portalAddress]);
443
+ }
444
+ static create(input) {
445
+ const _id = uuidv72();
446
+ const now = (/* @__PURE__ */ new Date()).toISOString();
447
+ const sql = `INSERT INTO ${this.TABLE} (_id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)`;
448
+ QueryBuilder.execute(sql, [_id, input.portalAddress, input.portalSeed, input.ownerAddress, now, now]);
449
+ const created = this.findByPortalAddress(input.portalAddress);
450
+ if (!created) {
451
+ throw new Error("Failed to create portal");
452
+ }
453
+ return created;
454
+ }
455
+ static update(portalAddress, input) {
456
+ const now = (/* @__PURE__ */ new Date()).toISOString();
457
+ const keys = [];
458
+ const values = [];
459
+ for (const [k, v] of Object.entries(input)) {
460
+ if (v !== void 0) {
461
+ keys.push(`${k} = ?`);
462
+ values.push(v);
463
+ }
464
+ }
465
+ keys.push("updatedAt = ?");
466
+ values.push(now);
467
+ const updateChain = keys.join(", ");
468
+ const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE portalAddress = ?`;
469
+ values.push(portalAddress);
470
+ QueryBuilder.execute(sql, values);
471
+ const updated = this.findByPortalAddress(portalAddress);
472
+ if (!updated) {
473
+ throw new Error("Failed to update portal");
474
+ }
475
+ return updated;
476
+ }
477
+ static upsert(input) {
478
+ const existing = this.findByPortalAddress(input.portalAddress);
479
+ if (existing) {
480
+ return this.update(input.portalAddress, {
481
+ portalSeed: input.portalSeed,
482
+ ownerAddress: input.ownerAddress
483
+ });
484
+ }
485
+ return this.create(input);
486
+ }
487
+ };
488
+
489
+ // src/infra/database/models/apikeys.model.ts
490
+ import { uuidv7 as uuidv73 } from "uuidv7";
491
+
492
+ // src/infra/database/models/events.model.ts
493
+ import { uuidv7 as uuidv74 } from "uuidv7";
494
+
495
+ // src/infra/worker/workerSignal.ts
496
+ import { EventEmitter } from "events";
497
+ var WorkerSignal = class extends EventEmitter {
498
+ };
499
+ var workerSignal = new WorkerSignal();
500
+ workerSignal.setMaxListeners(20);
501
+ function notifyNewEvent() {
502
+ workerSignal.emit("newEvent");
503
+ }
504
+ function onNewEvent(callback) {
505
+ workerSignal.on("newEvent", callback);
506
+ return () => workerSignal.off("newEvent", callback);
507
+ }
508
+
509
+ // src/infra/database/models/events.model.ts
510
+ var RETRY_DELAYS_MS = [5e3, 3e4, 12e4];
511
+ var EventsModel = class {
512
+ static TABLE = "events";
513
+ static create(input) {
514
+ const _id = uuidv74();
515
+ const timestamp = Date.now();
516
+ const status = "pending";
517
+ const sql = `
518
+ INSERT INTO ${this.TABLE}
519
+ (_id, type, timestamp, fileId, portalAddress, status, retryCount, lastError, lockedAt, nextRetryAt)
520
+ VALUES (?, ?, ?, ?, ?, ?, 0, NULL, NULL, NULL)
521
+ `;
522
+ QueryBuilder.execute(sql, [_id, input.type, timestamp, input.fileId, input.portalAddress, status]);
523
+ notifyNewEvent();
524
+ return {
525
+ _id,
526
+ type: input.type,
527
+ timestamp,
528
+ fileId: input.fileId,
529
+ portalAddress: input.portalAddress,
530
+ status,
531
+ retryCount: 0,
532
+ lastError: null,
533
+ lockedAt: null,
534
+ nextRetryAt: null
535
+ };
536
+ }
537
+ static findById(_id) {
538
+ const sql = `SELECT * FROM ${this.TABLE} WHERE _id = ?`;
539
+ const row = QueryBuilder.selectOne(sql, [_id]);
540
+ return row ? this.parseEvent(row) : void 0;
541
+ }
542
+ static findNextPending() {
543
+ const sql = `
544
+ SELECT * FROM ${this.TABLE}
545
+ WHERE status = 'pending'
546
+ ORDER BY timestamp ASC
547
+ LIMIT 1
548
+ `;
549
+ const row = QueryBuilder.selectOne(sql, []);
550
+ return row ? this.parseEvent(row) : void 0;
551
+ }
552
+ static findNextEligible(lockedFileIds) {
553
+ const now = Date.now();
554
+ const exclusionClause = lockedFileIds.length > 0 ? `AND e1.fileId NOT IN (${lockedFileIds.map(() => "?").join(", ")})` : "";
555
+ const sql = `
556
+ SELECT e1.* FROM ${this.TABLE} e1
557
+ WHERE e1.status = 'pending'
558
+ AND (e1.nextRetryAt IS NULL OR e1.nextRetryAt <= ?)
559
+ ${exclusionClause}
560
+ AND NOT EXISTS (
561
+ SELECT 1 FROM ${this.TABLE} e2
562
+ WHERE e2.fileId = e1.fileId
563
+ AND e2.status = 'pending'
564
+ AND e2.timestamp < e1.timestamp
565
+ )
566
+ ORDER BY e1.timestamp ASC
567
+ LIMIT 1
568
+ `;
569
+ const params = [now, ...lockedFileIds];
570
+ const row = QueryBuilder.selectOne(sql, params);
571
+ return row ? this.parseEvent(row) : void 0;
572
+ }
573
+ static markProcessing(_id) {
574
+ const sql = `
575
+ UPDATE ${this.TABLE}
576
+ SET status = 'processing',
577
+ lockedAt = ?
578
+ WHERE _id = ?
579
+ `;
580
+ QueryBuilder.execute(sql, [Date.now(), _id]);
581
+ }
582
+ static markProcessed(_id) {
583
+ const sql = `
584
+ UPDATE ${this.TABLE}
585
+ SET status = 'processed',
586
+ lockedAt = NULL
587
+ WHERE _id = ?
588
+ `;
589
+ QueryBuilder.execute(sql, [_id]);
590
+ }
591
+ static scheduleRetry(_id, errorMsg) {
592
+ const event = this.findById(_id);
593
+ if (!event) return;
594
+ const delay = RETRY_DELAYS_MS[Math.min(event.retryCount, RETRY_DELAYS_MS.length - 1)];
595
+ const nextRetryAt = Date.now() + delay;
596
+ const sql = `
597
+ UPDATE ${this.TABLE}
598
+ SET status = 'pending',
599
+ retryCount = retryCount + 1,
600
+ lastError = ?,
601
+ nextRetryAt = ?,
602
+ lockedAt = NULL
603
+ WHERE _id = ?
604
+ `;
605
+ QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);
606
+ }
607
+ static scheduleRetryAfter(_id, errorMsg, retryAfterMs) {
608
+ const nextRetryAt = Date.now() + retryAfterMs;
609
+ const sql = `
610
+ UPDATE ${this.TABLE}
611
+ SET status = 'pending',
612
+ lastError = ?,
613
+ nextRetryAt = ?,
614
+ lockedAt = NULL
615
+ WHERE _id = ?
616
+ `;
617
+ QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);
618
+ }
619
+ static markFailed(_id, errorMsg) {
620
+ const sql = `
621
+ UPDATE ${this.TABLE}
622
+ SET status = 'failed',
623
+ lastError = ?,
624
+ lockedAt = NULL
625
+ WHERE _id = ?
626
+ `;
627
+ QueryBuilder.execute(sql, [errorMsg, _id]);
628
+ }
629
+ static listFailed(portalAddress) {
630
+ const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
631
+ const sql = `
632
+ SELECT * FROM ${this.TABLE}
633
+ WHERE status = 'failed'
634
+ ${portalClause}
635
+ ORDER BY timestamp ASC
636
+ `;
637
+ const params = portalAddress != null ? [portalAddress] : [];
638
+ const rows = QueryBuilder.select(sql, params);
639
+ return rows.map((row) => this.parseEvent(row));
640
+ }
641
+ static resetFailedToPending(_id, portalAddress) {
642
+ const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
643
+ const sql = `
644
+ UPDATE ${this.TABLE}
645
+ SET status = 'pending',
646
+ retryCount = 0,
647
+ lastError = NULL,
648
+ nextRetryAt = NULL,
649
+ lockedAt = NULL
650
+ WHERE _id = ?
651
+ AND status = 'failed'
652
+ ${portalClause}
653
+ `;
654
+ const params = portalAddress != null ? [_id, portalAddress] : [_id];
655
+ const result = QueryBuilder.execute(sql, params);
656
+ if (result.changes > 0) {
657
+ notifyNewEvent();
658
+ }
659
+ return result.changes > 0;
660
+ }
661
+ static resetAllFailedToPending(portalAddress) {
662
+ const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
663
+ const sql = `
664
+ UPDATE ${this.TABLE}
665
+ SET status = 'pending',
666
+ retryCount = 0,
667
+ lastError = NULL,
668
+ nextRetryAt = NULL,
669
+ lockedAt = NULL
670
+ WHERE status = 'failed'
671
+ ${portalClause}
672
+ `;
673
+ const params = portalAddress != null ? [portalAddress] : [];
674
+ const result = QueryBuilder.execute(sql, params);
675
+ if (result.changes > 0) {
676
+ notifyNewEvent();
677
+ }
678
+ return result.changes;
679
+ }
680
+ static resetStaleEvents(staleThreshold) {
681
+ const sql = `
682
+ UPDATE ${this.TABLE}
683
+ SET status = 'pending',
684
+ lockedAt = NULL,
685
+ userOpHash = NULL,
686
+ pendingPayload = NULL
687
+ WHERE status = 'processing'
688
+ AND lockedAt IS NOT NULL
689
+ AND lockedAt < ?
690
+ `;
691
+ const result = QueryBuilder.execute(sql, [staleThreshold]);
692
+ return result.changes;
693
+ }
694
+ static setEventPendingOp(_id, userOpHash, payload) {
695
+ const sql = `UPDATE ${this.TABLE} SET userOpHash = ?, pendingPayload = ? WHERE _id = ?`;
696
+ QueryBuilder.execute(sql, [userOpHash, JSON.stringify(payload), _id]);
697
+ }
698
+ static clearEventPendingOp(_id) {
699
+ const sql = `UPDATE ${this.TABLE} SET userOpHash = NULL, pendingPayload = NULL WHERE _id = ?`;
700
+ QueryBuilder.execute(sql, [_id]);
701
+ }
702
+ static parseEvent(row) {
703
+ return {
704
+ _id: row._id,
705
+ type: row.type,
706
+ timestamp: row.timestamp,
707
+ fileId: row.fileId,
708
+ portalAddress: row.portalAddress ?? "",
709
+ status: row.status,
710
+ retryCount: row.retryCount,
711
+ lastError: row.lastError,
712
+ lockedAt: row.lockedAt,
713
+ nextRetryAt: row.nextRetryAt,
714
+ userOpHash: row.userOpHash ?? null,
715
+ pendingPayload: row.pendingPayload ?? null
716
+ };
717
+ }
718
+ };
719
+
720
+ // src/sdk/key-store.ts
721
+ import { eciesDecrypt, eciesEncrypt, generateECKeyPair } from "@fileverse/crypto/ecies";
722
+ var KeyStore = class {
723
+ constructor(seed, address, authTokenProvider) {
724
+ this.authTokenProvider = authTokenProvider;
725
+ this.portalKeySeed = seed;
726
+ this.portalAddress = address;
727
+ this.authTokenProvider = authTokenProvider;
728
+ }
729
+ portalKeySeed;
730
+ portalAddress;
731
+ getPortalAddress() {
732
+ if (!this.portalAddress) {
733
+ throw new Error("Portal address is not set");
734
+ }
735
+ return this.portalAddress;
736
+ }
737
+ getAppEncryptionKey() {
738
+ if (!this.portalKeySeed) {
739
+ throw new Error("Portal key seed is not set");
740
+ }
741
+ const keyPair = generateECKeyPair(this.portalKeySeed);
742
+ return keyPair.publicKey;
743
+ }
744
+ getAppDecryptionKey() {
745
+ if (!this.portalKeySeed) {
746
+ throw new Error("Portal key seed is not set");
747
+ }
748
+ const keyPair = generateECKeyPair(this.portalKeySeed);
749
+ return keyPair.privateKey;
750
+ }
751
+ encryptData(data) {
752
+ return eciesEncrypt(this.getAppEncryptionKey(), data);
753
+ }
754
+ decryptData(data) {
755
+ return eciesDecrypt(this.getAppDecryptionKey(), data);
756
+ }
757
+ getAuthToken(audienceDid) {
758
+ return this.authTokenProvider.getAuthToken(audienceDid);
759
+ }
760
+ };
761
+
762
+ // src/sdk/auth-token-provider.ts
763
+ import * as ucans from "@ucans/ucans";
764
+ var AuthTokenProvider = class {
765
+ DEFAULT_OPTIONS = {
766
+ namespace: "file",
767
+ segment: "CREATE",
768
+ scheme: "storage"
769
+ };
770
+ keyPair;
771
+ portalAddress;
772
+ constructor(keyPair, portalAddress) {
773
+ this.keyPair = keyPair;
774
+ this.portalAddress = portalAddress;
775
+ }
776
+ async getAuthToken(audienceDid, options = this.DEFAULT_OPTIONS) {
777
+ const ucan = await ucans.build({
778
+ audience: audienceDid,
779
+ issuer: this.keyPair,
780
+ lifetimeInSeconds: 7 * 86400,
781
+ capabilities: [
782
+ {
783
+ with: {
784
+ scheme: options.scheme,
785
+ hierPart: this.portalAddress.toLocaleLowerCase()
786
+ },
787
+ can: { namespace: options.namespace, segments: [options.segment] }
788
+ }
789
+ ]
790
+ });
791
+ return ucans.encode(ucan);
792
+ }
793
+ };
794
+
795
+ // src/domain/portal/publish.ts
796
+ import { fromUint8Array as fromUint8Array3, toUint8Array as toUint8Array3 } from "js-base64";
797
+ import { stringToBytes } from "viem";
798
+ import { deriveHKDFKey } from "@fileverse/crypto/kdf";
799
+ import { generateKeyPairFromSeed } from "@stablelib/ed25519";
800
+ import * as ucans2 from "@ucans/ucans";
801
+
802
+ // src/sdk/smart-agent.ts
803
+ import { toHex as toHex2 } from "viem";
804
+ import { privateKeyToAccount } from "viem/accounts";
805
+
806
+ // src/sdk/pimlico-utils.ts
807
+ import { createPublicClient, http, hexToBigInt, toHex, toBytes } from "viem";
808
+ import { createPimlicoClient } from "permissionless/clients/pimlico";
809
+ import { createSmartAccountClient } from "permissionless";
810
+ import { toSafeSmartAccount } from "permissionless/accounts";
811
+ import { entryPoint07Address } from "viem/account-abstraction";
812
+
813
+ // src/constants/chains.ts
814
+ import { sepolia, gnosis } from "viem/chains";
815
+
816
+ // src/constants/events.ts
817
+ var ADDED_FILE_EVENT = [
818
+ {
819
+ anonymous: false,
820
+ inputs: [
821
+ {
822
+ indexed: true,
823
+ internalType: "uint256",
824
+ name: "fileId",
825
+ type: "uint256"
826
+ },
827
+ {
828
+ indexed: false,
829
+ internalType: "string",
830
+ name: "appFileId",
831
+ type: "string"
832
+ },
833
+ {
834
+ indexed: false,
835
+ internalType: "enum FileverseApp.FileType",
836
+ name: "fileType",
837
+ type: "uint8"
838
+ },
839
+ {
840
+ indexed: false,
841
+ internalType: "string",
842
+ name: "metadataIPFSHash",
843
+ type: "string"
844
+ },
845
+ {
846
+ indexed: false,
847
+ internalType: "string",
848
+ name: "contentIPFSHash",
849
+ type: "string"
850
+ },
851
+ {
852
+ indexed: false,
853
+ internalType: "string",
854
+ name: "gateIPFSHash",
855
+ type: "string"
856
+ },
857
+ {
858
+ indexed: false,
859
+ internalType: "uint256",
860
+ name: "version",
861
+ type: "uint256"
862
+ },
863
+ {
864
+ indexed: true,
865
+ internalType: "address",
866
+ name: "by",
867
+ type: "address"
868
+ }
869
+ ],
870
+ name: "AddedFile",
871
+ type: "event"
872
+ }
873
+ ];
874
+ var EDITED_FILE_EVENT = [
875
+ {
876
+ anonymous: false,
877
+ inputs: [
878
+ {
879
+ indexed: true,
880
+ internalType: "uint256",
881
+ name: "fileId",
882
+ type: "uint256"
883
+ },
884
+ {
885
+ indexed: false,
886
+ internalType: "string",
887
+ name: "appFileId",
888
+ type: "string"
889
+ },
890
+ {
891
+ indexed: false,
892
+ internalType: "enum FileverseApp.FileType",
893
+ name: "fileType",
894
+ type: "uint8"
895
+ },
896
+ {
897
+ indexed: false,
898
+ internalType: "string",
899
+ name: "metadataIPFSHash",
900
+ type: "string"
901
+ },
902
+ {
903
+ indexed: false,
904
+ internalType: "string",
905
+ name: "contentIPFSHash",
906
+ type: "string"
907
+ },
908
+ {
909
+ indexed: false,
910
+ internalType: "string",
911
+ name: "gateIPFSHash",
912
+ type: "string"
913
+ },
914
+ {
915
+ indexed: false,
916
+ internalType: "uint256",
917
+ name: "version",
918
+ type: "uint256"
919
+ },
920
+ {
921
+ indexed: true,
922
+ internalType: "address",
923
+ name: "by",
924
+ type: "address"
925
+ }
926
+ ],
927
+ name: "EditedFile",
928
+ type: "event"
929
+ }
930
+ ];
931
+ var DELETED_FILE_EVENT = [
932
+ {
933
+ anonymous: false,
934
+ inputs: [
935
+ {
936
+ indexed: true,
937
+ internalType: "uint256",
938
+ name: "fileId",
939
+ type: "uint256"
940
+ },
941
+ {
942
+ indexed: false,
943
+ internalType: "string",
944
+ name: "appFileId",
945
+ type: "string"
946
+ },
947
+ {
948
+ indexed: true,
949
+ internalType: "address",
950
+ name: "by",
951
+ type: "address"
952
+ }
953
+ ],
954
+ name: "DeletedFile",
955
+ type: "event"
956
+ }
957
+ ];
958
+
959
+ // src/constants/methods.ts
960
+ var ADD_FILE_METHOD = [
961
+ {
962
+ inputs: [
963
+ {
964
+ internalType: "string",
965
+ name: "_appFileId",
966
+ type: "string"
967
+ },
968
+ {
969
+ internalType: "enum FileverseApp.FileType",
970
+ name: "fileType",
971
+ type: "uint8"
972
+ },
973
+ {
974
+ internalType: "string",
975
+ name: "_metadataIPFSHash",
976
+ type: "string"
977
+ },
978
+ {
979
+ internalType: "string",
980
+ name: "_contentIPFSHash",
981
+ type: "string"
982
+ },
983
+ {
984
+ internalType: "string",
985
+ name: "_gateIPFSHash",
986
+ type: "string"
987
+ },
988
+ {
989
+ internalType: "uint256",
990
+ name: "version",
991
+ type: "uint256"
992
+ }
993
+ ],
994
+ name: "addFile",
995
+ outputs: [],
996
+ stateMutability: "nonpayable",
997
+ type: "function"
998
+ }
999
+ ];
1000
+ var EDIT_FILE_METHOD = [
1001
+ {
1002
+ inputs: [
1003
+ {
1004
+ internalType: "uint256",
1005
+ name: "fileId",
1006
+ type: "uint256"
1007
+ },
1008
+ {
1009
+ internalType: "string",
1010
+ name: "_appFileId",
1011
+ type: "string"
1012
+ },
1013
+ {
1014
+ internalType: "string",
1015
+ name: "_metadataIPFSHash",
1016
+ type: "string"
1017
+ },
1018
+ {
1019
+ internalType: "string",
1020
+ name: "_contentIPFSHash",
1021
+ type: "string"
1022
+ },
1023
+ {
1024
+ internalType: "string",
1025
+ name: "_gateIPFSHash",
1026
+ type: "string"
1027
+ },
1028
+ {
1029
+ internalType: "enum FileverseApp.FileType",
1030
+ name: "fileType",
1031
+ type: "uint8"
1032
+ },
1033
+ {
1034
+ internalType: "uint256",
1035
+ name: "version",
1036
+ type: "uint256"
1037
+ }
1038
+ ],
1039
+ name: "editFile",
1040
+ outputs: [],
1041
+ stateMutability: "nonpayable",
1042
+ type: "function"
1043
+ }
1044
+ ];
1045
+ var DELETED_FILE_ABI = [
1046
+ {
1047
+ inputs: [
1048
+ {
1049
+ internalType: "uint256",
1050
+ name: "fileId",
1051
+ type: "uint256"
1052
+ }
1053
+ ],
1054
+ name: "deleteFile",
1055
+ outputs: [],
1056
+ stateMutability: "nonpayable",
1057
+ type: "function"
1058
+ }
1059
+ ];
1060
+
1061
+ // src/constants/index.ts
1062
+ var NETWORK_NAME = STATIC_CONFIG.NETWORK_NAME;
1063
+ var UPLOAD_SERVER_URL = STATIC_CONFIG.API_URL;
1064
+ var getRpcUrl = () => getRuntimeConfig().RPC_URL;
1065
+ var getPimlicoUrl = () => `${STATIC_CONFIG.PIMLICO_PROXY_URL}api/${NETWORK_NAME}/rpc`;
1066
+ var CHAIN_MAP = {
1067
+ gnosis,
1068
+ sepolia
1069
+ };
1070
+ var CHAIN = CHAIN_MAP[NETWORK_NAME];
1071
+
1072
+ // src/sdk/pimlico-utils.ts
1073
+ import { generatePrivateKey } from "viem/accounts";
1074
+ var getPublicClient = () => createPublicClient({
1075
+ transport: http(getRpcUrl(), {
1076
+ retryCount: 0
1077
+ }),
1078
+ chain: CHAIN
1079
+ });
1080
+ var getPimlicoClient = (authToken, portalAddress, invokerAddress) => createPimlicoClient({
1081
+ transport: http(getPimlicoUrl(), {
1082
+ retryCount: 0,
1083
+ fetchOptions: {
1084
+ headers: {
1085
+ Authorization: `Bearer ${authToken}`,
1086
+ contract: portalAddress,
1087
+ invoker: invokerAddress
1088
+ }
1089
+ }
1090
+ }),
1091
+ entryPoint: {
1092
+ address: entryPoint07Address,
1093
+ version: "0.7"
1094
+ }
1095
+ });
1096
+ var signerToSmartAccount = async (signer) => await toSafeSmartAccount({
1097
+ client: getPublicClient(),
1098
+ owners: [signer],
1099
+ entryPoint: {
1100
+ address: entryPoint07Address,
1101
+ version: "0.7"
1102
+ },
1103
+ version: "1.4.1"
1104
+ });
1105
+ var getSmartAccountClient = async (signer, authToken, portalAddress) => {
1106
+ const smartAccount = await signerToSmartAccount(signer);
1107
+ const pimlicoClient = getPimlicoClient(authToken, portalAddress, smartAccount.address);
1108
+ return createSmartAccountClient({
1109
+ account: smartAccount,
1110
+ chain: CHAIN,
1111
+ paymaster: pimlicoClient,
1112
+ bundlerTransport: http(getPimlicoUrl(), {
1113
+ fetchOptions: {
1114
+ headers: {
1115
+ Authorization: `Bearer ${authToken}`,
1116
+ contract: portalAddress,
1117
+ invoker: smartAccount.address
1118
+ }
1119
+ },
1120
+ retryCount: 0
1121
+ }),
1122
+ userOperation: {
1123
+ estimateFeesPerGas: async () => (await pimlicoClient.getUserOperationGasPrice()).fast
1124
+ }
1125
+ });
1126
+ };
1127
+ var getNonce = () => hexToBigInt(
1128
+ toHex(toBytes(generatePrivateKey()).slice(0, 24), {
1129
+ size: 32
1130
+ })
1131
+ );
1132
+ var waitForUserOpReceipt = async (hash, authToken, portalAddress, invokerAddress, timeout = 12e4) => {
1133
+ const pimlicoClient = getPimlicoClient(authToken, portalAddress, invokerAddress);
1134
+ return pimlicoClient.waitForUserOperationReceipt({
1135
+ hash,
1136
+ timeout
1137
+ });
1138
+ };
1139
+
1140
+ // src/sdk/smart-agent.ts
1141
+ var AgentClient = class {
1142
+ constructor(authTokenProvider) {
1143
+ this.authTokenProvider = authTokenProvider;
1144
+ this.authTokenProvider = authTokenProvider;
1145
+ }
1146
+ smartAccountAgent = null;
1147
+ MAX_CALL_GAS_LIMIT = 5e5;
1148
+ authOptions = { namespace: "proxy", segment: "ACCESS", scheme: "pimlico" };
1149
+ async initializeAgentClient(keyMaterial) {
1150
+ const agentAccount = privateKeyToAccount(toHex2(keyMaterial));
1151
+ const authToken = await this.authTokenProvider.getAuthToken(STATIC_CONFIG.PROXY_SERVER_DID, this.authOptions);
1152
+ const smartAccountClient = await getSmartAccountClient(
1153
+ agentAccount,
1154
+ authToken,
1155
+ this.authTokenProvider.portalAddress
1156
+ );
1157
+ this.smartAccountAgent = smartAccountClient;
1158
+ }
1159
+ getSmartAccountAgent() {
1160
+ if (!this.smartAccountAgent) throw new Error("Agent client not initialized");
1161
+ return this.smartAccountAgent;
1162
+ }
1163
+ getAgentAddress() {
1164
+ const smartAccountAgent = this.getSmartAccountAgent();
1165
+ if (!smartAccountAgent.account) throw new Error("Agent account not found");
1166
+ return smartAccountAgent.account.address;
1167
+ }
1168
+ getAgentAccount() {
1169
+ const smartAccountAgent = this.getSmartAccountAgent();
1170
+ if (!smartAccountAgent.account) throw new Error("Agent account not found");
1171
+ return smartAccountAgent.account;
1172
+ }
1173
+ destroyAgentClient() {
1174
+ this.smartAccountAgent = null;
1175
+ }
1176
+ async getCallData(request) {
1177
+ const agentAccount = this.getAgentAccount();
1178
+ if (Array.isArray(request)) {
1179
+ if (request.length === 0 || request.length > 10) throw new Error("Request length must be between 1 and 10");
1180
+ const encodedCallData = request.map((req) => ({
1181
+ to: req.contractAddress,
1182
+ data: req.data,
1183
+ value: BigInt(0)
1184
+ }));
1185
+ return await agentAccount.encodeCalls(encodedCallData);
1186
+ }
1187
+ return await agentAccount.encodeCalls([
1188
+ {
1189
+ to: request.contractAddress,
1190
+ data: request.data,
1191
+ value: BigInt(0)
1192
+ }
1193
+ ]);
1194
+ }
1195
+ async sendUserOperation(request, customGasLimit) {
1196
+ try {
1197
+ const smartAccountAgent = this.getSmartAccountAgent();
1198
+ const callData = await this.getCallData(request);
1199
+ return await smartAccountAgent.sendUserOperation({
1200
+ callData,
1201
+ callGasLimit: BigInt(customGasLimit || this.MAX_CALL_GAS_LIMIT),
1202
+ nonce: getNonce()
1203
+ });
1204
+ } catch (error) {
1205
+ throw error;
1206
+ }
1207
+ }
1208
+ async executeUserOperationRequest(request, timeout, customGasLimit) {
1209
+ const userOpHash = await this.sendUserOperation(request, customGasLimit);
1210
+ const { authToken, portalAddress, invokerAddress } = await this.getAuthParams();
1211
+ const receipt = await waitForUserOpReceipt(userOpHash, authToken, portalAddress, invokerAddress, timeout);
1212
+ if (!receipt.success) throw new Error(`Failed to execute user operation: ${receipt.reason}`);
1213
+ return receipt;
1214
+ }
1215
+ async getAuthParams() {
1216
+ const authToken = await this.authTokenProvider.getAuthToken(STATIC_CONFIG.PROXY_SERVER_DID, this.authOptions);
1217
+ return {
1218
+ authToken,
1219
+ portalAddress: this.authTokenProvider.portalAddress,
1220
+ invokerAddress: this.getAgentAddress()
1221
+ };
1222
+ }
1223
+ };
1224
+
1225
+ // src/sdk/file-manager.ts
1226
+ import { fromUint8Array as fromUint8Array2, toUint8Array as toUint8Array2 } from "js-base64";
1227
+
1228
+ // src/sdk/file-utils.ts
1229
+ import { getArgon2idHash } from "@fileverse/crypto/argon";
1230
+ import { bytesToBase64, generateRandomBytes as generateRandomBytes2 } from "@fileverse/crypto/utils";
1231
+ import { derivePBKDF2Key, encryptAesCBC } from "@fileverse/crypto/kdf";
1232
+ import { secretBoxEncrypt } from "@fileverse/crypto/nacl";
1233
+ import hkdf from "futoin-hkdf";
1234
+ import tweetnacl from "tweetnacl";
1235
+ import { fromUint8Array, toUint8Array } from "js-base64";
1236
+
1237
+ // node_modules/@noble/ciphers/utils.js
1238
+ function isBytes(a) {
1239
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
1240
+ }
1241
+ function abool(b) {
1242
+ if (typeof b !== "boolean")
1243
+ throw new Error(`boolean expected, not ${b}`);
1244
+ }
1245
+ function abytes(value, length, title = "") {
1246
+ const bytes = isBytes(value);
1247
+ const len = value?.length;
1248
+ const needsLen = length !== void 0;
1249
+ if (!bytes || needsLen && len !== length) {
1250
+ const prefix = title && `"${title}" `;
1251
+ const ofLen = needsLen ? ` of length ${length}` : "";
1252
+ const got = bytes ? `length=${len}` : `type=${typeof value}`;
1253
+ throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got);
1254
+ }
1255
+ return value;
1256
+ }
1257
+ function aexists(instance, checkFinished = true) {
1258
+ if (instance.destroyed)
1259
+ throw new Error("Hash instance has been destroyed");
1260
+ if (checkFinished && instance.finished)
1261
+ throw new Error("Hash#digest() has already been called");
1262
+ }
1263
+ function aoutput(out, instance) {
1264
+ abytes(out, void 0, "output");
1265
+ const min = instance.outputLen;
1266
+ if (out.length < min) {
1267
+ throw new Error("digestInto() expects output buffer of length at least " + min);
1268
+ }
1269
+ }
1270
+ function u8(arr) {
1271
+ return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
1272
+ }
1273
+ function u32(arr) {
1274
+ return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
1275
+ }
1276
+ function clean(...arrays) {
1277
+ for (let i = 0; i < arrays.length; i++) {
1278
+ arrays[i].fill(0);
1279
+ }
1280
+ }
1281
+ function createView(arr) {
1282
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
1283
+ }
1284
+ var isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
1285
+ function equalBytes(a, b) {
1286
+ if (a.length !== b.length)
1287
+ return false;
1288
+ let diff = 0;
1289
+ for (let i = 0; i < a.length; i++)
1290
+ diff |= a[i] ^ b[i];
1291
+ return diff === 0;
1292
+ }
1293
+ var wrapCipher = /* @__NO_SIDE_EFFECTS__ */ (params, constructor) => {
1294
+ function wrappedCipher(key, ...args) {
1295
+ abytes(key, void 0, "key");
1296
+ if (!isLE)
1297
+ throw new Error("Non little-endian hardware is not yet supported");
1298
+ if (params.nonceLength !== void 0) {
1299
+ const nonce = args[0];
1300
+ abytes(nonce, params.varSizeNonce ? void 0 : params.nonceLength, "nonce");
1301
+ }
1302
+ const tagl = params.tagLength;
1303
+ if (tagl && args[1] !== void 0)
1304
+ abytes(args[1], void 0, "AAD");
1305
+ const cipher = constructor(key, ...args);
1306
+ const checkOutput = (fnLength, output) => {
1307
+ if (output !== void 0) {
1308
+ if (fnLength !== 2)
1309
+ throw new Error("cipher output not supported");
1310
+ abytes(output, void 0, "output");
1311
+ }
1312
+ };
1313
+ let called = false;
1314
+ const wrCipher = {
1315
+ encrypt(data, output) {
1316
+ if (called)
1317
+ throw new Error("cannot encrypt() twice with same key + nonce");
1318
+ called = true;
1319
+ abytes(data);
1320
+ checkOutput(cipher.encrypt.length, output);
1321
+ return cipher.encrypt(data, output);
1322
+ },
1323
+ decrypt(data, output) {
1324
+ abytes(data);
1325
+ if (tagl && data.length < tagl)
1326
+ throw new Error('"ciphertext" expected length bigger than tagLength=' + tagl);
1327
+ checkOutput(cipher.decrypt.length, output);
1328
+ return cipher.decrypt(data, output);
1329
+ }
1330
+ };
1331
+ return wrCipher;
1332
+ }
1333
+ Object.assign(wrappedCipher, params);
1334
+ return wrappedCipher;
1335
+ };
1336
+ function getOutput(expectedLength, out, onlyAligned = true) {
1337
+ if (out === void 0)
1338
+ return new Uint8Array(expectedLength);
1339
+ if (out.length !== expectedLength)
1340
+ throw new Error('"output" expected Uint8Array of length ' + expectedLength + ", got: " + out.length);
1341
+ if (onlyAligned && !isAligned32(out))
1342
+ throw new Error("invalid output, must be aligned");
1343
+ return out;
1344
+ }
1345
+ function u64Lengths(dataLength, aadLength, isLE2) {
1346
+ abool(isLE2);
1347
+ const num = new Uint8Array(16);
1348
+ const view = createView(num);
1349
+ view.setBigUint64(0, BigInt(aadLength), isLE2);
1350
+ view.setBigUint64(8, BigInt(dataLength), isLE2);
1351
+ return num;
1352
+ }
1353
+ function isAligned32(bytes) {
1354
+ return bytes.byteOffset % 4 === 0;
1355
+ }
1356
+ function copyBytes(bytes) {
1357
+ return Uint8Array.from(bytes);
1358
+ }
1359
+
1360
+ // node_modules/@noble/ciphers/_polyval.js
1361
+ var BLOCK_SIZE = 16;
1362
+ var ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
1363
+ var ZEROS32 = u32(ZEROS16);
1364
+ var POLY = 225;
1365
+ var mul2 = (s0, s1, s2, s3) => {
1366
+ const hiBit = s3 & 1;
1367
+ return {
1368
+ s3: s2 << 31 | s3 >>> 1,
1369
+ s2: s1 << 31 | s2 >>> 1,
1370
+ s1: s0 << 31 | s1 >>> 1,
1371
+ s0: s0 >>> 1 ^ POLY << 24 & -(hiBit & 1)
1372
+ // reduce % poly
1373
+ };
1374
+ };
1375
+ var swapLE = (n) => (n >>> 0 & 255) << 24 | (n >>> 8 & 255) << 16 | (n >>> 16 & 255) << 8 | n >>> 24 & 255 | 0;
1376
+ function _toGHASHKey(k) {
1377
+ k.reverse();
1378
+ const hiBit = k[15] & 1;
1379
+ let carry = 0;
1380
+ for (let i = 0; i < k.length; i++) {
1381
+ const t = k[i];
1382
+ k[i] = t >>> 1 | carry;
1383
+ carry = (t & 1) << 7;
1384
+ }
1385
+ k[0] ^= -hiBit & 225;
1386
+ return k;
1387
+ }
1388
+ var estimateWindow = (bytes) => {
1389
+ if (bytes > 64 * 1024)
1390
+ return 8;
1391
+ if (bytes > 1024)
1392
+ return 4;
1393
+ return 2;
1394
+ };
1395
+ var GHASH = class {
1396
+ blockLen = BLOCK_SIZE;
1397
+ outputLen = BLOCK_SIZE;
1398
+ s0 = 0;
1399
+ s1 = 0;
1400
+ s2 = 0;
1401
+ s3 = 0;
1402
+ finished = false;
1403
+ t;
1404
+ W;
1405
+ windowSize;
1406
+ // We select bits per window adaptively based on expectedLength
1407
+ constructor(key, expectedLength) {
1408
+ abytes(key, 16, "key");
1409
+ key = copyBytes(key);
1410
+ const kView = createView(key);
1411
+ let k0 = kView.getUint32(0, false);
1412
+ let k1 = kView.getUint32(4, false);
1413
+ let k2 = kView.getUint32(8, false);
1414
+ let k3 = kView.getUint32(12, false);
1415
+ const doubles = [];
1416
+ for (let i = 0; i < 128; i++) {
1417
+ doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
1418
+ ({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2(k0, k1, k2, k3));
1419
+ }
1420
+ const W = estimateWindow(expectedLength || 1024);
1421
+ if (![1, 2, 4, 8].includes(W))
1422
+ throw new Error("ghash: invalid window size, expected 2, 4 or 8");
1423
+ this.W = W;
1424
+ const bits = 128;
1425
+ const windows = bits / W;
1426
+ const windowSize = this.windowSize = 2 ** W;
1427
+ const items = [];
1428
+ for (let w = 0; w < windows; w++) {
1429
+ for (let byte = 0; byte < windowSize; byte++) {
1430
+ let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
1431
+ for (let j = 0; j < W; j++) {
1432
+ const bit = byte >>> W - j - 1 & 1;
1433
+ if (!bit)
1434
+ continue;
1435
+ const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
1436
+ s0 ^= d0, s1 ^= d1, s2 ^= d2, s3 ^= d3;
1437
+ }
1438
+ items.push({ s0, s1, s2, s3 });
1439
+ }
1440
+ }
1441
+ this.t = items;
1442
+ }
1443
+ _updateBlock(s0, s1, s2, s3) {
1444
+ s0 ^= this.s0, s1 ^= this.s1, s2 ^= this.s2, s3 ^= this.s3;
1445
+ const { W, t, windowSize } = this;
1446
+ let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
1447
+ const mask = (1 << W) - 1;
1448
+ let w = 0;
1449
+ for (const num of [s0, s1, s2, s3]) {
1450
+ for (let bytePos = 0; bytePos < 4; bytePos++) {
1451
+ const byte = num >>> 8 * bytePos & 255;
1452
+ for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
1453
+ const bit = byte >>> W * bitPos & mask;
1454
+ const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
1455
+ o0 ^= e0, o1 ^= e1, o2 ^= e2, o3 ^= e3;
1456
+ w += 1;
1457
+ }
1458
+ }
1459
+ }
1460
+ this.s0 = o0;
1461
+ this.s1 = o1;
1462
+ this.s2 = o2;
1463
+ this.s3 = o3;
1464
+ }
1465
+ update(data) {
1466
+ aexists(this);
1467
+ abytes(data);
1468
+ data = copyBytes(data);
1469
+ const b32 = u32(data);
1470
+ const blocks = Math.floor(data.length / BLOCK_SIZE);
1471
+ const left = data.length % BLOCK_SIZE;
1472
+ for (let i = 0; i < blocks; i++) {
1473
+ this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
1474
+ }
1475
+ if (left) {
1476
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
1477
+ this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
1478
+ clean(ZEROS32);
1479
+ }
1480
+ return this;
1481
+ }
1482
+ destroy() {
1483
+ const { t } = this;
1484
+ for (const elm of t) {
1485
+ elm.s0 = 0, elm.s1 = 0, elm.s2 = 0, elm.s3 = 0;
1486
+ }
1487
+ }
1488
+ digestInto(out) {
1489
+ aexists(this);
1490
+ aoutput(out, this);
1491
+ this.finished = true;
1492
+ const { s0, s1, s2, s3 } = this;
1493
+ const o32 = u32(out);
1494
+ o32[0] = s0;
1495
+ o32[1] = s1;
1496
+ o32[2] = s2;
1497
+ o32[3] = s3;
1498
+ return out;
1499
+ }
1500
+ digest() {
1501
+ const res = new Uint8Array(BLOCK_SIZE);
1502
+ this.digestInto(res);
1503
+ this.destroy();
1504
+ return res;
1505
+ }
1506
+ };
1507
+ var Polyval = class extends GHASH {
1508
+ constructor(key, expectedLength) {
1509
+ abytes(key);
1510
+ const ghKey = _toGHASHKey(copyBytes(key));
1511
+ super(ghKey, expectedLength);
1512
+ clean(ghKey);
1513
+ }
1514
+ update(data) {
1515
+ aexists(this);
1516
+ abytes(data);
1517
+ data = copyBytes(data);
1518
+ const b32 = u32(data);
1519
+ const left = data.length % BLOCK_SIZE;
1520
+ const blocks = Math.floor(data.length / BLOCK_SIZE);
1521
+ for (let i = 0; i < blocks; i++) {
1522
+ this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
1523
+ }
1524
+ if (left) {
1525
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
1526
+ this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
1527
+ clean(ZEROS32);
1528
+ }
1529
+ return this;
1530
+ }
1531
+ digestInto(out) {
1532
+ aexists(this);
1533
+ aoutput(out, this);
1534
+ this.finished = true;
1535
+ const { s0, s1, s2, s3 } = this;
1536
+ const o32 = u32(out);
1537
+ o32[0] = s0;
1538
+ o32[1] = s1;
1539
+ o32[2] = s2;
1540
+ o32[3] = s3;
1541
+ return out.reverse();
1542
+ }
1543
+ };
1544
+ function wrapConstructorWithKey(hashCons) {
1545
+ const hashC = (msg, key) => hashCons(key, msg.length).update(msg).digest();
1546
+ const tmp = hashCons(new Uint8Array(16), 0);
1547
+ hashC.outputLen = tmp.outputLen;
1548
+ hashC.blockLen = tmp.blockLen;
1549
+ hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
1550
+ return hashC;
1551
+ }
1552
+ var ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
1553
+ var polyval = wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
1554
+
1555
+ // node_modules/@noble/ciphers/aes.js
1556
+ var BLOCK_SIZE2 = 16;
1557
+ var BLOCK_SIZE32 = 4;
1558
+ var EMPTY_BLOCK = /* @__PURE__ */ new Uint8Array(BLOCK_SIZE2);
1559
+ var POLY2 = 283;
1560
+ function validateKeyLength(key) {
1561
+ if (![16, 24, 32].includes(key.length))
1562
+ throw new Error('"aes key" expected Uint8Array of length 16/24/32, got length=' + key.length);
1563
+ }
1564
+ function mul22(n) {
1565
+ return n << 1 ^ POLY2 & -(n >> 7);
1566
+ }
1567
+ function mul(a, b) {
1568
+ let res = 0;
1569
+ for (; b > 0; b >>= 1) {
1570
+ res ^= a & -(b & 1);
1571
+ a = mul22(a);
1572
+ }
1573
+ return res;
1574
+ }
1575
+ var sbox = /* @__PURE__ */ (() => {
1576
+ const t = new Uint8Array(256);
1577
+ for (let i = 0, x = 1; i < 256; i++, x ^= mul22(x))
1578
+ t[i] = x;
1579
+ const box = new Uint8Array(256);
1580
+ box[0] = 99;
1581
+ for (let i = 0; i < 255; i++) {
1582
+ let x = t[255 - i];
1583
+ x |= x << 8;
1584
+ box[t[i]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
1585
+ }
1586
+ clean(t);
1587
+ return box;
1588
+ })();
1589
+ var rotr32_8 = (n) => n << 24 | n >>> 8;
1590
+ var rotl32_8 = (n) => n << 8 | n >>> 24;
1591
+ function genTtable(sbox2, fn) {
1592
+ if (sbox2.length !== 256)
1593
+ throw new Error("Wrong sbox length");
1594
+ const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
1595
+ const T1 = T0.map(rotl32_8);
1596
+ const T2 = T1.map(rotl32_8);
1597
+ const T3 = T2.map(rotl32_8);
1598
+ const T01 = new Uint32Array(256 * 256);
1599
+ const T23 = new Uint32Array(256 * 256);
1600
+ const sbox22 = new Uint16Array(256 * 256);
1601
+ for (let i = 0; i < 256; i++) {
1602
+ for (let j = 0; j < 256; j++) {
1603
+ const idx = i * 256 + j;
1604
+ T01[idx] = T0[i] ^ T1[j];
1605
+ T23[idx] = T2[i] ^ T3[j];
1606
+ sbox22[idx] = sbox2[i] << 8 | sbox2[j];
1607
+ }
1608
+ }
1609
+ return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
1610
+ }
1611
+ var tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2));
1612
+ var xPowers = /* @__PURE__ */ (() => {
1613
+ const p = new Uint8Array(16);
1614
+ for (let i = 0, x = 1; i < 16; i++, x = mul22(x))
1615
+ p[i] = x;
1616
+ return p;
1617
+ })();
1618
+ function expandKeyLE(key) {
1619
+ abytes(key);
1620
+ const len = key.length;
1621
+ validateKeyLength(key);
1622
+ const { sbox2 } = tableEncoding;
1623
+ const toClean = [];
1624
+ if (!isAligned32(key))
1625
+ toClean.push(key = copyBytes(key));
1626
+ const k32 = u32(key);
1627
+ const Nk = k32.length;
1628
+ const subByte = (n) => applySbox(sbox2, n, n, n, n);
1629
+ const xk = new Uint32Array(len + 28);
1630
+ xk.set(k32);
1631
+ for (let i = Nk; i < xk.length; i++) {
1632
+ let t = xk[i - 1];
1633
+ if (i % Nk === 0)
1634
+ t = subByte(rotr32_8(t)) ^ xPowers[i / Nk - 1];
1635
+ else if (Nk > 6 && i % Nk === 4)
1636
+ t = subByte(t);
1637
+ xk[i] = xk[i - Nk] ^ t;
1638
+ }
1639
+ clean(...toClean);
1640
+ return xk;
1641
+ }
1642
+ function apply0123(T01, T23, s0, s1, s2, s3) {
1643
+ return T01[s0 << 8 & 65280 | s1 >>> 8 & 255] ^ T23[s2 >>> 8 & 65280 | s3 >>> 24 & 255];
1644
+ }
1645
+ function applySbox(sbox2, s0, s1, s2, s3) {
1646
+ return sbox2[s0 & 255 | s1 & 65280] | sbox2[s2 >>> 16 & 255 | s3 >>> 16 & 65280] << 16;
1647
+ }
1648
+ function encrypt(xk, s0, s1, s2, s3) {
1649
+ const { sbox2, T01, T23 } = tableEncoding;
1650
+ let k = 0;
1651
+ s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++];
1652
+ const rounds = xk.length / 4 - 2;
1653
+ for (let i = 0; i < rounds; i++) {
1654
+ const t02 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
1655
+ const t12 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
1656
+ const t22 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
1657
+ const t32 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
1658
+ s0 = t02, s1 = t12, s2 = t22, s3 = t32;
1659
+ }
1660
+ const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
1661
+ const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
1662
+ const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
1663
+ const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
1664
+ return { s0: t0, s1: t1, s2: t2, s3: t3 };
1665
+ }
1666
+ function ctr32(xk, isLE2, nonce, src, dst) {
1667
+ abytes(nonce, BLOCK_SIZE2, "nonce");
1668
+ abytes(src);
1669
+ dst = getOutput(src.length, dst);
1670
+ const ctr = nonce;
1671
+ const c32 = u32(ctr);
1672
+ const view = createView(ctr);
1673
+ const src32 = u32(src);
1674
+ const dst32 = u32(dst);
1675
+ const ctrPos = isLE2 ? 0 : 12;
1676
+ const srcLen = src.length;
1677
+ let ctrNum = view.getUint32(ctrPos, isLE2);
1678
+ let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
1679
+ for (let i = 0; i + 4 <= src32.length; i += 4) {
1680
+ dst32[i + 0] = src32[i + 0] ^ s0;
1681
+ dst32[i + 1] = src32[i + 1] ^ s1;
1682
+ dst32[i + 2] = src32[i + 2] ^ s2;
1683
+ dst32[i + 3] = src32[i + 3] ^ s3;
1684
+ ctrNum = ctrNum + 1 >>> 0;
1685
+ view.setUint32(ctrPos, ctrNum, isLE2);
1686
+ ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
1687
+ }
1688
+ const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32);
1689
+ if (start < srcLen) {
1690
+ const b32 = new Uint32Array([s0, s1, s2, s3]);
1691
+ const buf = u8(b32);
1692
+ for (let i = start, pos = 0; i < srcLen; i++, pos++)
1693
+ dst[i] = src[i] ^ buf[pos];
1694
+ clean(b32);
1695
+ }
1696
+ return dst;
1697
+ }
1698
+ function computeTag(fn, isLE2, key, data, AAD) {
1699
+ const aadLength = AAD ? AAD.length : 0;
1700
+ const h = fn.create(key, data.length + aadLength);
1701
+ if (AAD)
1702
+ h.update(AAD);
1703
+ const num = u64Lengths(8 * data.length, 8 * aadLength, isLE2);
1704
+ h.update(data);
1705
+ h.update(num);
1706
+ const res = h.digest();
1707
+ clean(num);
1708
+ return res;
1709
+ }
1710
+ var gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16, varSizeNonce: true }, function aesgcm(key, nonce, AAD) {
1711
+ if (nonce.length < 8)
1712
+ throw new Error("aes/gcm: invalid nonce length");
1713
+ const tagLength = 16;
1714
+ function _computeTag(authKey, tagMask, data) {
1715
+ const tag = computeTag(ghash, false, authKey, data, AAD);
1716
+ for (let i = 0; i < tagMask.length; i++)
1717
+ tag[i] ^= tagMask[i];
1718
+ return tag;
1719
+ }
1720
+ function deriveKeys() {
1721
+ const xk = expandKeyLE(key);
1722
+ const authKey = EMPTY_BLOCK.slice();
1723
+ const counter = EMPTY_BLOCK.slice();
1724
+ ctr32(xk, false, counter, counter, authKey);
1725
+ if (nonce.length === 12) {
1726
+ counter.set(nonce);
1727
+ } else {
1728
+ const nonceLen = EMPTY_BLOCK.slice();
1729
+ const view = createView(nonceLen);
1730
+ view.setBigUint64(8, BigInt(nonce.length * 8), false);
1731
+ const g = ghash.create(authKey).update(nonce).update(nonceLen);
1732
+ g.digestInto(counter);
1733
+ g.destroy();
1734
+ }
1735
+ const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
1736
+ return { xk, authKey, counter, tagMask };
1737
+ }
1738
+ return {
1739
+ encrypt(plaintext) {
1740
+ const { xk, authKey, counter, tagMask } = deriveKeys();
1741
+ const out = new Uint8Array(plaintext.length + tagLength);
1742
+ const toClean = [xk, authKey, counter, tagMask];
1743
+ if (!isAligned32(plaintext))
1744
+ toClean.push(plaintext = copyBytes(plaintext));
1745
+ ctr32(xk, false, counter, plaintext, out.subarray(0, plaintext.length));
1746
+ const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
1747
+ toClean.push(tag);
1748
+ out.set(tag, plaintext.length);
1749
+ clean(...toClean);
1750
+ return out;
1751
+ },
1752
+ decrypt(ciphertext) {
1753
+ const { xk, authKey, counter, tagMask } = deriveKeys();
1754
+ const toClean = [xk, authKey, tagMask, counter];
1755
+ if (!isAligned32(ciphertext))
1756
+ toClean.push(ciphertext = copyBytes(ciphertext));
1757
+ const data = ciphertext.subarray(0, -tagLength);
1758
+ const passedTag = ciphertext.subarray(-tagLength);
1759
+ const tag = _computeTag(authKey, tagMask, data);
1760
+ toClean.push(tag);
1761
+ if (!equalBytes(tag, passedTag))
1762
+ throw new Error("aes/gcm: invalid ghash tag");
1763
+ const out = ctr32(xk, false, counter, data);
1764
+ clean(...toClean);
1765
+ return out;
1766
+ }
1767
+ };
1768
+ });
1769
+ function isBytes32(a) {
1770
+ return a instanceof Uint32Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint32Array";
1771
+ }
1772
+ function encryptBlock(xk, block) {
1773
+ abytes(block, 16, "block");
1774
+ if (!isBytes32(xk))
1775
+ throw new Error("_encryptBlock accepts result of expandKeyLE");
1776
+ const b32 = u32(block);
1777
+ let { s0, s1, s2, s3 } = encrypt(xk, b32[0], b32[1], b32[2], b32[3]);
1778
+ b32[0] = s0, b32[1] = s1, b32[2] = s2, b32[3] = s3;
1779
+ return block;
1780
+ }
1781
+ function dbl(block) {
1782
+ let carry = 0;
1783
+ for (let i = BLOCK_SIZE2 - 1; i >= 0; i--) {
1784
+ const newCarry = (block[i] & 128) >>> 7;
1785
+ block[i] = block[i] << 1 | carry;
1786
+ carry = newCarry;
1787
+ }
1788
+ if (carry) {
1789
+ block[BLOCK_SIZE2 - 1] ^= 135;
1790
+ }
1791
+ return block;
1792
+ }
1793
+ function xorBlock(a, b) {
1794
+ if (a.length !== b.length)
1795
+ throw new Error("xorBlock: blocks must have same length");
1796
+ for (let i = 0; i < a.length; i++) {
1797
+ a[i] = a[i] ^ b[i];
1798
+ }
1799
+ return a;
1800
+ }
1801
+ var _CMAC = class {
1802
+ buffer;
1803
+ destroyed;
1804
+ k1;
1805
+ k2;
1806
+ xk;
1807
+ constructor(key) {
1808
+ abytes(key);
1809
+ validateKeyLength(key);
1810
+ this.xk = expandKeyLE(key);
1811
+ this.buffer = new Uint8Array(0);
1812
+ this.destroyed = false;
1813
+ const L = new Uint8Array(BLOCK_SIZE2);
1814
+ encryptBlock(this.xk, L);
1815
+ this.k1 = dbl(L);
1816
+ this.k2 = dbl(new Uint8Array(this.k1));
1817
+ }
1818
+ update(data) {
1819
+ const { destroyed, buffer } = this;
1820
+ if (destroyed)
1821
+ throw new Error("CMAC instance was destroyed");
1822
+ abytes(data);
1823
+ const newBuffer = new Uint8Array(buffer.length + data.length);
1824
+ newBuffer.set(buffer);
1825
+ newBuffer.set(data, buffer.length);
1826
+ this.buffer = newBuffer;
1827
+ return this;
1828
+ }
1829
+ // see https://www.rfc-editor.org/rfc/rfc4493.html#section-2.4
1830
+ digest() {
1831
+ if (this.destroyed)
1832
+ throw new Error("CMAC instance was destroyed");
1833
+ const { buffer } = this;
1834
+ const msgLen = buffer.length;
1835
+ let n = Math.ceil(msgLen / BLOCK_SIZE2);
1836
+ let flag;
1837
+ if (n === 0) {
1838
+ n = 1;
1839
+ flag = false;
1840
+ } else {
1841
+ flag = msgLen % BLOCK_SIZE2 === 0;
1842
+ }
1843
+ const lastBlockStart = (n - 1) * BLOCK_SIZE2;
1844
+ const lastBlockData = buffer.subarray(lastBlockStart);
1845
+ let m_last;
1846
+ if (flag) {
1847
+ m_last = xorBlock(new Uint8Array(lastBlockData), this.k1);
1848
+ } else {
1849
+ const padded = new Uint8Array(BLOCK_SIZE2);
1850
+ padded.set(lastBlockData);
1851
+ padded[lastBlockData.length] = 128;
1852
+ m_last = xorBlock(padded, this.k2);
1853
+ }
1854
+ let x = new Uint8Array(BLOCK_SIZE2);
1855
+ for (let i = 0; i < n - 1; i++) {
1856
+ const m_i = buffer.subarray(i * BLOCK_SIZE2, (i + 1) * BLOCK_SIZE2);
1857
+ xorBlock(x, m_i);
1858
+ encryptBlock(this.xk, x);
1859
+ }
1860
+ xorBlock(x, m_last);
1861
+ encryptBlock(this.xk, x);
1862
+ clean(m_last);
1863
+ return x;
1864
+ }
1865
+ destroy() {
1866
+ const { buffer, destroyed, xk, k1, k2 } = this;
1867
+ if (destroyed)
1868
+ return;
1869
+ this.destroyed = true;
1870
+ clean(buffer, xk, k1, k2);
1871
+ }
1872
+ };
1873
+ var cmac = (key, message) => new _CMAC(key).update(message).digest();
1874
+ cmac.create = (key) => new _CMAC(key);
1875
+
1876
+ // src/sdk/file-encryption.ts
1877
+ import { generateRandomBytes } from "@fileverse/crypto/utils";
1878
+ var KEY_LEN = 32;
1879
+ var IV_LEN = 12;
1880
+ var TAG_LEN = 16;
1881
+ var bytesToB64 = (b) => Buffer.from(b).toString("base64");
1882
+ function gcmEncrypt(plaintext) {
1883
+ const key = generateRandomBytes(KEY_LEN);
1884
+ const iv = generateRandomBytes(IV_LEN);
1885
+ if (key.length !== KEY_LEN) throw new Error("key must be 32 bytes");
1886
+ if (iv.length !== IV_LEN) throw new Error("iv must be 12 bytes");
1887
+ const out = gcm(key, iv).encrypt(plaintext);
1888
+ const ciphertext = out.subarray(0, out.length - TAG_LEN);
1889
+ const authTag = out.subarray(out.length - TAG_LEN);
1890
+ return {
1891
+ ciphertext,
1892
+ authTag: bytesToB64(authTag),
1893
+ key: bytesToB64(key),
1894
+ iv: bytesToB64(iv)
1895
+ };
1896
+ }
1897
+
1898
+ // src/sdk/file-utils.ts
1899
+ import { toAESKey, aesEncrypt } from "@fileverse/crypto/webcrypto";
1900
+ import axios from "axios";
1901
+ import { encodeFunctionData, parseEventLogs } from "viem";
1902
+ var deriveKeyFromAg2Hash = async (pass, salt) => {
1903
+ const key = await getArgon2idHash(pass, salt);
1904
+ return hkdf(Buffer.from(key), tweetnacl.secretbox.keyLength, {
1905
+ info: Buffer.from("encryptionKey")
1906
+ });
1907
+ };
1908
+ var decryptSecretKey = async (docId, nonce, encryptedSecretKey) => {
1909
+ const derivedKey = await deriveKeyFromAg2Hash(docId, toUint8Array(nonce));
1910
+ return tweetnacl.secretbox.open(toUint8Array(encryptedSecretKey), toUint8Array(nonce), derivedKey);
1911
+ };
1912
+ var getExistingEncryptionMaterial = async (existingEncryptedSecretKey, existingNonce, docId) => {
1913
+ const secretKey = await decryptSecretKey(docId, existingNonce, existingEncryptedSecretKey);
1914
+ return {
1915
+ encryptedSecretKey: existingEncryptedSecretKey,
1916
+ nonce: toUint8Array(existingNonce),
1917
+ secretKey
1918
+ };
1919
+ };
1920
+ var getNaclSecretKey = async (ddocId) => {
1921
+ const { secretKey } = tweetnacl.box.keyPair();
1922
+ const nonce = tweetnacl.randomBytes(tweetnacl.secretbox.nonceLength);
1923
+ const derivedKey = await deriveKeyFromAg2Hash(ddocId, nonce);
1924
+ const encryptedSecretKey = fromUint8Array(tweetnacl.secretbox(secretKey, nonce, derivedKey), true);
1925
+ return { nonce, encryptedSecretKey, secretKey };
1926
+ };
1927
+ var generateLinkKeyMaterial = async (params) => {
1928
+ if (params.linkKeyNonce && params.linkKey) {
1929
+ const { encryptedSecretKey: encryptedSecretKey2, nonce: nonce2, secretKey: secretKey2 } = await getExistingEncryptionMaterial(
1930
+ params.linkKey,
1931
+ params.linkKeyNonce,
1932
+ params.ddocId
1933
+ );
1934
+ if (secretKey2) return { encryptedSecretKey: encryptedSecretKey2, nonce: nonce2, secretKey: secretKey2 };
1935
+ }
1936
+ const { secretKey, nonce, encryptedSecretKey } = await getNaclSecretKey(params.ddocId);
1937
+ return { secretKey, nonce, encryptedSecretKey };
1938
+ };
1939
+ var jsonToFile = (json, fileName) => {
1940
+ const blob = new Blob([JSON.stringify(json)], {
1941
+ type: "application/json"
1942
+ });
1943
+ const file = new File([blob], fileName, {
1944
+ type: "application/json"
1945
+ });
1946
+ return file;
1947
+ };
1948
+ var appendAuthTagIvToBlob = async (blob, authTag, iv) => {
1949
+ const encryptedFileBytes = await blob.arrayBuffer();
1950
+ const encryptedBytes = new Uint8Array(encryptedFileBytes);
1951
+ const combinedLength = encryptedBytes.length + authTag.length + iv.length;
1952
+ const combinedArray = new Uint8Array(combinedLength);
1953
+ let offset = 0;
1954
+ combinedArray.set(encryptedBytes, offset);
1955
+ offset += encryptedBytes.length;
1956
+ combinedArray.set(authTag, offset);
1957
+ offset += authTag.length;
1958
+ combinedArray.set(iv, offset);
1959
+ return new Blob([combinedArray], { type: blob.type });
1960
+ };
1961
+ var encryptFile = async (file) => {
1962
+ const arrayBuffer = await file.arrayBuffer();
1963
+ const plaintext = new Uint8Array(arrayBuffer);
1964
+ const { ciphertext, authTag, key, iv } = gcmEncrypt(plaintext);
1965
+ const encryptedBlob = new Blob([ciphertext], { type: file.type });
1966
+ const encryptedBlobWithAuthTagIv = await appendAuthTagIvToBlob(
1967
+ encryptedBlob,
1968
+ toUint8Array(authTag),
1969
+ toUint8Array(iv)
1970
+ );
1971
+ return {
1972
+ encryptedFile: new File([encryptedBlobWithAuthTagIv], file.name),
1973
+ key
1974
+ };
1975
+ };
1976
+ var getNonceAppendedCipherText = (nonce, cipherText) => {
1977
+ return fromUint8Array(nonce, true) + "__n__" + fromUint8Array(cipherText, true);
1978
+ };
1979
+ var jsonToBytes = (json) => new TextEncoder().encode(JSON.stringify(json));
1980
+ var buildLinklock = (key, fileKey, commentKey) => {
1981
+ const ikm = generateRandomBytes2();
1982
+ const kdfSalt = generateRandomBytes2();
1983
+ const derivedEphermalKey = derivePBKDF2Key(ikm, kdfSalt);
1984
+ const { iv, cipherText } = encryptAesCBC(
1985
+ {
1986
+ key: derivedEphermalKey,
1987
+ message: fileKey
1988
+ },
1989
+ "base64"
1990
+ );
1991
+ const { iv: commentIv, cipherText: commentCipherText } = encryptAesCBC(
1992
+ {
1993
+ key: derivedEphermalKey,
1994
+ message: commentKey
1995
+ },
1996
+ "base64"
1997
+ );
1998
+ const encryptedIkm = secretBoxEncrypt(ikm, key);
1999
+ const lockedFileKey = iv + "__n__" + cipherText;
2000
+ const lockedChatKey = commentIv + "__n__" + commentCipherText;
2001
+ const keyMaterial = bytesToBase64(kdfSalt) + "__n__" + encryptedIkm;
2002
+ const fileKeyNonce = generateRandomBytes2(24);
2003
+ const encryptedFileKey = tweetnacl.secretbox(jsonToBytes({ key: fromUint8Array(fileKey) }), fileKeyNonce, key);
2004
+ const chatKeyNonce = generateRandomBytes2(24);
2005
+ const encryptedChatKey = tweetnacl.secretbox(commentKey, chatKeyNonce, key);
2006
+ return {
2007
+ lockedFileKey: getNonceAppendedCipherText(fileKeyNonce, encryptedFileKey),
2008
+ lockedChatKey: getNonceAppendedCipherText(chatKeyNonce, encryptedChatKey),
2009
+ lockedFileKey_v2: lockedFileKey,
2010
+ lockedChatKey_v2: lockedChatKey,
2011
+ keyMaterial
2012
+ };
2013
+ };
2014
+ var encryptTitleWithFileKey = async (args) => {
2015
+ const key = await toAESKey(toUint8Array(args.key));
2016
+ if (!key) throw new Error("Key is undefined");
2017
+ const titleBytes = new TextEncoder().encode(args.title);
2018
+ const encryptedTitle = await aesEncrypt(key, titleBytes, "base64");
2019
+ return encryptedTitle;
2020
+ };
2021
+ var uploadFileToIPFS = async (fileParams, authParams) => {
2022
+ const { file, ipfsType, appFileId } = fileParams;
2023
+ const { token, invoker, contractAddress } = authParams;
2024
+ const body = new FormData();
2025
+ body.append("file", file);
2026
+ body.append("ipfsType", ipfsType);
2027
+ body.append("appFileId", appFileId);
2028
+ body.append("sourceApp", "ddoc");
2029
+ const uploadEndpoint = UPLOAD_SERVER_URL + "upload";
2030
+ const response = await axios.post(uploadEndpoint, body, {
2031
+ headers: {
2032
+ Authorization: `Bearer ${token}`,
2033
+ contract: contractAddress,
2034
+ invoker,
2035
+ chain: process.env.chainId
2036
+ }
2037
+ });
2038
+ return response.data.ipfsHash;
2039
+ };
2040
+ var getEditFileTrxCalldata = (args) => {
2041
+ return encodeFunctionData({
2042
+ abi: EDIT_FILE_METHOD,
2043
+ functionName: "editFile",
2044
+ args: [BigInt(args.fileId), args.appFileId, args.metadataHash, args.contentHash, args.gateHash, 2, BigInt(0)]
2045
+ });
2046
+ };
2047
+ var getAddFileTrxCalldata = (args) => {
2048
+ return encodeFunctionData({
2049
+ abi: ADD_FILE_METHOD,
2050
+ functionName: "addFile",
2051
+ args: [args.appFileId, 2, args.metadataHash, args.contentHash, args.gateHash, BigInt(0)]
2052
+ });
2053
+ };
2054
+ var prepareCallData = (args) => {
2055
+ if (args.fileId) {
2056
+ return getEditFileTrxCalldata({
2057
+ fileId: args.fileId,
2058
+ appFileId: args.appFileId,
2059
+ metadataHash: args.metadataHash,
2060
+ contentHash: args.contentHash,
2061
+ gateHash: args.gateHash
2062
+ });
2063
+ }
2064
+ return getAddFileTrxCalldata(args);
2065
+ };
2066
+ var prepareDeleteFileCallData = (args) => {
2067
+ return encodeFunctionData({
2068
+ abi: DELETED_FILE_ABI,
2069
+ functionName: "deleteFile",
2070
+ args: [BigInt(args.onChainFileId)]
2071
+ });
2072
+ };
2073
+ var createEncryptedContentFile = async (content) => {
2074
+ const contentFile = jsonToFile(
2075
+ { file: content, source: "ddoc" },
2076
+ `${fromUint8Array(generateRandomBytes2(16))}-CONTENT`
2077
+ );
2078
+ return encryptFile(contentFile);
2079
+ };
2080
+ var buildFileMetadata = (params) => ({
2081
+ title: params.encryptedTitle,
2082
+ size: params.encryptedFileSize,
2083
+ mimeType: "application/json",
2084
+ appLock: params.appLock,
2085
+ ownerLock: params.ownerLock,
2086
+ ddocId: params.ddocId,
2087
+ nonce: params.nonce,
2088
+ owner: params.owner,
2089
+ version: "4",
2090
+ sourceApp: "fileverse-api"
2091
+ });
2092
+ var parseFileEventLog = (logs, eventName, abi) => {
2093
+ const [parsedLog] = parseEventLogs({ abi, logs, eventName });
2094
+ if (!parsedLog) throw new Error(`${eventName} event not found`);
2095
+ const fileId = parsedLog.args.fileId;
2096
+ if (fileId === void 0 || fileId === null) throw new Error("FileId not found in event logs");
2097
+ return Number(fileId);
2098
+ };
2099
+ var uploadAllFilesToIPFS = async (params, authParams) => {
2100
+ const { metadata, encryptedFile, linkLock, ddocId } = params;
2101
+ const [metadataHash, contentHash, gateHash] = await Promise.all([
2102
+ uploadFileToIPFS(
2103
+ {
2104
+ file: jsonToFile(metadata, `${fromUint8Array(generateRandomBytes2(16))}-METADATA`),
2105
+ ipfsType: "METADATA",
2106
+ appFileId: ddocId
2107
+ },
2108
+ authParams
2109
+ ),
2110
+ uploadFileToIPFS(
2111
+ {
2112
+ file: encryptedFile,
2113
+ ipfsType: "CONTENT",
2114
+ appFileId: ddocId
2115
+ },
2116
+ authParams
2117
+ ),
2118
+ uploadFileToIPFS(
2119
+ {
2120
+ file: jsonToFile(linkLock, `${fromUint8Array(generateRandomBytes2(16))}-GATE`),
2121
+ ipfsType: "GATE",
2122
+ appFileId: ddocId
2123
+ },
2124
+ authParams
2125
+ )
2126
+ ]);
2127
+ return { metadataHash, contentHash, gateHash };
2128
+ };
2129
+
2130
+ // src/sdk/file-manager.ts
2131
+ import { generateAESKey, exportAESKey } from "@fileverse/crypto/webcrypto";
2132
+ import { markdownToYjs } from "@fileverse/content-processor";
2133
+ var FileManager = class {
2134
+ keyStore;
2135
+ agentClient;
2136
+ constructor(keyStore, agentClient) {
2137
+ this.keyStore = keyStore;
2138
+ this.agentClient = agentClient;
2139
+ }
2140
+ createLocks(key, encryptedSecretKey, commentKey) {
2141
+ const appLock = {
2142
+ lockedFileKey: this.keyStore.encryptData(toUint8Array2(key)),
2143
+ lockedLinkKey: this.keyStore.encryptData(toUint8Array2(encryptedSecretKey)),
2144
+ lockedChatKey: this.keyStore.encryptData(commentKey)
2145
+ };
2146
+ return { appLock, ownerLock: { ...appLock } };
2147
+ }
2148
+ async getAuthParams() {
2149
+ return {
2150
+ token: await this.keyStore.getAuthToken(STATIC_CONFIG.SERVER_DID),
2151
+ contractAddress: this.keyStore.getPortalAddress(),
2152
+ invoker: this.agentClient.getAgentAddress()
2153
+ };
2154
+ }
2155
+ async executeFileOperation(callData) {
2156
+ return this.agentClient.executeUserOperationRequest(
2157
+ {
2158
+ contractAddress: this.keyStore.getPortalAddress(),
2159
+ data: callData
2160
+ },
2161
+ 1e6
2162
+ );
2163
+ }
2164
+ async sendFileOperation(callData) {
2165
+ return this.agentClient.sendUserOperation(
2166
+ {
2167
+ contractAddress: this.keyStore.getPortalAddress(),
2168
+ data: callData
2169
+ },
2170
+ 1e6
2171
+ );
2172
+ }
2173
+ async getProxyAuthParams() {
2174
+ return this.agentClient.getAuthParams();
2175
+ }
2176
+ async submitAddFileTrx(file) {
2177
+ logger.debug(`Preparing to add file ${file.ddocId}`);
2178
+ const { encryptedSecretKey, nonce, secretKey } = await generateLinkKeyMaterial({
2179
+ ddocId: file.ddocId,
2180
+ linkKey: file.linkKey,
2181
+ linkKeyNonce: file.linkKeyNonce
2182
+ });
2183
+ const yJSContent = markdownToYjs(file.content);
2184
+ const { encryptedFile, key } = await createEncryptedContentFile(yJSContent);
2185
+ logger.debug(`Generated encrypted content file for file ${file.ddocId}`);
2186
+ const commentKey = await exportAESKey(await generateAESKey(128));
2187
+ const { appLock, ownerLock } = this.createLocks(key, encryptedSecretKey, commentKey);
2188
+ const linkLock = buildLinklock(secretKey, toUint8Array2(key), commentKey);
2189
+ const encryptedTitle = await encryptTitleWithFileKey({
2190
+ title: file.title || "Untitled",
2191
+ key
2192
+ });
2193
+ const metadata = buildFileMetadata({
2194
+ encryptedTitle,
2195
+ encryptedFileSize: encryptedFile.size,
2196
+ appLock,
2197
+ ownerLock,
2198
+ ddocId: file.ddocId,
2199
+ nonce: fromUint8Array2(nonce),
2200
+ owner: this.agentClient.getAgentAddress()
2201
+ });
2202
+ const authParams = await this.getAuthParams();
2203
+ const { metadataHash, contentHash, gateHash } = await uploadAllFilesToIPFS(
2204
+ { metadata, encryptedFile, linkLock, ddocId: file.ddocId },
2205
+ authParams
2206
+ );
2207
+ logger.debug(`Uploaded files to IPFS for file ${file.ddocId}`);
2208
+ const callData = prepareCallData({
2209
+ metadataHash,
2210
+ contentHash,
2211
+ gateHash,
2212
+ appFileId: file.ddocId,
2213
+ fileId: file.fileId
2214
+ });
2215
+ logger.debug(`Prepared call data for file ${file.ddocId}`);
2216
+ const userOpHash = await this.sendFileOperation(callData);
2217
+ logger.debug(`Submitted user op for file ${file.ddocId}`);
2218
+ return {
2219
+ userOpHash,
2220
+ linkKey: encryptedSecretKey,
2221
+ linkKeyNonce: fromUint8Array2(nonce),
2222
+ commentKey: fromUint8Array2(commentKey),
2223
+ metadata
2224
+ };
2225
+ }
2226
+ async updateFile(file) {
2227
+ logger.debug(`Updating file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);
2228
+ const { encryptedSecretKey, nonce, secretKey } = await generateLinkKeyMaterial({
2229
+ ddocId: file.ddocId,
2230
+ linkKey: file.linkKey,
2231
+ linkKeyNonce: file.linkKeyNonce
2232
+ });
2233
+ logger.debug(`Generating encrypted content file for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);
2234
+ const yjsContent = markdownToYjs(file.content);
2235
+ const { encryptedFile, key } = await createEncryptedContentFile(yjsContent);
2236
+ const commentKey = toUint8Array2(file.commentKey);
2237
+ const { appLock, ownerLock } = this.createLocks(key, encryptedSecretKey, commentKey);
2238
+ const linkLock = buildLinklock(secretKey, toUint8Array2(key), commentKey);
2239
+ const encryptedTitle = await encryptTitleWithFileKey({
2240
+ title: file.title || "Untitled",
2241
+ key
2242
+ });
2243
+ const metadata = buildFileMetadata({
2244
+ encryptedTitle,
2245
+ encryptedFileSize: encryptedFile.size,
2246
+ appLock,
2247
+ ownerLock,
2248
+ ddocId: file.ddocId,
2249
+ nonce: fromUint8Array2(nonce),
2250
+ owner: this.agentClient.getAgentAddress()
2251
+ });
2252
+ const authParams = await this.getAuthParams();
2253
+ logger.debug(`Uploading files to IPFS for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);
2254
+ const { metadataHash, contentHash, gateHash } = await uploadAllFilesToIPFS(
2255
+ { metadata, encryptedFile, linkLock, ddocId: file.ddocId },
2256
+ authParams
2257
+ );
2258
+ const callData = prepareCallData({
2259
+ metadataHash,
2260
+ contentHash,
2261
+ gateHash,
2262
+ appFileId: file.ddocId,
2263
+ fileId: file.onChainFileId
2264
+ });
2265
+ logger.debug(`Executing file operation for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);
2266
+ const { logs } = await this.executeFileOperation(callData);
2267
+ const onChainFileId = parseFileEventLog(logs, "EditedFile", EDITED_FILE_EVENT);
2268
+ return { onChainFileId, metadata };
2269
+ }
2270
+ async deleteFile(file) {
2271
+ logger.debug(`Deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);
2272
+ const callData = prepareDeleteFileCallData({
2273
+ onChainFileId: file.onChainFileId
2274
+ });
2275
+ logger.debug(`Prepared call data for deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);
2276
+ const { logs } = await this.executeFileOperation(callData);
2277
+ parseFileEventLog(logs, "DeletedFile", DELETED_FILE_EVENT);
2278
+ logger.debug(`Executed file operation for deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);
2279
+ return {
2280
+ fileId: file.id,
2281
+ onChainFileId: file.onChainFileId,
2282
+ metadata: file.metadata
2283
+ };
2284
+ }
2285
+ };
2286
+
2287
+ // src/domain/portal/publish.ts
2288
+ function getPortalData(fileId) {
2289
+ const file = FilesModel.findByIdIncludingDeleted(fileId);
2290
+ if (!file) {
2291
+ throw new Error(`File with _id ${fileId} not found`);
2292
+ }
2293
+ const portalDetails = PortalsModel.findByPortalAddress(file.portalAddress);
2294
+ if (!portalDetails) {
2295
+ throw new Error(`Portal with address ${file.portalAddress} not found`);
2296
+ }
2297
+ const apiKey = getRuntimeConfig().API_KEY;
2298
+ if (!apiKey) {
2299
+ throw new Error("API key is not set");
2300
+ }
2301
+ return { file, portalDetails, apiKey };
2302
+ }
2303
+ function deriveCollaboratorKeys(apiKeySeed) {
2304
+ const salt = new Uint8Array([0]);
2305
+ const privateAccountKey = deriveHKDFKey(apiKeySeed, salt, stringToBytes("COLLABORATOR_PRIVATE_KEY"));
2306
+ const ucanDerivedSecret = deriveHKDFKey(apiKeySeed, salt, stringToBytes("COLLABORATOR_UCAN_SECRET"));
2307
+ const { secretKey: ucanSecret } = generateKeyPairFromSeed(ucanDerivedSecret);
2308
+ return { privateAccountKey, ucanSecret };
2309
+ }
2310
+ var createFileManager = async (portalSeed, portalAddress, ucanSecret, privateAccountKey) => {
2311
+ const keyPair = ucans2.EdKeypair.fromSecretKey(fromUint8Array3(ucanSecret), {
2312
+ exportable: true
2313
+ });
2314
+ const authTokenProvider = new AuthTokenProvider(keyPair, portalAddress);
2315
+ const keyStore = new KeyStore(toUint8Array3(portalSeed), portalAddress, authTokenProvider);
2316
+ const agentClient = new AgentClient(authTokenProvider);
2317
+ await agentClient.initializeAgentClient(privateAccountKey);
2318
+ return new FileManager(keyStore, agentClient);
2319
+ };
2320
+ var executeOperation = async (fileManager, file, operation) => {
2321
+ if (operation === "update") {
2322
+ const result = await fileManager.updateFile(file);
2323
+ return { success: true, ...result };
2324
+ }
2325
+ if (operation === "delete") {
2326
+ const result = await fileManager.deleteFile(file);
2327
+ return { success: true, ...result };
2328
+ }
2329
+ throw new Error(`Invalid operation: ${operation}`);
2330
+ };
2331
+ var handleExistingFileOp = async (fileId, operation) => {
2332
+ try {
2333
+ const { file, portalDetails, apiKey } = getPortalData(fileId);
2334
+ const apiKeySeed = toUint8Array3(apiKey);
2335
+ const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
2336
+ const fileManager = await createFileManager(
2337
+ portalDetails.portalSeed,
2338
+ portalDetails.portalAddress,
2339
+ ucanSecret,
2340
+ privateAccountKey
2341
+ );
2342
+ return executeOperation(fileManager, file, operation);
2343
+ } catch (error) {
2344
+ logger.error(`Failed to publish file ${fileId}:`, error);
2345
+ throw error;
2346
+ }
2347
+ };
2348
+ var handleNewFileOp = async (fileId) => {
2349
+ const { file, portalDetails, apiKey } = getPortalData(fileId);
2350
+ const apiKeySeed = toUint8Array3(apiKey);
2351
+ const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
2352
+ const fileManager = await createFileManager(
2353
+ portalDetails.portalSeed,
2354
+ portalDetails.portalAddress,
2355
+ ucanSecret,
2356
+ privateAccountKey
2357
+ );
2358
+ return fileManager.submitAddFileTrx(file);
2359
+ };
2360
+ var getProxyAuthParams = async (fileId) => {
2361
+ const { portalDetails, apiKey } = getPortalData(fileId);
2362
+ const apiKeySeed = toUint8Array3(apiKey);
2363
+ const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
2364
+ const fileManager = await createFileManager(
2365
+ portalDetails.portalSeed,
2366
+ portalDetails.portalAddress,
2367
+ ucanSecret,
2368
+ privateAccountKey
2369
+ );
2370
+ return fileManager.getProxyAuthParams();
2371
+ };
2372
+
2373
+ // src/errors/rate-limit.ts
2374
+ import { HttpRequestError } from "viem";
2375
+ var RateLimitError = class extends Error {
2376
+ retryAfterSeconds;
2377
+ constructor(retryAfterSeconds, message = "Rate limit exceeded") {
2378
+ super(message);
2379
+ this.name = "RateLimitError";
2380
+ this.retryAfterSeconds = retryAfterSeconds;
2381
+ }
2382
+ };
2383
+ var MAX_RETRY_AFTER_SECONDS = 300;
2384
+ var DEFAULT_RETRY_AFTER_SECONDS = 3600;
2385
+ function parseRetryAfterRaw(raw) {
2386
+ if (!raw) return DEFAULT_RETRY_AFTER_SECONDS;
2387
+ const parsed = parseInt(raw, 10);
2388
+ if (!Number.isNaN(parsed) && parsed >= 0) return Math.min(parsed, MAX_RETRY_AFTER_SECONDS);
2389
+ const date = Date.parse(raw);
2390
+ if (!Number.isNaN(date)) {
2391
+ const seconds = Math.max(0, Math.ceil((date - Date.now()) / 1e3));
2392
+ return Math.min(seconds, MAX_RETRY_AFTER_SECONDS);
2393
+ }
2394
+ return DEFAULT_RETRY_AFTER_SECONDS;
2395
+ }
2396
+ var parseRetryAfterFromHeaders = (headers) => parseRetryAfterRaw(headers?.get("Retry-After") ?? null);
2397
+ function normalizeRateLimitError(error) {
2398
+ if (!(error instanceof HttpRequestError) || error.status !== 429) return error;
2399
+ const retryAfter = parseRetryAfterFromHeaders(error.headers);
2400
+ const message = "Beta API rate limit reached. Try again in an hour please!";
2401
+ return new RateLimitError(retryAfter, message);
2402
+ }
2403
+
2404
+ // src/infra/worker/eventProcessor.ts
2405
+ var processEvent = async (event) => {
2406
+ const { fileId, type } = event;
2407
+ try {
2408
+ switch (type) {
2409
+ case "create":
2410
+ await processCreateEvent(event);
2411
+ break;
2412
+ case "update":
2413
+ await processUpdateEvent(event);
2414
+ break;
2415
+ case "delete":
2416
+ await processDeleteEvent(event);
2417
+ break;
2418
+ default:
2419
+ throw new Error(`Unknown event type: ${type}`);
2420
+ }
2421
+ return { success: true };
2422
+ } catch (error) {
2423
+ const normalized = normalizeRateLimitError(error);
2424
+ if (normalized instanceof RateLimitError) throw normalized;
2425
+ const errorMsg = error instanceof Error ? error.message : String(error);
2426
+ logger.error(`Error processing ${type} event for file ${fileId}:`, errorMsg);
2427
+ return { success: false, error: errorMsg };
2428
+ }
2429
+ };
2430
+ var onTransactionSuccess = (fileId, file, onChainFileId, pending) => {
2431
+ const frontendUrl = getRuntimeConfig().FRONTEND_URL;
2432
+ const payload = {
2433
+ onchainVersion: file.localVersion,
2434
+ onChainFileId,
2435
+ linkKey: pending.linkKey,
2436
+ linkKeyNonce: pending.linkKeyNonce,
2437
+ commentKey: pending.commentKey,
2438
+ metadata: pending.metadata,
2439
+ link: `${frontendUrl}/${file.portalAddress}/${onChainFileId}#key=${pending.linkKey}`
2440
+ };
2441
+ const updatedFile = FilesModel.update(fileId, payload, file.portalAddress);
2442
+ if (updatedFile.localVersion === updatedFile.onchainVersion) {
2443
+ FilesModel.update(fileId, { syncStatus: "synced" }, file.portalAddress);
2444
+ }
2445
+ };
2446
+ var processCreateEvent = async (event) => {
2447
+ const { fileId } = event;
2448
+ const file = FilesModel.findByIdIncludingDeleted(fileId);
2449
+ if (!file) {
2450
+ throw new Error(`File ${fileId} not found`);
2451
+ }
2452
+ if (file.isDeleted === 1) {
2453
+ logger.info(`File ${fileId} is deleted, skipping create event`);
2454
+ return;
2455
+ }
2456
+ const waitContext = await getProxyAuthParams(fileId);
2457
+ const timeout = 12e4;
2458
+ if (event.userOpHash) {
2459
+ const receipt2 = await waitForUserOpReceipt(
2460
+ event.userOpHash,
2461
+ waitContext.authToken,
2462
+ waitContext.portalAddress,
2463
+ waitContext.invokerAddress,
2464
+ timeout
2465
+ );
2466
+ if (!receipt2.success) {
2467
+ EventsModel.clearEventPendingOp(event._id);
2468
+ throw new Error(`User operation failed: ${receipt2.reason}`);
2469
+ }
2470
+ const onChainFileId2 = parseFileEventLog(receipt2.logs, "AddedFile", ADDED_FILE_EVENT);
2471
+ const pending = JSON.parse(event.pendingPayload);
2472
+ onTransactionSuccess(fileId, file, onChainFileId2, pending);
2473
+ EventsModel.clearEventPendingOp(event._id);
2474
+ logger.info(`File ${file.ddocId} created and published successfully (resumed from pending op)`);
2475
+ return;
2476
+ }
2477
+ const result = await handleNewFileOp(fileId);
2478
+ EventsModel.setEventPendingOp(event._id, result.userOpHash, {
2479
+ linkKey: result.linkKey,
2480
+ linkKeyNonce: result.linkKeyNonce,
2481
+ commentKey: result.commentKey,
2482
+ metadata: result.metadata
2483
+ });
2484
+ const receipt = await waitForUserOpReceipt(
2485
+ result.userOpHash,
2486
+ waitContext.authToken,
2487
+ waitContext.portalAddress,
2488
+ waitContext.invokerAddress,
2489
+ timeout
2490
+ );
2491
+ if (!receipt.success) {
2492
+ EventsModel.clearEventPendingOp(event._id);
2493
+ throw new Error(`User operation failed: ${receipt.reason}`);
2494
+ }
2495
+ const onChainFileId = parseFileEventLog(receipt.logs, "AddedFile", ADDED_FILE_EVENT);
2496
+ onTransactionSuccess(fileId, file, onChainFileId, {
2497
+ linkKey: result.linkKey,
2498
+ linkKeyNonce: result.linkKeyNonce,
2499
+ commentKey: result.commentKey,
2500
+ metadata: result.metadata
2501
+ });
2502
+ EventsModel.clearEventPendingOp(event._id);
2503
+ logger.info(`File ${file.ddocId} created and published successfully`);
2504
+ };
2505
+ var processUpdateEvent = async (event) => {
2506
+ const { fileId } = event;
2507
+ const file = FilesModel.findByIdExcludingDeleted(fileId);
2508
+ if (!file) {
2509
+ return;
2510
+ }
2511
+ if (file.localVersion <= file.onchainVersion) {
2512
+ return;
2513
+ }
2514
+ const result = await handleExistingFileOp(fileId, "update");
2515
+ if (!result.success) {
2516
+ throw new Error(`Publish failed for file ${fileId}`);
2517
+ }
2518
+ const payload = {
2519
+ onchainVersion: file.localVersion,
2520
+ metadata: result.metadata
2521
+ };
2522
+ const updatedFile = FilesModel.update(fileId, payload, file.portalAddress);
2523
+ if (updatedFile.localVersion === updatedFile.onchainVersion) {
2524
+ FilesModel.update(fileId, { syncStatus: "synced" }, file.portalAddress);
2525
+ }
2526
+ logger.info(`File ${file.ddocId} updated and published successfully`);
2527
+ };
2528
+ var processDeleteEvent = async (event) => {
2529
+ const { fileId } = event;
2530
+ const file = FilesModel.findByIdIncludingDeleted(fileId);
2531
+ if (!file) {
2532
+ return;
2533
+ }
2534
+ if (file.isDeleted === 1 && file.syncStatus === "synced") {
2535
+ logger.info(`File ${fileId} deletion already synced, skipping`);
2536
+ return;
2537
+ }
2538
+ const payload = {
2539
+ syncStatus: "synced",
2540
+ isDeleted: 1
2541
+ };
2542
+ if (file.onChainFileId !== null || file.onChainFileId !== void 0) {
2543
+ const result = await handleExistingFileOp(fileId, "delete");
2544
+ if (!result.success) {
2545
+ throw new Error(`Publish failed for file ${fileId}`);
2546
+ }
2547
+ payload.onchainVersion = file.localVersion;
2548
+ payload.metadata = result.metadata;
2549
+ payload.isDeleted = 1;
2550
+ }
2551
+ FilesModel.update(fileId, payload, file.portalAddress);
2552
+ logger.info(`File ${fileId} delete event processed (syncStatus set to synced)`);
2553
+ };
2554
+
2555
+ // src/infra/worker/worker.ts
2556
+ var DEFAULT_CONCURRENCY = 5;
2557
+ var STALE_THRESHOLD_MS = 5 * 60 * 1e3;
2558
+ var SIGNAL_RETRY_DELAY_MS = 50;
2559
+ var FALLBACK_POLL_MS = 3e4;
2560
+ var MAX_RETRIES = 10;
2561
+ var FileEventsWorker = class {
2562
+ isRunning = false;
2563
+ concurrency;
2564
+ activeProcessors = /* @__PURE__ */ new Map();
2565
+ signalCleanup = null;
2566
+ pendingSignal = false;
2567
+ wakeResolver = null;
2568
+ constructor(concurrency2 = DEFAULT_CONCURRENCY) {
2569
+ this.concurrency = concurrency2;
2570
+ }
2571
+ start() {
2572
+ if (this.isRunning) {
2573
+ logger.warn("Worker is already running");
2574
+ return;
2575
+ }
2576
+ this.isRunning = true;
2577
+ const staleCount = this.recoverStaleEvents();
2578
+ if (staleCount > 0) {
2579
+ logger.info(`Recovered ${staleCount} stale event(s)`);
2580
+ }
2581
+ this.signalCleanup = onNewEvent(() => {
2582
+ this.pendingSignal = true;
2583
+ this.wakeUp();
2584
+ });
2585
+ logger.debug(`File events worker started (concurrency: ${this.concurrency})`);
2586
+ this.run();
2587
+ }
2588
+ async run() {
2589
+ while (this.isRunning) {
2590
+ const foundEvents = await this.fillSlots();
2591
+ logger.debug(`Found ${foundEvents ? "events" : "no events"} to process`);
2592
+ if (this.activeProcessors.size === 0) {
2593
+ if (this.pendingSignal && !foundEvents) {
2594
+ this.pendingSignal = false;
2595
+ await this.sleep(SIGNAL_RETRY_DELAY_MS);
2596
+ continue;
2597
+ }
2598
+ this.pendingSignal = false;
2599
+ await this.waitForSignalOrTimeout(FALLBACK_POLL_MS);
2600
+ } else {
2601
+ await Promise.race(this.activeProcessors.values());
2602
+ }
2603
+ }
2604
+ }
2605
+ async fillSlots() {
2606
+ let foundAny = false;
2607
+ while (this.activeProcessors.size < this.concurrency && this.isRunning) {
2608
+ const lockedFileIds = Array.from(this.activeProcessors.keys());
2609
+ const event = EventsModel.findNextEligible(lockedFileIds);
2610
+ if (!event) break;
2611
+ foundAny = true;
2612
+ EventsModel.markProcessing(event._id);
2613
+ const processor = this.processEventWrapper(event);
2614
+ this.activeProcessors.set(event.fileId, processor);
2615
+ }
2616
+ logger.debug(`Slots filled: ${this.activeProcessors.size}`);
2617
+ return foundAny;
2618
+ }
2619
+ async processEventWrapper(event) {
2620
+ try {
2621
+ const result = await processEvent(event);
2622
+ if (result.success) {
2623
+ EventsModel.markProcessed(event._id);
2624
+ } else {
2625
+ this.handleFailure(event, result.error);
2626
+ }
2627
+ } catch (err) {
2628
+ this.handleFailure(event, err);
2629
+ } finally {
2630
+ this.activeProcessors.delete(event.fileId);
2631
+ }
2632
+ }
2633
+ handleFailure(event, error) {
2634
+ const errorMsg = error instanceof Error ? error.message : String(error);
2635
+ if (error instanceof RateLimitError) {
2636
+ const retryAfterMs = error.retryAfterSeconds * 1e3;
2637
+ EventsModel.scheduleRetryAfter(event._id, errorMsg, retryAfterMs);
2638
+ logger.warn(`Event ${event._id} rate limited; retry after ${error.retryAfterSeconds}s`);
2639
+ return;
2640
+ }
2641
+ if (event.retryCount < MAX_RETRIES) {
2642
+ EventsModel.scheduleRetry(event._id, errorMsg);
2643
+ logger.warn(`Event ${event._id} failed (retry ${event.retryCount + 1}/${MAX_RETRIES}): ${errorMsg}`);
2644
+ } else {
2645
+ EventsModel.markFailed(event._id, errorMsg);
2646
+ logger.error(`Event ${event._id} permanently failed after ${MAX_RETRIES} retries: ${errorMsg}`);
2647
+ }
2648
+ }
2649
+ recoverStaleEvents() {
2650
+ const staleThreshold = Date.now() - STALE_THRESHOLD_MS;
2651
+ return EventsModel.resetStaleEvents(staleThreshold);
2652
+ }
2653
+ wakeUp() {
2654
+ if (this.wakeResolver) {
2655
+ this.wakeResolver();
2656
+ this.wakeResolver = null;
2657
+ }
2658
+ }
2659
+ waitForSignalOrTimeout(ms) {
2660
+ return new Promise((resolve) => {
2661
+ const timeout = setTimeout(() => {
2662
+ this.wakeResolver = null;
2663
+ resolve();
2664
+ }, ms);
2665
+ this.wakeResolver = () => {
2666
+ clearTimeout(timeout);
2667
+ resolve();
2668
+ };
2669
+ });
2670
+ }
2671
+ sleep(ms) {
2672
+ return new Promise((resolve) => setTimeout(resolve, ms));
2673
+ }
2674
+ async close() {
2675
+ if (!this.isRunning) {
2676
+ return;
2677
+ }
2678
+ logger.info("Closing worker gracefully...");
2679
+ this.isRunning = false;
2680
+ if (this.signalCleanup) {
2681
+ this.signalCleanup();
2682
+ this.signalCleanup = null;
2683
+ }
2684
+ this.wakeUp();
2685
+ this.wakeResolver = null;
2686
+ if (this.activeProcessors.size > 0) {
2687
+ logger.info(`Waiting for ${this.activeProcessors.size} active processor(s) to complete...`);
2688
+ await Promise.all(this.activeProcessors.values());
2689
+ }
2690
+ logger.info("Worker closed");
2691
+ }
2692
+ isActive() {
2693
+ return this.isRunning;
2694
+ }
2695
+ getActiveCount() {
2696
+ return this.activeProcessors.size;
2697
+ }
2698
+ };
2699
+ function createWorker(concurrency2 = DEFAULT_CONCURRENCY) {
2700
+ return new FileEventsWorker(concurrency2);
2701
+ }
2702
+
2703
+ // src/appWorker.ts
2704
+ var DEFAULT_CONCURRENCY2 = 5;
2705
+ var worker = null;
2706
+ function startWorker(concurrency2 = DEFAULT_CONCURRENCY2) {
2707
+ if (worker?.isActive()) {
2708
+ return;
2709
+ }
2710
+ worker = createWorker(concurrency2);
2711
+ worker.start();
2712
+ }
2713
+ async function closeWorker() {
2714
+ if (worker) {
2715
+ await worker.close();
2716
+ worker = null;
2717
+ }
2718
+ }
2719
+ function isWorkerActive() {
2720
+ return worker?.isActive() ?? false;
2721
+ }
2722
+
2723
+ // src/infra/reporter.ts
2724
+ var Reporter = class {
2725
+ async reportError(message) {
2726
+ console.error("Error reported:", message);
2727
+ }
2728
+ };
2729
+ var reporter_default = new Reporter();
2730
+
2731
+ // src/infra/database/migrations/index.ts
2732
+ var STABLE_SCHEMA = `
2733
+ CREATE TABLE IF NOT EXISTS files (
2734
+ _id TEXT PRIMARY KEY,
2735
+ ddocId TEXT NOT NULL,
2736
+ title TEXT NOT NULL,
2737
+ content TEXT NOT NULL,
2738
+ localVersion INTEGER NOT NULL DEFAULT 1,
2739
+ onchainVersion INTEGER NOT NULL DEFAULT 0,
2740
+ syncStatus TEXT NOT NULL DEFAULT 'pending',
2741
+ createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
2742
+ updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
2743
+ isDeleted INTEGER NOT NULL DEFAULT 0,
2744
+ portalAddress TEXT NOT NULL,
2745
+ metadata TEXT DEFAULT '{}',
2746
+ onChainFileId INTEGER,
2747
+ commentKey TEXT,
2748
+ linkKey TEXT,
2749
+ linkKeyNonce TEXT,
2750
+ link TEXT
2751
+ );
2752
+ CREATE INDEX IF NOT EXISTS idx_files_createdAt ON files(createdAt);
2753
+ CREATE INDEX IF NOT EXISTS idx_files_syncStatus ON files(syncStatus);
2754
+ CREATE INDEX IF NOT EXISTS idx_files_title ON files(title);
2755
+ CREATE INDEX IF NOT EXISTS idx_files_portalAddress ON files(portalAddress);
2756
+
2757
+ CREATE TABLE IF NOT EXISTS portals (
2758
+ _id TEXT PRIMARY KEY,
2759
+ portalAddress TEXT NOT NULL UNIQUE,
2760
+ portalSeed TEXT NOT NULL UNIQUE,
2761
+ ownerAddress TEXT NOT NULL,
2762
+ createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
2763
+ updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
2764
+ );
2765
+
2766
+ CREATE TABLE IF NOT EXISTS api_keys (
2767
+ _id TEXT PRIMARY KEY,
2768
+ apiKeySeed TEXT NOT NULL UNIQUE,
2769
+ name TEXT NOT NULL,
2770
+ collaboratorAddress TEXT NOT NULL UNIQUE,
2771
+ portalAddress TEXT NOT NULL,
2772
+ createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
2773
+ isDeleted INTEGER NOT NULL DEFAULT 0
2774
+ );
2775
+
2776
+ CREATE TABLE IF NOT EXISTS events (
2777
+ _id TEXT PRIMARY KEY,
2778
+ type TEXT NOT NULL CHECK (type IN ('create', 'update', 'delete')),
2779
+ timestamp INTEGER NOT NULL,
2780
+ fileId TEXT NOT NULL,
2781
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'processed', 'failed')),
2782
+ retryCount INTEGER NOT NULL DEFAULT 0,
2783
+ lastError TEXT,
2784
+ lockedAt INTEGER,
2785
+ nextRetryAt INTEGER,
2786
+ userOpHash TEXT,
2787
+ pendingPayload TEXT,
2788
+ portalAddress TEXT
2789
+ );
2790
+ CREATE INDEX IF NOT EXISTS idx_events_pending_eligible ON events (status, nextRetryAt, timestamp) WHERE status = 'pending';
2791
+ CREATE INDEX IF NOT EXISTS idx_events_file_pending_ts ON events (fileId, status, timestamp) WHERE status = 'pending';
2792
+ CREATE INDEX IF NOT EXISTS idx_events_processing_locked ON events (status, lockedAt) WHERE status = 'processing';
2793
+ CREATE INDEX IF NOT EXISTS idx_events_failed_portal ON events (portalAddress, status) WHERE status = 'failed';
2794
+
2795
+ CREATE TABLE IF NOT EXISTS folders (
2796
+ _id TEXT PRIMARY KEY,
2797
+ onchainFileId INTEGER NOT NULL,
2798
+ folderId TEXT NOT NULL,
2799
+ folderRef TEXT NOT NULL,
2800
+ folderName TEXT NOT NULL,
2801
+ portalAddress TEXT NOT NULL,
2802
+ metadataIPFSHash TEXT NOT NULL,
2803
+ contentIPFSHash TEXT NOT NULL,
2804
+ isDeleted INTEGER NOT NULL DEFAULT 0,
2805
+ lastTransactionHash TEXT,
2806
+ lastTransactionBlockNumber INTEGER NOT NULL,
2807
+ lastTransactionBlockTimestamp INTEGER NOT NULL,
2808
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
2809
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
2810
+ );
2811
+ CREATE INDEX IF NOT EXISTS idx_folders_folderRef_folderId ON folders(folderRef, folderId);
2812
+ CREATE INDEX IF NOT EXISTS idx_folders_folderRef ON folders(folderRef);
2813
+ CREATE INDEX IF NOT EXISTS idx_folders_created_at ON folders(created_at);
2814
+ `;
2815
+ function runMigrations() {
2816
+ const db = database_default();
2817
+ db.exec(STABLE_SCHEMA);
2818
+ logger.debug("Database schema ready");
2819
+ }
2820
+
2821
+ // src/worker.ts
2822
+ validateDbPath();
2823
+ runMigrations();
2824
+ var concurrency = parseInt(process.env.WORKER_CONCURRENCY || "5", 10);
2825
+ startWorker(concurrency);
2826
+ setTimeout(() => {
2827
+ if (isWorkerActive()) {
2828
+ logger.info("File events worker started and active");
2829
+ return;
2830
+ }
2831
+ logger.error("Worker failed to start");
2832
+ process.exit(1);
2833
+ }, 100);
2834
+ var shutdown = async () => {
2835
+ logger.info("Shutting down worker gracefully...");
2836
+ await closeWorker();
2837
+ process.exit(0);
2838
+ };
2839
+ process.on("SIGTERM", shutdown);
2840
+ process.on("SIGINT", shutdown);
2841
+ /*! Bundled license information:
2842
+
2843
+ @noble/ciphers/utils.js:
2844
+ (*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
2845
+ */
2846
+ //# sourceMappingURL=worker.js.map