@fileverse/api 0.0.2 → 0.0.3
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/cli/index.js +94 -287
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/index.js +192 -356
- package/dist/commands/index.js.map +1 -1
- package/dist/index.js +255 -428
- package/dist/index.js.map +1 -1
- package/dist/worker.js +196 -367
- package/dist/worker.js.map +1 -1
- package/package.json +1 -3
package/dist/worker.js
CHANGED
|
@@ -43,9 +43,6 @@ function getRuntimeConfig() {
|
|
|
43
43
|
get DB_PATH() {
|
|
44
44
|
return process.env.DB_PATH;
|
|
45
45
|
},
|
|
46
|
-
get DATABASE_URL() {
|
|
47
|
-
return process.env.DATABASE_URL;
|
|
48
|
-
},
|
|
49
46
|
get PORT() {
|
|
50
47
|
return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;
|
|
51
48
|
},
|
|
@@ -57,19 +54,16 @@ function getRuntimeConfig() {
|
|
|
57
54
|
}
|
|
58
55
|
};
|
|
59
56
|
}
|
|
60
|
-
function
|
|
57
|
+
function validateDbPath() {
|
|
61
58
|
const dbPath = process.env.DB_PATH;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.error("
|
|
65
|
-
console.error("Please set DB_PATH or DATABASE_URL in your .env file (config/.env or ~/.fileverse/.env) or run the CLI first");
|
|
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");
|
|
66
62
|
process.exit(1);
|
|
67
63
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
fs.mkdirSync(dbDir, { recursive: true });
|
|
72
|
-
}
|
|
64
|
+
const dbDir = path.dirname(dbPath.trim());
|
|
65
|
+
if (!fs.existsSync(dbDir)) {
|
|
66
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
73
67
|
}
|
|
74
68
|
}
|
|
75
69
|
var config = {
|
|
@@ -98,9 +92,6 @@ var config = {
|
|
|
98
92
|
get DB_PATH() {
|
|
99
93
|
return process.env.DB_PATH;
|
|
100
94
|
},
|
|
101
|
-
get DATABASE_URL() {
|
|
102
|
-
return process.env.DATABASE_URL;
|
|
103
|
-
},
|
|
104
95
|
get PORT() {
|
|
105
96
|
return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;
|
|
106
97
|
},
|
|
@@ -187,231 +178,71 @@ var logger = {
|
|
|
187
178
|
child: pinoInstance.child.bind(pinoInstance)
|
|
188
179
|
};
|
|
189
180
|
|
|
190
|
-
// src/infra/database/
|
|
181
|
+
// src/infra/database/connection.ts
|
|
191
182
|
import Database from "better-sqlite3";
|
|
192
|
-
var
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this.db.prepare("SELECT 1").get();
|
|
201
|
-
logger.info(`SQLite database connected: ${dbPath}`);
|
|
202
|
-
}
|
|
203
|
-
async select(sql, params = []) {
|
|
204
|
-
const stmt = this.db.prepare(sql);
|
|
205
|
-
return stmt.all(params);
|
|
206
|
-
}
|
|
207
|
-
async selectOne(sql, params = []) {
|
|
208
|
-
const stmt = this.db.prepare(sql);
|
|
209
|
-
return stmt.get(params);
|
|
210
|
-
}
|
|
211
|
-
async execute(sql, params = []) {
|
|
212
|
-
const stmt = this.db.prepare(sql);
|
|
213
|
-
const result = stmt.run(params);
|
|
214
|
-
return {
|
|
215
|
-
changes: result.changes,
|
|
216
|
-
lastInsertRowid: result.lastInsertRowid
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
async transaction(callback) {
|
|
220
|
-
this.db.exec("SAVEPOINT txn");
|
|
221
|
-
try {
|
|
222
|
-
const result = await callback(this);
|
|
223
|
-
this.db.exec("RELEASE txn");
|
|
224
|
-
return result;
|
|
225
|
-
} catch (err) {
|
|
226
|
-
this.db.exec("ROLLBACK TO txn");
|
|
227
|
-
throw err;
|
|
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();
|
|
228
191
|
}
|
|
192
|
+
return _DatabaseConnectionManager.instance;
|
|
229
193
|
}
|
|
230
|
-
|
|
231
|
-
this.db
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
import pg from "pg";
|
|
241
|
-
var { Pool } = pg;
|
|
242
|
-
var COLUMN_NAME_MAP = {
|
|
243
|
-
ddocid: "ddocId",
|
|
244
|
-
localversion: "localVersion",
|
|
245
|
-
onchainversion: "onchainVersion",
|
|
246
|
-
syncstatus: "syncStatus",
|
|
247
|
-
isdeleted: "isDeleted",
|
|
248
|
-
onchainfileid: "onChainFileId",
|
|
249
|
-
portaladdress: "portalAddress",
|
|
250
|
-
createdat: "createdAt",
|
|
251
|
-
updatedat: "updatedAt",
|
|
252
|
-
linkkey: "linkKey",
|
|
253
|
-
linkkeynonce: "linkKeyNonce",
|
|
254
|
-
commentkey: "commentKey",
|
|
255
|
-
portalseed: "portalSeed",
|
|
256
|
-
owneraddress: "ownerAddress",
|
|
257
|
-
apikeyseed: "apiKeySeed",
|
|
258
|
-
collaboratoraddress: "collaboratorAddress",
|
|
259
|
-
fileid: "fileId",
|
|
260
|
-
retrycount: "retryCount",
|
|
261
|
-
lasterror: "lastError",
|
|
262
|
-
lockedat: "lockedAt",
|
|
263
|
-
nextretryat: "nextRetryAt",
|
|
264
|
-
userophash: "userOpHash",
|
|
265
|
-
pendingpayload: "pendingPayload",
|
|
266
|
-
folderid: "folderId",
|
|
267
|
-
folderref: "folderRef",
|
|
268
|
-
foldername: "folderName",
|
|
269
|
-
metadataipfshash: "metadataIPFSHash",
|
|
270
|
-
contentipfshash: "contentIPFSHash",
|
|
271
|
-
lasttransactionhash: "lastTransactionHash",
|
|
272
|
-
lasttransactionblocknumber: "lastTransactionBlockNumber",
|
|
273
|
-
lasttransactionblocktimestamp: "lastTransactionBlockTimestamp",
|
|
274
|
-
created_at: "created_at",
|
|
275
|
-
updated_at: "updated_at"
|
|
276
|
-
};
|
|
277
|
-
function translateParams(sql) {
|
|
278
|
-
let idx = 0;
|
|
279
|
-
return sql.replace(/\?/g, () => `$${++idx}`);
|
|
280
|
-
}
|
|
281
|
-
function mapRow(row) {
|
|
282
|
-
const mapped = {};
|
|
283
|
-
for (const [key, value] of Object.entries(row)) {
|
|
284
|
-
const mappedKey = COLUMN_NAME_MAP[key] ?? key;
|
|
285
|
-
mapped[mappedKey] = value instanceof Date ? value.toISOString() : value;
|
|
286
|
-
}
|
|
287
|
-
return mapped;
|
|
288
|
-
}
|
|
289
|
-
var PgClientAdapter = class {
|
|
290
|
-
constructor(client) {
|
|
291
|
-
this.client = client;
|
|
292
|
-
}
|
|
293
|
-
async select(sql, params = []) {
|
|
294
|
-
const result = await this.client.query(translateParams(sql), params);
|
|
295
|
-
return result.rows.map((row) => mapRow(row));
|
|
296
|
-
}
|
|
297
|
-
async selectOne(sql, params = []) {
|
|
298
|
-
const result = await this.client.query(translateParams(sql), params);
|
|
299
|
-
return result.rows[0] ? mapRow(result.rows[0]) : void 0;
|
|
300
|
-
}
|
|
301
|
-
async execute(sql, params = []) {
|
|
302
|
-
const result = await this.client.query(translateParams(sql), params);
|
|
303
|
-
return { changes: result.rowCount ?? 0, lastInsertRowid: 0 };
|
|
304
|
-
}
|
|
305
|
-
async transaction(callback) {
|
|
306
|
-
await this.client.query("SAVEPOINT nested_txn");
|
|
307
|
-
try {
|
|
308
|
-
const result = await callback(this);
|
|
309
|
-
await this.client.query("RELEASE SAVEPOINT nested_txn");
|
|
310
|
-
return result;
|
|
311
|
-
} catch (err) {
|
|
312
|
-
await this.client.query("ROLLBACK TO SAVEPOINT nested_txn");
|
|
313
|
-
throw err;
|
|
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}`);
|
|
314
204
|
}
|
|
315
|
-
|
|
316
|
-
async exec(sql) {
|
|
317
|
-
await this.client.query(sql);
|
|
205
|
+
return this.db;
|
|
318
206
|
}
|
|
319
207
|
async close() {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
constructor(connectionString) {
|
|
325
|
-
const url = new URL(connectionString);
|
|
326
|
-
const isLocal = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
327
|
-
const sslDisabled = connectionString.includes("sslmode=disable");
|
|
328
|
-
const needsSsl = !isLocal && !sslDisabled;
|
|
329
|
-
this.pool = new Pool({
|
|
330
|
-
connectionString,
|
|
331
|
-
// pg requires password to be a string; local trust/peer auth uses empty string
|
|
332
|
-
password: url.password || "",
|
|
333
|
-
max: 20,
|
|
334
|
-
idleTimeoutMillis: 3e4,
|
|
335
|
-
ssl: needsSsl ? { rejectUnauthorized: false } : void 0
|
|
336
|
-
});
|
|
337
|
-
logger.info(`PostgreSQL pool created (ssl: ${needsSsl ? "on" : "off"})`);
|
|
338
|
-
}
|
|
339
|
-
async select(sql, params = []) {
|
|
340
|
-
const result = await this.pool.query(translateParams(sql), params);
|
|
341
|
-
return result.rows.map((row) => mapRow(row));
|
|
342
|
-
}
|
|
343
|
-
async selectOne(sql, params = []) {
|
|
344
|
-
const result = await this.pool.query(translateParams(sql), params);
|
|
345
|
-
return result.rows[0] ? mapRow(result.rows[0]) : void 0;
|
|
346
|
-
}
|
|
347
|
-
async execute(sql, params = []) {
|
|
348
|
-
const result = await this.pool.query(translateParams(sql), params);
|
|
349
|
-
return { changes: result.rowCount ?? 0, lastInsertRowid: 0 };
|
|
350
|
-
}
|
|
351
|
-
async transaction(callback) {
|
|
352
|
-
const client = await this.pool.connect();
|
|
353
|
-
try {
|
|
354
|
-
await client.query("BEGIN");
|
|
355
|
-
const clientAdapter = new PgClientAdapter(client);
|
|
356
|
-
const result = await callback(clientAdapter);
|
|
357
|
-
await client.query("COMMIT");
|
|
358
|
-
return result;
|
|
359
|
-
} catch (err) {
|
|
360
|
-
await client.query("ROLLBACK");
|
|
361
|
-
throw err;
|
|
362
|
-
} finally {
|
|
363
|
-
client.release();
|
|
208
|
+
if (this.db) {
|
|
209
|
+
this.db.close();
|
|
210
|
+
this.db = null;
|
|
211
|
+
logger.info("Database connection closed");
|
|
364
212
|
}
|
|
365
213
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
async close() {
|
|
370
|
-
await this.pool.end();
|
|
371
|
-
logger.info("PostgreSQL pool closed");
|
|
214
|
+
isConnected() {
|
|
215
|
+
return this.db !== null && this.db.open;
|
|
372
216
|
}
|
|
373
217
|
};
|
|
374
|
-
|
|
375
|
-
// src/infra/database/connection.ts
|
|
376
|
-
var adapter = null;
|
|
377
|
-
async function getAdapter() {
|
|
378
|
-
if (adapter) return adapter;
|
|
379
|
-
const databaseUrl = config.DATABASE_URL;
|
|
380
|
-
const dbPath = config.DB_PATH;
|
|
381
|
-
if (databaseUrl) {
|
|
382
|
-
adapter = new PgAdapter(databaseUrl);
|
|
383
|
-
} else if (dbPath) {
|
|
384
|
-
adapter = new SqliteAdapter(dbPath);
|
|
385
|
-
} else {
|
|
386
|
-
throw new Error("Either DATABASE_URL or DB_PATH must be set");
|
|
387
|
-
}
|
|
388
|
-
return adapter;
|
|
389
|
-
}
|
|
390
|
-
function getAdapterSync() {
|
|
391
|
-
if (!adapter) {
|
|
392
|
-
throw new Error(
|
|
393
|
-
"Database adapter not initialized. Call getAdapter() at startup first."
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
return adapter;
|
|
397
|
-
}
|
|
218
|
+
var databaseConnectionManager = DatabaseConnectionManager.getInstance();
|
|
398
219
|
|
|
399
220
|
// src/domain/file/constants.ts
|
|
400
221
|
var DEFAULT_LIST_LIMIT = 10;
|
|
401
222
|
|
|
402
223
|
// src/infra/database/query-builder.ts
|
|
224
|
+
function getDb() {
|
|
225
|
+
return databaseConnectionManager.getConnection();
|
|
226
|
+
}
|
|
403
227
|
var QueryBuilder = class {
|
|
404
|
-
static
|
|
405
|
-
|
|
228
|
+
static select(sql, params = []) {
|
|
229
|
+
const stmt = getDb().prepare(sql);
|
|
230
|
+
return stmt.all(params);
|
|
406
231
|
}
|
|
407
|
-
static
|
|
408
|
-
|
|
232
|
+
static selectOne(sql, params = []) {
|
|
233
|
+
const stmt = getDb().prepare(sql);
|
|
234
|
+
return stmt.get(params);
|
|
409
235
|
}
|
|
410
|
-
static
|
|
411
|
-
|
|
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
|
+
};
|
|
412
243
|
}
|
|
413
|
-
static
|
|
414
|
-
return
|
|
244
|
+
static transaction(callback) {
|
|
245
|
+
return getDb().transaction(callback)();
|
|
415
246
|
}
|
|
416
247
|
static paginate(sql, options = {}) {
|
|
417
248
|
let query = sql;
|
|
@@ -430,6 +261,12 @@ var QueryBuilder = class {
|
|
|
430
261
|
}
|
|
431
262
|
};
|
|
432
263
|
|
|
264
|
+
// src/infra/database/index.ts
|
|
265
|
+
function getDb2() {
|
|
266
|
+
return databaseConnectionManager.getConnection();
|
|
267
|
+
}
|
|
268
|
+
var database_default = getDb2;
|
|
269
|
+
|
|
433
270
|
// src/infra/database/models/files.model.ts
|
|
434
271
|
import { uuidv7 } from "uuidv7";
|
|
435
272
|
var FilesModel = class {
|
|
@@ -463,15 +300,15 @@ var FilesModel = class {
|
|
|
463
300
|
link: fileRaw.link
|
|
464
301
|
};
|
|
465
302
|
}
|
|
466
|
-
static
|
|
303
|
+
static findAll(portalAddress, limit, skip) {
|
|
467
304
|
const whereClause = "isDeleted = 0 AND portalAddress = ?";
|
|
468
305
|
const params = [portalAddress];
|
|
469
306
|
const countSql = `
|
|
470
|
-
SELECT COUNT(*) as count
|
|
471
|
-
FROM ${this.TABLE}
|
|
307
|
+
SELECT COUNT(*) as count
|
|
308
|
+
FROM ${this.TABLE}
|
|
472
309
|
WHERE ${whereClause}
|
|
473
310
|
`;
|
|
474
|
-
const totalResult =
|
|
311
|
+
const totalResult = QueryBuilder.selectOne(countSql, params);
|
|
475
312
|
const total = totalResult?.count || 0;
|
|
476
313
|
const sql = `
|
|
477
314
|
SELECT *
|
|
@@ -484,51 +321,51 @@ var FilesModel = class {
|
|
|
484
321
|
orderBy: "createdAt",
|
|
485
322
|
orderDirection: "DESC"
|
|
486
323
|
});
|
|
487
|
-
const filesRaw =
|
|
324
|
+
const filesRaw = QueryBuilder.select(completeSql, params);
|
|
488
325
|
const files = filesRaw.map(this.parseFile);
|
|
489
326
|
const hasNext = skip !== void 0 && limit !== void 0 ? skip + limit < total : false;
|
|
490
327
|
return { files, total, hasNext };
|
|
491
328
|
}
|
|
492
|
-
static
|
|
329
|
+
static findById(_id, portalAddress) {
|
|
493
330
|
const sql = `
|
|
494
331
|
SELECT *
|
|
495
|
-
FROM ${this.TABLE}
|
|
332
|
+
FROM ${this.TABLE}
|
|
496
333
|
WHERE _id = ? AND isDeleted = 0 AND portalAddress = ?
|
|
497
334
|
`;
|
|
498
|
-
const result =
|
|
335
|
+
const result = QueryBuilder.selectOne(sql, [_id, portalAddress]);
|
|
499
336
|
return result ? this.parseFile(result) : void 0;
|
|
500
337
|
}
|
|
501
|
-
static
|
|
338
|
+
static findByIdIncludingDeleted(_id) {
|
|
502
339
|
const sql = `
|
|
503
340
|
SELECT *
|
|
504
|
-
FROM ${this.TABLE}
|
|
341
|
+
FROM ${this.TABLE}
|
|
505
342
|
WHERE _id = ?
|
|
506
343
|
`;
|
|
507
|
-
const result =
|
|
344
|
+
const result = QueryBuilder.selectOne(sql, [_id]);
|
|
508
345
|
return result ? this.parseFile(result) : void 0;
|
|
509
346
|
}
|
|
510
|
-
static
|
|
347
|
+
static findByIdExcludingDeleted(_id) {
|
|
511
348
|
const sql = `
|
|
512
349
|
SELECT *
|
|
513
|
-
FROM ${this.TABLE}
|
|
350
|
+
FROM ${this.TABLE}
|
|
514
351
|
WHERE _id = ? AND isDeleted = 0
|
|
515
352
|
`;
|
|
516
|
-
const result =
|
|
353
|
+
const result = QueryBuilder.selectOne(sql, [_id]);
|
|
517
354
|
return result ? this.parseFile(result) : void 0;
|
|
518
355
|
}
|
|
519
|
-
static
|
|
356
|
+
static findByDDocId(ddocId, portalAddress) {
|
|
520
357
|
const sql = `
|
|
521
358
|
SELECT *
|
|
522
|
-
FROM ${this.TABLE}
|
|
359
|
+
FROM ${this.TABLE}
|
|
523
360
|
WHERE ddocId = ? AND isDeleted = 0 AND portalAddress = ?
|
|
524
361
|
`;
|
|
525
|
-
const result =
|
|
362
|
+
const result = QueryBuilder.selectOne(sql, [ddocId, portalAddress]);
|
|
526
363
|
return result ? this.parseFile(result) : void 0;
|
|
527
364
|
}
|
|
528
|
-
static
|
|
365
|
+
static searchByTitle(searchTerm, portalAddress, limit, skip) {
|
|
529
366
|
const sql = `
|
|
530
367
|
SELECT *
|
|
531
|
-
FROM ${this.TABLE}
|
|
368
|
+
FROM ${this.TABLE}
|
|
532
369
|
WHERE LOWER(title) LIKE LOWER(?) AND isDeleted = 0 AND portalAddress = ?
|
|
533
370
|
`;
|
|
534
371
|
const completeSql = QueryBuilder.paginate(sql, {
|
|
@@ -537,24 +374,24 @@ var FilesModel = class {
|
|
|
537
374
|
orderBy: "createdAt",
|
|
538
375
|
orderDirection: "DESC"
|
|
539
376
|
});
|
|
540
|
-
const filesRaw =
|
|
377
|
+
const filesRaw = QueryBuilder.select(completeSql, [`%${searchTerm}%`, portalAddress]);
|
|
541
378
|
return filesRaw.map(this.parseFile);
|
|
542
379
|
}
|
|
543
|
-
static
|
|
380
|
+
static create(input) {
|
|
544
381
|
const _id = uuidv7();
|
|
545
382
|
const sql = `
|
|
546
|
-
INSERT INTO ${this.TABLE}
|
|
547
|
-
(_id, title, content, ddocId, portalAddress)
|
|
383
|
+
INSERT INTO ${this.TABLE}
|
|
384
|
+
(_id, title, content, ddocId, portalAddress)
|
|
548
385
|
VALUES (?, ?, ?, ?, ?)
|
|
549
386
|
`;
|
|
550
|
-
|
|
551
|
-
const created =
|
|
387
|
+
QueryBuilder.execute(sql, [_id, input.title, input.content, input.ddocId, input.portalAddress]);
|
|
388
|
+
const created = this.findById(_id, input.portalAddress);
|
|
552
389
|
if (!created) {
|
|
553
390
|
throw new Error("Failed to create file");
|
|
554
391
|
}
|
|
555
392
|
return created;
|
|
556
393
|
}
|
|
557
|
-
static
|
|
394
|
+
static update(_id, payload, portalAddress) {
|
|
558
395
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
559
396
|
const keys = [];
|
|
560
397
|
const values = [];
|
|
@@ -573,22 +410,22 @@ var FilesModel = class {
|
|
|
573
410
|
values.push(now, _id, portalAddress);
|
|
574
411
|
const updateChain = keys.join(", ");
|
|
575
412
|
const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE _id = ? AND portalAddress = ?`;
|
|
576
|
-
|
|
577
|
-
const updated =
|
|
413
|
+
QueryBuilder.execute(sql, values);
|
|
414
|
+
const updated = this.findById(_id, portalAddress);
|
|
578
415
|
if (!updated) {
|
|
579
416
|
throw new Error("Failed to update file");
|
|
580
417
|
}
|
|
581
418
|
return updated;
|
|
582
419
|
}
|
|
583
|
-
static
|
|
420
|
+
static softDelete(_id) {
|
|
584
421
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
585
422
|
const sql = `
|
|
586
|
-
UPDATE ${this.TABLE}
|
|
423
|
+
UPDATE ${this.TABLE}
|
|
587
424
|
SET isDeleted = 1, syncStatus = 'pending', updatedAt = ?
|
|
588
425
|
WHERE _id = ?
|
|
589
426
|
`;
|
|
590
|
-
|
|
591
|
-
const deleted =
|
|
427
|
+
QueryBuilder.execute(sql, [now, _id]);
|
|
428
|
+
const deleted = this.findByIdIncludingDeleted(_id);
|
|
592
429
|
if (!deleted) {
|
|
593
430
|
throw new Error("Failed to delete file");
|
|
594
431
|
}
|
|
@@ -600,22 +437,22 @@ var FilesModel = class {
|
|
|
600
437
|
import { uuidv7 as uuidv72 } from "uuidv7";
|
|
601
438
|
var PortalsModel = class {
|
|
602
439
|
static TABLE = "portals";
|
|
603
|
-
static
|
|
440
|
+
static findByPortalAddress(portalAddress) {
|
|
604
441
|
const sql = `SELECT _id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt FROM ${this.TABLE} WHERE portalAddress = ?`;
|
|
605
442
|
return QueryBuilder.selectOne(sql, [portalAddress]);
|
|
606
443
|
}
|
|
607
|
-
static
|
|
444
|
+
static create(input) {
|
|
608
445
|
const _id = uuidv72();
|
|
609
446
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
610
447
|
const sql = `INSERT INTO ${this.TABLE} (_id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)`;
|
|
611
|
-
|
|
612
|
-
const created =
|
|
448
|
+
QueryBuilder.execute(sql, [_id, input.portalAddress, input.portalSeed, input.ownerAddress, now, now]);
|
|
449
|
+
const created = this.findByPortalAddress(input.portalAddress);
|
|
613
450
|
if (!created) {
|
|
614
451
|
throw new Error("Failed to create portal");
|
|
615
452
|
}
|
|
616
453
|
return created;
|
|
617
454
|
}
|
|
618
|
-
static
|
|
455
|
+
static update(portalAddress, input) {
|
|
619
456
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
620
457
|
const keys = [];
|
|
621
458
|
const values = [];
|
|
@@ -630,15 +467,15 @@ var PortalsModel = class {
|
|
|
630
467
|
const updateChain = keys.join(", ");
|
|
631
468
|
const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE portalAddress = ?`;
|
|
632
469
|
values.push(portalAddress);
|
|
633
|
-
|
|
634
|
-
const updated =
|
|
470
|
+
QueryBuilder.execute(sql, values);
|
|
471
|
+
const updated = this.findByPortalAddress(portalAddress);
|
|
635
472
|
if (!updated) {
|
|
636
473
|
throw new Error("Failed to update portal");
|
|
637
474
|
}
|
|
638
475
|
return updated;
|
|
639
476
|
}
|
|
640
|
-
static
|
|
641
|
-
const existing =
|
|
477
|
+
static upsert(input) {
|
|
478
|
+
const existing = this.findByPortalAddress(input.portalAddress);
|
|
642
479
|
if (existing) {
|
|
643
480
|
return this.update(input.portalAddress, {
|
|
644
481
|
portalSeed: input.portalSeed,
|
|
@@ -673,16 +510,16 @@ function onNewEvent(callback) {
|
|
|
673
510
|
var RETRY_DELAYS_MS = [5e3, 3e4, 12e4];
|
|
674
511
|
var EventsModel = class {
|
|
675
512
|
static TABLE = "events";
|
|
676
|
-
static
|
|
513
|
+
static create(input) {
|
|
677
514
|
const _id = uuidv74();
|
|
678
515
|
const timestamp = Date.now();
|
|
679
516
|
const status = "pending";
|
|
680
517
|
const sql = `
|
|
681
|
-
INSERT INTO ${this.TABLE}
|
|
682
|
-
(_id, type, timestamp, fileId, portalAddress, status, retryCount, lastError, lockedAt, nextRetryAt)
|
|
518
|
+
INSERT INTO ${this.TABLE}
|
|
519
|
+
(_id, type, timestamp, fileId, portalAddress, status, retryCount, lastError, lockedAt, nextRetryAt)
|
|
683
520
|
VALUES (?, ?, ?, ?, ?, ?, 0, NULL, NULL, NULL)
|
|
684
521
|
`;
|
|
685
|
-
|
|
522
|
+
QueryBuilder.execute(sql, [_id, input.type, timestamp, input.fileId, input.portalAddress, status]);
|
|
686
523
|
notifyNewEvent();
|
|
687
524
|
return {
|
|
688
525
|
_id,
|
|
@@ -697,22 +534,22 @@ var EventsModel = class {
|
|
|
697
534
|
nextRetryAt: null
|
|
698
535
|
};
|
|
699
536
|
}
|
|
700
|
-
static
|
|
537
|
+
static findById(_id) {
|
|
701
538
|
const sql = `SELECT * FROM ${this.TABLE} WHERE _id = ?`;
|
|
702
|
-
const row =
|
|
539
|
+
const row = QueryBuilder.selectOne(sql, [_id]);
|
|
703
540
|
return row ? this.parseEvent(row) : void 0;
|
|
704
541
|
}
|
|
705
|
-
static
|
|
542
|
+
static findNextPending() {
|
|
706
543
|
const sql = `
|
|
707
544
|
SELECT * FROM ${this.TABLE}
|
|
708
545
|
WHERE status = 'pending'
|
|
709
546
|
ORDER BY timestamp ASC
|
|
710
547
|
LIMIT 1
|
|
711
548
|
`;
|
|
712
|
-
const row =
|
|
549
|
+
const row = QueryBuilder.selectOne(sql, []);
|
|
713
550
|
return row ? this.parseEvent(row) : void 0;
|
|
714
551
|
}
|
|
715
|
-
static
|
|
552
|
+
static findNextEligible(lockedFileIds) {
|
|
716
553
|
const now = Date.now();
|
|
717
554
|
const exclusionClause = lockedFileIds.length > 0 ? `AND e1.fileId NOT IN (${lockedFileIds.map(() => "?").join(", ")})` : "";
|
|
718
555
|
const sql = `
|
|
@@ -730,29 +567,29 @@ var EventsModel = class {
|
|
|
730
567
|
LIMIT 1
|
|
731
568
|
`;
|
|
732
569
|
const params = [now, ...lockedFileIds];
|
|
733
|
-
const row =
|
|
570
|
+
const row = QueryBuilder.selectOne(sql, params);
|
|
734
571
|
return row ? this.parseEvent(row) : void 0;
|
|
735
572
|
}
|
|
736
|
-
static
|
|
573
|
+
static markProcessing(_id) {
|
|
737
574
|
const sql = `
|
|
738
575
|
UPDATE ${this.TABLE}
|
|
739
576
|
SET status = 'processing',
|
|
740
577
|
lockedAt = ?
|
|
741
578
|
WHERE _id = ?
|
|
742
579
|
`;
|
|
743
|
-
|
|
580
|
+
QueryBuilder.execute(sql, [Date.now(), _id]);
|
|
744
581
|
}
|
|
745
|
-
static
|
|
582
|
+
static markProcessed(_id) {
|
|
746
583
|
const sql = `
|
|
747
584
|
UPDATE ${this.TABLE}
|
|
748
585
|
SET status = 'processed',
|
|
749
586
|
lockedAt = NULL
|
|
750
587
|
WHERE _id = ?
|
|
751
588
|
`;
|
|
752
|
-
|
|
589
|
+
QueryBuilder.execute(sql, [_id]);
|
|
753
590
|
}
|
|
754
|
-
static
|
|
755
|
-
const event =
|
|
591
|
+
static scheduleRetry(_id, errorMsg) {
|
|
592
|
+
const event = this.findById(_id);
|
|
756
593
|
if (!event) return;
|
|
757
594
|
const delay = RETRY_DELAYS_MS[Math.min(event.retryCount, RETRY_DELAYS_MS.length - 1)];
|
|
758
595
|
const nextRetryAt = Date.now() + delay;
|
|
@@ -765,9 +602,9 @@ var EventsModel = class {
|
|
|
765
602
|
lockedAt = NULL
|
|
766
603
|
WHERE _id = ?
|
|
767
604
|
`;
|
|
768
|
-
|
|
605
|
+
QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);
|
|
769
606
|
}
|
|
770
|
-
static
|
|
607
|
+
static scheduleRetryAfter(_id, errorMsg, retryAfterMs) {
|
|
771
608
|
const nextRetryAt = Date.now() + retryAfterMs;
|
|
772
609
|
const sql = `
|
|
773
610
|
UPDATE ${this.TABLE}
|
|
@@ -777,9 +614,9 @@ var EventsModel = class {
|
|
|
777
614
|
lockedAt = NULL
|
|
778
615
|
WHERE _id = ?
|
|
779
616
|
`;
|
|
780
|
-
|
|
617
|
+
QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);
|
|
781
618
|
}
|
|
782
|
-
static
|
|
619
|
+
static markFailed(_id, errorMsg) {
|
|
783
620
|
const sql = `
|
|
784
621
|
UPDATE ${this.TABLE}
|
|
785
622
|
SET status = 'failed',
|
|
@@ -787,9 +624,9 @@ var EventsModel = class {
|
|
|
787
624
|
lockedAt = NULL
|
|
788
625
|
WHERE _id = ?
|
|
789
626
|
`;
|
|
790
|
-
|
|
627
|
+
QueryBuilder.execute(sql, [errorMsg, _id]);
|
|
791
628
|
}
|
|
792
|
-
static
|
|
629
|
+
static listFailed(portalAddress) {
|
|
793
630
|
const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
|
|
794
631
|
const sql = `
|
|
795
632
|
SELECT * FROM ${this.TABLE}
|
|
@@ -798,10 +635,10 @@ var EventsModel = class {
|
|
|
798
635
|
ORDER BY timestamp ASC
|
|
799
636
|
`;
|
|
800
637
|
const params = portalAddress != null ? [portalAddress] : [];
|
|
801
|
-
const rows =
|
|
638
|
+
const rows = QueryBuilder.select(sql, params);
|
|
802
639
|
return rows.map((row) => this.parseEvent(row));
|
|
803
640
|
}
|
|
804
|
-
static
|
|
641
|
+
static resetFailedToPending(_id, portalAddress) {
|
|
805
642
|
const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
|
|
806
643
|
const sql = `
|
|
807
644
|
UPDATE ${this.TABLE}
|
|
@@ -815,13 +652,13 @@ var EventsModel = class {
|
|
|
815
652
|
${portalClause}
|
|
816
653
|
`;
|
|
817
654
|
const params = portalAddress != null ? [_id, portalAddress] : [_id];
|
|
818
|
-
const result =
|
|
655
|
+
const result = QueryBuilder.execute(sql, params);
|
|
819
656
|
if (result.changes > 0) {
|
|
820
657
|
notifyNewEvent();
|
|
821
658
|
}
|
|
822
659
|
return result.changes > 0;
|
|
823
660
|
}
|
|
824
|
-
static
|
|
661
|
+
static resetAllFailedToPending(portalAddress) {
|
|
825
662
|
const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
|
|
826
663
|
const sql = `
|
|
827
664
|
UPDATE ${this.TABLE}
|
|
@@ -834,13 +671,13 @@ var EventsModel = class {
|
|
|
834
671
|
${portalClause}
|
|
835
672
|
`;
|
|
836
673
|
const params = portalAddress != null ? [portalAddress] : [];
|
|
837
|
-
const result =
|
|
674
|
+
const result = QueryBuilder.execute(sql, params);
|
|
838
675
|
if (result.changes > 0) {
|
|
839
676
|
notifyNewEvent();
|
|
840
677
|
}
|
|
841
678
|
return result.changes;
|
|
842
679
|
}
|
|
843
|
-
static
|
|
680
|
+
static resetStaleEvents(staleThreshold) {
|
|
844
681
|
const sql = `
|
|
845
682
|
UPDATE ${this.TABLE}
|
|
846
683
|
SET status = 'pending',
|
|
@@ -851,16 +688,16 @@ var EventsModel = class {
|
|
|
851
688
|
AND lockedAt IS NOT NULL
|
|
852
689
|
AND lockedAt < ?
|
|
853
690
|
`;
|
|
854
|
-
const result =
|
|
691
|
+
const result = QueryBuilder.execute(sql, [staleThreshold]);
|
|
855
692
|
return result.changes;
|
|
856
693
|
}
|
|
857
|
-
static
|
|
694
|
+
static setEventPendingOp(_id, userOpHash, payload) {
|
|
858
695
|
const sql = `UPDATE ${this.TABLE} SET userOpHash = ?, pendingPayload = ? WHERE _id = ?`;
|
|
859
|
-
|
|
696
|
+
QueryBuilder.execute(sql, [userOpHash, JSON.stringify(payload), _id]);
|
|
860
697
|
}
|
|
861
|
-
static
|
|
698
|
+
static clearEventPendingOp(_id) {
|
|
862
699
|
const sql = `UPDATE ${this.TABLE} SET userOpHash = NULL, pendingPayload = NULL WHERE _id = ?`;
|
|
863
|
-
|
|
700
|
+
QueryBuilder.execute(sql, [_id]);
|
|
864
701
|
}
|
|
865
702
|
static parseEvent(row) {
|
|
866
703
|
return {
|
|
@@ -2448,12 +2285,12 @@ var FileManager = class {
|
|
|
2448
2285
|
};
|
|
2449
2286
|
|
|
2450
2287
|
// src/domain/portal/publish.ts
|
|
2451
|
-
|
|
2452
|
-
const file =
|
|
2288
|
+
function getPortalData(fileId) {
|
|
2289
|
+
const file = FilesModel.findByIdIncludingDeleted(fileId);
|
|
2453
2290
|
if (!file) {
|
|
2454
2291
|
throw new Error(`File with _id ${fileId} not found`);
|
|
2455
2292
|
}
|
|
2456
|
-
const portalDetails =
|
|
2293
|
+
const portalDetails = PortalsModel.findByPortalAddress(file.portalAddress);
|
|
2457
2294
|
if (!portalDetails) {
|
|
2458
2295
|
throw new Error(`Portal with address ${file.portalAddress} not found`);
|
|
2459
2296
|
}
|
|
@@ -2493,7 +2330,7 @@ var executeOperation = async (fileManager, file, operation) => {
|
|
|
2493
2330
|
};
|
|
2494
2331
|
var handleExistingFileOp = async (fileId, operation) => {
|
|
2495
2332
|
try {
|
|
2496
|
-
const { file, portalDetails, apiKey } =
|
|
2333
|
+
const { file, portalDetails, apiKey } = getPortalData(fileId);
|
|
2497
2334
|
const apiKeySeed = toUint8Array3(apiKey);
|
|
2498
2335
|
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2499
2336
|
const fileManager = await createFileManager(
|
|
@@ -2509,7 +2346,7 @@ var handleExistingFileOp = async (fileId, operation) => {
|
|
|
2509
2346
|
}
|
|
2510
2347
|
};
|
|
2511
2348
|
var handleNewFileOp = async (fileId) => {
|
|
2512
|
-
const { file, portalDetails, apiKey } =
|
|
2349
|
+
const { file, portalDetails, apiKey } = getPortalData(fileId);
|
|
2513
2350
|
const apiKeySeed = toUint8Array3(apiKey);
|
|
2514
2351
|
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2515
2352
|
const fileManager = await createFileManager(
|
|
@@ -2521,7 +2358,7 @@ var handleNewFileOp = async (fileId) => {
|
|
|
2521
2358
|
return fileManager.submitAddFileTrx(file);
|
|
2522
2359
|
};
|
|
2523
2360
|
var getProxyAuthParams = async (fileId) => {
|
|
2524
|
-
const { portalDetails, apiKey } =
|
|
2361
|
+
const { portalDetails, apiKey } = getPortalData(fileId);
|
|
2525
2362
|
const apiKeySeed = toUint8Array3(apiKey);
|
|
2526
2363
|
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2527
2364
|
const fileManager = await createFileManager(
|
|
@@ -2590,7 +2427,7 @@ var processEvent = async (event) => {
|
|
|
2590
2427
|
return { success: false, error: errorMsg };
|
|
2591
2428
|
}
|
|
2592
2429
|
};
|
|
2593
|
-
var onTransactionSuccess =
|
|
2430
|
+
var onTransactionSuccess = (fileId, file, onChainFileId, pending) => {
|
|
2594
2431
|
const frontendUrl = getRuntimeConfig().FRONTEND_URL;
|
|
2595
2432
|
const payload = {
|
|
2596
2433
|
onchainVersion: file.localVersion,
|
|
@@ -2601,14 +2438,14 @@ var onTransactionSuccess = async (fileId, file, onChainFileId, pending) => {
|
|
|
2601
2438
|
metadata: pending.metadata,
|
|
2602
2439
|
link: `${frontendUrl}/${file.portalAddress}/${onChainFileId}#key=${pending.linkKey}`
|
|
2603
2440
|
};
|
|
2604
|
-
const updatedFile =
|
|
2441
|
+
const updatedFile = FilesModel.update(fileId, payload, file.portalAddress);
|
|
2605
2442
|
if (updatedFile.localVersion === updatedFile.onchainVersion) {
|
|
2606
|
-
|
|
2443
|
+
FilesModel.update(fileId, { syncStatus: "synced" }, file.portalAddress);
|
|
2607
2444
|
}
|
|
2608
2445
|
};
|
|
2609
2446
|
var processCreateEvent = async (event) => {
|
|
2610
2447
|
const { fileId } = event;
|
|
2611
|
-
const file =
|
|
2448
|
+
const file = FilesModel.findByIdIncludingDeleted(fileId);
|
|
2612
2449
|
if (!file) {
|
|
2613
2450
|
throw new Error(`File ${fileId} not found`);
|
|
2614
2451
|
}
|
|
@@ -2627,18 +2464,18 @@ var processCreateEvent = async (event) => {
|
|
|
2627
2464
|
timeout
|
|
2628
2465
|
);
|
|
2629
2466
|
if (!receipt2.success) {
|
|
2630
|
-
|
|
2467
|
+
EventsModel.clearEventPendingOp(event._id);
|
|
2631
2468
|
throw new Error(`User operation failed: ${receipt2.reason}`);
|
|
2632
2469
|
}
|
|
2633
2470
|
const onChainFileId2 = parseFileEventLog(receipt2.logs, "AddedFile", ADDED_FILE_EVENT);
|
|
2634
2471
|
const pending = JSON.parse(event.pendingPayload);
|
|
2635
|
-
|
|
2636
|
-
|
|
2472
|
+
onTransactionSuccess(fileId, file, onChainFileId2, pending);
|
|
2473
|
+
EventsModel.clearEventPendingOp(event._id);
|
|
2637
2474
|
logger.info(`File ${file.ddocId} created and published successfully (resumed from pending op)`);
|
|
2638
2475
|
return;
|
|
2639
2476
|
}
|
|
2640
2477
|
const result = await handleNewFileOp(fileId);
|
|
2641
|
-
|
|
2478
|
+
EventsModel.setEventPendingOp(event._id, result.userOpHash, {
|
|
2642
2479
|
linkKey: result.linkKey,
|
|
2643
2480
|
linkKeyNonce: result.linkKeyNonce,
|
|
2644
2481
|
commentKey: result.commentKey,
|
|
@@ -2652,22 +2489,22 @@ var processCreateEvent = async (event) => {
|
|
|
2652
2489
|
timeout
|
|
2653
2490
|
);
|
|
2654
2491
|
if (!receipt.success) {
|
|
2655
|
-
|
|
2492
|
+
EventsModel.clearEventPendingOp(event._id);
|
|
2656
2493
|
throw new Error(`User operation failed: ${receipt.reason}`);
|
|
2657
2494
|
}
|
|
2658
2495
|
const onChainFileId = parseFileEventLog(receipt.logs, "AddedFile", ADDED_FILE_EVENT);
|
|
2659
|
-
|
|
2496
|
+
onTransactionSuccess(fileId, file, onChainFileId, {
|
|
2660
2497
|
linkKey: result.linkKey,
|
|
2661
2498
|
linkKeyNonce: result.linkKeyNonce,
|
|
2662
2499
|
commentKey: result.commentKey,
|
|
2663
2500
|
metadata: result.metadata
|
|
2664
2501
|
});
|
|
2665
|
-
|
|
2502
|
+
EventsModel.clearEventPendingOp(event._id);
|
|
2666
2503
|
logger.info(`File ${file.ddocId} created and published successfully`);
|
|
2667
2504
|
};
|
|
2668
2505
|
var processUpdateEvent = async (event) => {
|
|
2669
2506
|
const { fileId } = event;
|
|
2670
|
-
const file =
|
|
2507
|
+
const file = FilesModel.findByIdExcludingDeleted(fileId);
|
|
2671
2508
|
if (!file) {
|
|
2672
2509
|
return;
|
|
2673
2510
|
}
|
|
@@ -2682,15 +2519,15 @@ var processUpdateEvent = async (event) => {
|
|
|
2682
2519
|
onchainVersion: file.localVersion,
|
|
2683
2520
|
metadata: result.metadata
|
|
2684
2521
|
};
|
|
2685
|
-
const updatedFile =
|
|
2522
|
+
const updatedFile = FilesModel.update(fileId, payload, file.portalAddress);
|
|
2686
2523
|
if (updatedFile.localVersion === updatedFile.onchainVersion) {
|
|
2687
|
-
|
|
2524
|
+
FilesModel.update(fileId, { syncStatus: "synced" }, file.portalAddress);
|
|
2688
2525
|
}
|
|
2689
2526
|
logger.info(`File ${file.ddocId} updated and published successfully`);
|
|
2690
2527
|
};
|
|
2691
2528
|
var processDeleteEvent = async (event) => {
|
|
2692
2529
|
const { fileId } = event;
|
|
2693
|
-
const file =
|
|
2530
|
+
const file = FilesModel.findByIdIncludingDeleted(fileId);
|
|
2694
2531
|
if (!file) {
|
|
2695
2532
|
return;
|
|
2696
2533
|
}
|
|
@@ -2711,7 +2548,7 @@ var processDeleteEvent = async (event) => {
|
|
|
2711
2548
|
payload.metadata = result.metadata;
|
|
2712
2549
|
payload.isDeleted = 1;
|
|
2713
2550
|
}
|
|
2714
|
-
|
|
2551
|
+
FilesModel.update(fileId, payload, file.portalAddress);
|
|
2715
2552
|
logger.info(`File ${fileId} delete event processed (syncStatus set to synced)`);
|
|
2716
2553
|
};
|
|
2717
2554
|
|
|
@@ -2728,8 +2565,8 @@ var FileEventsWorker = class {
|
|
|
2728
2565
|
signalCleanup = null;
|
|
2729
2566
|
pendingSignal = false;
|
|
2730
2567
|
wakeResolver = null;
|
|
2731
|
-
constructor(
|
|
2732
|
-
this.concurrency =
|
|
2568
|
+
constructor(concurrency2 = DEFAULT_CONCURRENCY) {
|
|
2569
|
+
this.concurrency = concurrency2;
|
|
2733
2570
|
}
|
|
2734
2571
|
start() {
|
|
2735
2572
|
if (this.isRunning) {
|
|
@@ -2737,11 +2574,10 @@ var FileEventsWorker = class {
|
|
|
2737
2574
|
return;
|
|
2738
2575
|
}
|
|
2739
2576
|
this.isRunning = true;
|
|
2740
|
-
this.recoverStaleEvents()
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
});
|
|
2577
|
+
const staleCount = this.recoverStaleEvents();
|
|
2578
|
+
if (staleCount > 0) {
|
|
2579
|
+
logger.info(`Recovered ${staleCount} stale event(s)`);
|
|
2580
|
+
}
|
|
2745
2581
|
this.signalCleanup = onNewEvent(() => {
|
|
2746
2582
|
this.pendingSignal = true;
|
|
2747
2583
|
this.wakeUp();
|
|
@@ -2770,10 +2606,10 @@ var FileEventsWorker = class {
|
|
|
2770
2606
|
let foundAny = false;
|
|
2771
2607
|
while (this.activeProcessors.size < this.concurrency && this.isRunning) {
|
|
2772
2608
|
const lockedFileIds = Array.from(this.activeProcessors.keys());
|
|
2773
|
-
const event =
|
|
2609
|
+
const event = EventsModel.findNextEligible(lockedFileIds);
|
|
2774
2610
|
if (!event) break;
|
|
2775
2611
|
foundAny = true;
|
|
2776
|
-
|
|
2612
|
+
EventsModel.markProcessing(event._id);
|
|
2777
2613
|
const processor = this.processEventWrapper(event);
|
|
2778
2614
|
this.activeProcessors.set(event.fileId, processor);
|
|
2779
2615
|
}
|
|
@@ -2784,33 +2620,33 @@ var FileEventsWorker = class {
|
|
|
2784
2620
|
try {
|
|
2785
2621
|
const result = await processEvent(event);
|
|
2786
2622
|
if (result.success) {
|
|
2787
|
-
|
|
2623
|
+
EventsModel.markProcessed(event._id);
|
|
2788
2624
|
} else {
|
|
2789
|
-
|
|
2625
|
+
this.handleFailure(event, result.error);
|
|
2790
2626
|
}
|
|
2791
2627
|
} catch (err) {
|
|
2792
|
-
|
|
2628
|
+
this.handleFailure(event, err);
|
|
2793
2629
|
} finally {
|
|
2794
2630
|
this.activeProcessors.delete(event.fileId);
|
|
2795
2631
|
}
|
|
2796
2632
|
}
|
|
2797
|
-
|
|
2633
|
+
handleFailure(event, error) {
|
|
2798
2634
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2799
2635
|
if (error instanceof RateLimitError) {
|
|
2800
2636
|
const retryAfterMs = error.retryAfterSeconds * 1e3;
|
|
2801
|
-
|
|
2637
|
+
EventsModel.scheduleRetryAfter(event._id, errorMsg, retryAfterMs);
|
|
2802
2638
|
logger.warn(`Event ${event._id} rate limited; retry after ${error.retryAfterSeconds}s`);
|
|
2803
2639
|
return;
|
|
2804
2640
|
}
|
|
2805
2641
|
if (event.retryCount < MAX_RETRIES) {
|
|
2806
|
-
|
|
2642
|
+
EventsModel.scheduleRetry(event._id, errorMsg);
|
|
2807
2643
|
logger.warn(`Event ${event._id} failed (retry ${event.retryCount + 1}/${MAX_RETRIES}): ${errorMsg}`);
|
|
2808
2644
|
} else {
|
|
2809
|
-
|
|
2645
|
+
EventsModel.markFailed(event._id, errorMsg);
|
|
2810
2646
|
logger.error(`Event ${event._id} permanently failed after ${MAX_RETRIES} retries: ${errorMsg}`);
|
|
2811
2647
|
}
|
|
2812
2648
|
}
|
|
2813
|
-
|
|
2649
|
+
recoverStaleEvents() {
|
|
2814
2650
|
const staleThreshold = Date.now() - STALE_THRESHOLD_MS;
|
|
2815
2651
|
return EventsModel.resetStaleEvents(staleThreshold);
|
|
2816
2652
|
}
|
|
@@ -2860,18 +2696,18 @@ var FileEventsWorker = class {
|
|
|
2860
2696
|
return this.activeProcessors.size;
|
|
2861
2697
|
}
|
|
2862
2698
|
};
|
|
2863
|
-
function createWorker(
|
|
2864
|
-
return new FileEventsWorker(
|
|
2699
|
+
function createWorker(concurrency2 = DEFAULT_CONCURRENCY) {
|
|
2700
|
+
return new FileEventsWorker(concurrency2);
|
|
2865
2701
|
}
|
|
2866
2702
|
|
|
2867
2703
|
// src/appWorker.ts
|
|
2868
2704
|
var DEFAULT_CONCURRENCY2 = 5;
|
|
2869
2705
|
var worker = null;
|
|
2870
|
-
function startWorker(
|
|
2706
|
+
function startWorker(concurrency2 = DEFAULT_CONCURRENCY2) {
|
|
2871
2707
|
if (worker?.isActive()) {
|
|
2872
2708
|
return;
|
|
2873
2709
|
}
|
|
2874
|
-
worker = createWorker(
|
|
2710
|
+
worker = createWorker(concurrency2);
|
|
2875
2711
|
worker.start();
|
|
2876
2712
|
}
|
|
2877
2713
|
async function closeWorker() {
|
|
@@ -2902,8 +2738,8 @@ CREATE TABLE IF NOT EXISTS files (
|
|
|
2902
2738
|
localVersion INTEGER NOT NULL DEFAULT 1,
|
|
2903
2739
|
onchainVersion INTEGER NOT NULL DEFAULT 0,
|
|
2904
2740
|
syncStatus TEXT NOT NULL DEFAULT 'pending',
|
|
2905
|
-
createdAt
|
|
2906
|
-
updatedAt
|
|
2741
|
+
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
2742
|
+
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
2907
2743
|
isDeleted INTEGER NOT NULL DEFAULT 0,
|
|
2908
2744
|
portalAddress TEXT NOT NULL,
|
|
2909
2745
|
metadata TEXT DEFAULT '{}',
|
|
@@ -2923,8 +2759,8 @@ CREATE TABLE IF NOT EXISTS portals (
|
|
|
2923
2759
|
portalAddress TEXT NOT NULL UNIQUE,
|
|
2924
2760
|
portalSeed TEXT NOT NULL UNIQUE,
|
|
2925
2761
|
ownerAddress TEXT NOT NULL,
|
|
2926
|
-
createdAt
|
|
2927
|
-
updatedAt
|
|
2762
|
+
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
2763
|
+
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
2928
2764
|
);
|
|
2929
2765
|
|
|
2930
2766
|
CREATE TABLE IF NOT EXISTS api_keys (
|
|
@@ -2933,7 +2769,7 @@ CREATE TABLE IF NOT EXISTS api_keys (
|
|
|
2933
2769
|
name TEXT NOT NULL,
|
|
2934
2770
|
collaboratorAddress TEXT NOT NULL UNIQUE,
|
|
2935
2771
|
portalAddress TEXT NOT NULL,
|
|
2936
|
-
createdAt
|
|
2772
|
+
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
2937
2773
|
isDeleted INTEGER NOT NULL DEFAULT 0
|
|
2938
2774
|
);
|
|
2939
2775
|
|
|
@@ -2969,39 +2805,32 @@ CREATE TABLE IF NOT EXISTS folders (
|
|
|
2969
2805
|
lastTransactionHash TEXT,
|
|
2970
2806
|
lastTransactionBlockNumber INTEGER NOT NULL,
|
|
2971
2807
|
lastTransactionBlockTimestamp INTEGER NOT NULL,
|
|
2972
|
-
created_at
|
|
2973
|
-
updated_at
|
|
2808
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
2809
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
2974
2810
|
);
|
|
2975
2811
|
CREATE INDEX IF NOT EXISTS idx_folders_folderRef_folderId ON folders(folderRef, folderId);
|
|
2976
2812
|
CREATE INDEX IF NOT EXISTS idx_folders_folderRef ON folders(folderRef);
|
|
2977
2813
|
CREATE INDEX IF NOT EXISTS idx_folders_created_at ON folders(created_at);
|
|
2978
2814
|
`;
|
|
2979
|
-
|
|
2980
|
-
const
|
|
2981
|
-
|
|
2815
|
+
function runMigrations() {
|
|
2816
|
+
const db = database_default();
|
|
2817
|
+
db.exec(STABLE_SCHEMA);
|
|
2982
2818
|
logger.debug("Database schema ready");
|
|
2983
2819
|
}
|
|
2984
2820
|
|
|
2985
2821
|
// src/worker.ts
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
}
|
|
2997
|
-
logger.error("Worker failed to start");
|
|
2998
|
-
process.exit(1);
|
|
2999
|
-
}, 100);
|
|
3000
|
-
}
|
|
3001
|
-
main().catch((error) => {
|
|
3002
|
-
logger.error("Failed to start worker:", error);
|
|
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");
|
|
3003
2832
|
process.exit(1);
|
|
3004
|
-
});
|
|
2833
|
+
}, 100);
|
|
3005
2834
|
var shutdown = async () => {
|
|
3006
2835
|
logger.info("Shutting down worker gracefully...");
|
|
3007
2836
|
await closeWorker();
|