@fileverse/api 0.0.3 → 0.0.4
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 +275 -83
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/index.js +1193 -602
- package/dist/commands/index.js.map +1 -1
- package/dist/index.js +3096 -2356
- package/dist/index.js.map +1 -1
- package/dist/worker.js +2731 -2139
- package/dist/worker.js.map +1 -1
- package/package.json +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/index.ts","../../src/config/index.ts","../../src/cli/constants.generated.ts","../../src/infra/logger.ts","../../src/infra/database/connection.ts","../../src/infra/database/models/files.model.ts","../../src/infra/database/models/portals.model.ts","../../src/infra/database/models/apikeys.model.ts","../../src/infra/database/models/events.model.ts","../../src/infra/worker/workerSignal.ts","../../src/sdk/key-store.ts","../../src/sdk/auth-token-provider.ts","../../src/domain/portal/publish.ts","../../src/sdk/smart-agent.ts","../../src/sdk/pimlico-utils.ts","../../src/constants/chains.ts","../../src/constants/index.ts","../../src/sdk/file-manager.ts","../../src/sdk/file-utils.ts","../../src/sdk/file-encryption.ts","../../src/errors/rate-limit.ts","../../src/infra/worker/worker.ts","../../src/appWorker.ts","../../src/infra/reporter.ts","../../src/domain/file/constants.ts","../../src/infra/database/query-builder.ts","../../src/infra/database/index.ts","../../src/infra/database/migrations/index.ts","../../src/commands/listCommand.ts","../../src/domain/file/index.ts","../../src/commands/utils/util.ts","../../src/commands/getCommand.ts","../../src/commands/createCommand.ts","../../src/commands/updateCommand.ts","../../src/commands/deleteCommand.ts","../../src/commands/downloadCommand.ts","../../src/commands/viewCommand.ts","../../src/commands/eventsCommand.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\n\n// Import config early to validate DB_PATH\nimport \"../config\";\n\nimport { logger } from \"../infra/logger\";\nlogger.level = \"error\";\n\nimport { runMigrations } from \"../infra/database/migrations\";\nrunMigrations();\n\nimport { listCommand } from \"./listCommand\";\nimport { getCommand } from \"./getCommand\";\nimport { createCommand } from \"./createCommand\";\nimport { updateCommand } from \"./updateCommand\";\nimport { deleteCommand } from \"./deleteCommand\";\nimport { downloadCommand } from \"./downloadCommand\";\nimport { viewCommand } from \"./viewCommand\";\nimport { eventsCommand } from \"./eventsCommand\";\nimport { closeWorker, closeDatabase } from \"../infra\";\n\nexport const program = new Command()\n .name(\"ddctl\")\n .description(\"CLI tool to manage your ddocs\")\n .version(\"0.0.3\")\n .addHelpText(\"beforeAll\", \"\\n\")\n .addHelpText(\"afterAll\", \"\\n\");\n\nprogram.addCommand(listCommand);\nprogram.addCommand(getCommand);\nprogram.addCommand(createCommand);\nprogram.addCommand(updateCommand);\nprogram.addCommand(deleteCommand);\nprogram.addCommand(downloadCommand);\nprogram.addCommand(viewCommand);\nprogram.addCommand(eventsCommand);\n\n// Close connections and exit after command completes\nprogram\n .parseAsync()\n .then(async () => {\n try {\n await closeWorker();\n await closeDatabase();\n } catch (error) {\n // Ignore errors during cleanup\n }\n process.exit(0);\n })\n .catch((error) => {\n console.error(\"Error:\", error);\n process.exit(1);\n });\n","import dotenv from \"dotenv\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport os from \"os\";\nimport { STATIC_CONFIG } from \"../cli/constants.js\";\n\nconst projectEnvPath = path.join(process.cwd(), \"config\", \".env\");\nconst userEnvPath = path.join(os.homedir(), \".fileverse\", \".env\");\n\nfunction getEnvPath(): string {\n if (fs.existsSync(projectEnvPath)) {\n return projectEnvPath;\n }\n return userEnvPath;\n}\n\nexport function loadConfig(override = true): void {\n const envPath = getEnvPath();\n dotenv.config({ path: envPath, override });\n}\n\nloadConfig(false);\n\nexport function getRuntimeConfig() {\n return {\n get API_KEY() {\n return process.env.API_KEY;\n },\n get RPC_URL() {\n return process.env.RPC_URL || STATIC_CONFIG.DEFAULT_RPC_URL;\n },\n get DB_PATH() {\n return process.env.DB_PATH;\n },\n get PORT() {\n return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;\n },\n get NODE_ENV() {\n return process.env.NODE_ENV || \"production\";\n },\n get FRONTEND_URL() {\n return process.env.FRONTEND_URL || STATIC_CONFIG.FRONTEND_URL;\n },\n };\n}\n\nexport function validateDbPath(): void {\n const dbPath = process.env.DB_PATH;\n if (!dbPath) {\n console.error(\"Error: DB_PATH environment variable is required\");\n console.error(\"Please set DB_PATH in your .env file (config/.env or ~/.fileverse/.env) or run the CLI first\");\n process.exit(1);\n }\n\n const dbDir = path.dirname(dbPath.trim());\n if (!fs.existsSync(dbDir)) {\n fs.mkdirSync(dbDir, { recursive: true });\n }\n}\n\nconst config: Record<string, string | undefined> = {\n ...STATIC_CONFIG,\n get SERVICE_NAME() {\n return STATIC_CONFIG.SERVICE_NAME;\n },\n get LOG_LEVEL() {\n return STATIC_CONFIG.LOG_LEVEL;\n },\n get NETWORK_NAME() {\n return STATIC_CONFIG.NETWORK_NAME;\n },\n get UPLOAD_SERVER_URL() {\n return STATIC_CONFIG.API_URL;\n },\n get UPLOAD_SERVER_DID() {\n return STATIC_CONFIG.SERVER_DID;\n },\n get API_KEY() {\n return process.env.API_KEY;\n },\n get RPC_URL() {\n return process.env.RPC_URL || STATIC_CONFIG.DEFAULT_RPC_URL;\n },\n get DB_PATH() {\n return process.env.DB_PATH;\n },\n get PORT() {\n return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;\n },\n get NODE_ENV() {\n return process.env.NODE_ENV || \"production\";\n },\n get IP() {\n return process.env.IP || \"0.0.0.0\";\n },\n get FRONTEND_URL() {\n return process.env.FRONTEND_URL || STATIC_CONFIG.FRONTEND_URL;\n },\n};\n\nexport { config };\n","export const STATIC_CONFIG = {\n API_URL: 'https://prod-apps-storage-5cdacc06ff79.herokuapp.com/',\n SERVER_DID: 'did:key:z6Mkroj9bxTin6Z5S9qwx2G2b87NPrCX7S85FhCpmBGPcDCz',\n PROXY_SERVER_DID: 'did:key:z6MkrZSmq8D6vQG87YbjUQatXeptaCCXWdTx8fYaWxWbRUHB',\n NETWORK_NAME: 'gnosis',\n DEFAULT_PORT: '8001',\n DEFAULT_RPC_URL: 'https://rpc.gnosischain.com',\n PIMLICO_PROXY_URL: 'https://pimlico-proxy-0a326da116f8.herokuapp.com/',\n SERVICE_NAME: 'fileverse-api',\n LOG_LEVEL: 'info',\n FRONTEND_URL: 'https://docs.fileverse.io'\n} as const;\n\nexport const BASE_CONFIG = STATIC_CONFIG;\n","import pino, { Logger as PinoLogger, Level } from \"pino\";\nimport { STATIC_CONFIG } from \"../cli/constants\";\nimport { config } from \"../config\";\n\nconst isProduction = config.NODE_ENV === \"production\";\n\nconst pinoInstance = pino({\n name: STATIC_CONFIG.SERVICE_NAME,\n level: STATIC_CONFIG.LOG_LEVEL,\n formatters: {\n bindings: (bindings) => ({ name: bindings.name }),\n level: (label) => ({ level: label }),\n },\n serializers: {\n err(err: Error | undefined) {\n if (!err) return err;\n if (isProduction) {\n return { type: err.name, message: err.message };\n }\n return {\n type: err.name,\n message: err.message,\n stack: err.stack,\n };\n },\n },\n transport:\n config.NODE_ENV !== \"production\"\n ? {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"SYS:standard\",\n ignore: \"pid,hostname\",\n errorProps: \"*\",\n errorLikeObjectKeys: [\"err\", \"error\"],\n },\n }\n : undefined,\n});\n\ntype LogFn = {\n (msg: string, ...args: unknown[]): void;\n (obj: object, msg?: string, ...args: unknown[]): void;\n};\n\nconst createLogMethod = (level: Level): LogFn => {\n return (...args: unknown[]) => {\n const [first, ...rest] = args;\n const log = pinoInstance[level].bind(pinoInstance) as (...a: unknown[]) => void;\n\n if (typeof first === \"object\" && first !== null && !(first instanceof Error)) {\n log(first, ...rest);\n return;\n }\n\n if (rest.length > 0) {\n const last = rest[rest.length - 1];\n if (last instanceof Error) {\n log({ err: last }, first, ...rest.slice(0, -1));\n return;\n }\n }\n\n if (first instanceof Error) {\n log({ err: first }, first.message);\n return;\n }\n\n log(first, ...rest);\n };\n};\n\ninterface Logger {\n trace: LogFn;\n debug: LogFn;\n info: LogFn;\n warn: LogFn;\n error: LogFn;\n fatal: LogFn;\n level: Level;\n child: PinoLogger[\"child\"];\n}\n\nexport const logger: Logger = {\n trace: createLogMethod(\"trace\"),\n debug: createLogMethod(\"debug\"),\n info: createLogMethod(\"info\"),\n warn: createLogMethod(\"warn\"),\n error: createLogMethod(\"error\"),\n fatal: createLogMethod(\"fatal\"),\n get level() {\n return pinoInstance.level as Level;\n },\n set level(lvl: Level) {\n pinoInstance.level = lvl;\n },\n child: pinoInstance.child.bind(pinoInstance),\n};\n","import Database from \"better-sqlite3\";\nimport { config } from \"../../config\";\nimport { logger } from \"../index\";\nimport path from \"path\";\nimport fs from \"fs\";\n\n/**\n * Database connection manager - Singleton pattern\n * Provides a shared SQLite database connection\n */\nclass DatabaseConnectionManager {\n private static instance: DatabaseConnectionManager;\n private db: Database.Database | null = null;\n\n private constructor() {}\n\n static getInstance(): DatabaseConnectionManager {\n if (!DatabaseConnectionManager.instance) {\n DatabaseConnectionManager.instance = new DatabaseConnectionManager();\n }\n return DatabaseConnectionManager.instance;\n }\n\n getConnection(): Database.Database {\n if (!this.db) {\n // DB_PATH is required, validated, and normalized in config/index.ts\n const dbPath = config.DB_PATH!;\n\n // Create database instance\n this.db = new Database(dbPath, {\n verbose: config.NODE_ENV === \"development\" ? (msg: unknown) => logger.debug(String(msg)) : undefined,\n });\n\n // Enable WAL mode for better concurrency\n this.db.pragma(\"journal_mode = WAL\");\n\n // Enable foreign keys\n this.db.pragma(\"foreign_keys = ON\");\n\n // Connection health check\n this.db.prepare(\"SELECT 1\").get();\n\n logger.info(`SQLite database connected: ${dbPath}`);\n }\n\n return this.db;\n }\n\n async close(): Promise<void> {\n if (this.db) {\n this.db.close();\n this.db = null;\n logger.info(\"Database connection closed\");\n }\n }\n\n isConnected(): boolean {\n return this.db !== null && this.db.open;\n }\n}\n\nexport const databaseConnectionManager = DatabaseConnectionManager.getInstance();\n","import { QueryBuilder } from \"../index\";\nimport { uuidv7 } from \"uuidv7\";\nimport type { File, FileListResponse, UpdateFilePayload } from \"../../../types\";\n\nexport type { File, FileListResponse };\n\nexport class FilesModel {\n private static readonly TABLE = \"files\";\n\n private static parseFile(fileRaw: any): File {\n let metadata: Record<string, unknown> = {};\n try {\n if (fileRaw.metadata) {\n metadata = typeof fileRaw.metadata === \"string\" ? JSON.parse(fileRaw.metadata) : fileRaw.metadata;\n }\n } catch (e) {\n // If parsing fails, use empty object\n metadata = {};\n }\n\n return {\n _id: fileRaw._id,\n ddocId: fileRaw.ddocId,\n title: fileRaw.title,\n content: fileRaw.content,\n localVersion: fileRaw.localVersion,\n onchainVersion: fileRaw.onchainVersion,\n syncStatus: fileRaw.syncStatus,\n isDeleted: fileRaw.isDeleted,\n onChainFileId: fileRaw.onChainFileId ?? null,\n portalAddress: fileRaw.portalAddress,\n metadata: metadata || {},\n createdAt: fileRaw.createdAt,\n updatedAt: fileRaw.updatedAt,\n linkKey: fileRaw.linkKey,\n linkKeyNonce: fileRaw.linkKeyNonce,\n commentKey: fileRaw.commentKey,\n link: fileRaw.link,\n };\n }\n\n static findAll(\n portalAddress: string,\n limit?: number,\n skip?: number,\n ): { files: File[]; total: number; hasNext: boolean } {\n const whereClause = \"isDeleted = 0 AND portalAddress = ?\";\n const params: any[] = [portalAddress];\n\n const countSql = `\n SELECT COUNT(*) as count \n FROM ${this.TABLE} \n WHERE ${whereClause}\n `;\n const totalResult = QueryBuilder.selectOne<{ count: number }>(countSql, params);\n const total = totalResult?.count || 0;\n const sql = `\n SELECT *\n FROM ${this.TABLE}\n WHERE ${whereClause}\n `;\n const completeSql = QueryBuilder.paginate(sql, {\n limit,\n offset: skip,\n orderBy: \"createdAt\",\n orderDirection: \"DESC\",\n });\n\n const filesRaw = QueryBuilder.select<any>(completeSql, params);\n const files = filesRaw.map(this.parseFile);\n const hasNext = skip !== undefined && limit !== undefined ? skip + limit < total : false;\n return { files, total, hasNext };\n }\n\n static findById(_id: string, portalAddress: string): File | undefined {\n const sql = `\n SELECT *\n FROM ${this.TABLE} \n WHERE _id = ? AND isDeleted = 0 AND portalAddress = ?\n `;\n const result = QueryBuilder.selectOne<any>(sql, [_id, portalAddress]);\n return result ? this.parseFile(result) : undefined;\n }\n\n static findByIdIncludingDeleted(_id: string): File | undefined {\n const sql = `\n SELECT *\n FROM ${this.TABLE} \n WHERE _id = ?\n `;\n const result = QueryBuilder.selectOne<any>(sql, [_id]);\n return result ? this.parseFile(result) : undefined;\n }\n\n static findByIdExcludingDeleted(_id: string): File | undefined {\n const sql = `\n SELECT *\n FROM ${this.TABLE} \n WHERE _id = ? AND isDeleted = 0\n `;\n const result = QueryBuilder.selectOne<any>(sql, [_id]);\n return result ? this.parseFile(result) : undefined;\n }\n\n static findByDDocId(ddocId: string, portalAddress: string): File | undefined {\n const sql = `\n SELECT *\n FROM ${this.TABLE} \n WHERE ddocId = ? AND isDeleted = 0 AND portalAddress = ?\n `;\n const result = QueryBuilder.selectOne<any>(sql, [ddocId, portalAddress]);\n return result ? this.parseFile(result) : undefined;\n }\n\n static searchByTitle(searchTerm: string, portalAddress: string, limit?: number, skip?: number): File[] {\n const sql = `\n SELECT *\n FROM ${this.TABLE} \n WHERE LOWER(title) LIKE LOWER(?) AND isDeleted = 0 AND portalAddress = ?\n `;\n const completeSql = QueryBuilder.paginate(sql, {\n limit,\n offset: skip,\n orderBy: \"createdAt\",\n orderDirection: \"DESC\",\n });\n const filesRaw = QueryBuilder.select<any>(completeSql, [`%${searchTerm}%`, portalAddress]);\n return filesRaw.map(this.parseFile);\n }\n\n static create(input: { title: string; content: string; ddocId: string; portalAddress: string }): File {\n const _id = uuidv7();\n const sql = `\n INSERT INTO ${this.TABLE} \n (_id, title, content, ddocId, portalAddress) \n VALUES (?, ?, ?, ?, ?)\n `;\n\n QueryBuilder.execute(sql, [_id, input.title, input.content, input.ddocId, input.portalAddress]);\n // NOTE: default values while file creation: localVersion = 1, onchainVersion = 0, syncStatus = 'pending'\n\n const created = this.findById(_id, input.portalAddress);\n if (!created) {\n throw new Error(\"Failed to create file\");\n }\n return created;\n }\n\n static update(_id: string, payload: UpdateFilePayload, portalAddress: string): File {\n const now = new Date().toISOString();\n\n const keys: string[] = [];\n const values: any[] = [];\n for (const [k, v] of Object.entries(payload)) {\n if (v !== undefined) {\n // Handle metadata specially - convert to JSON string\n if (k === \"metadata\" && typeof v === \"object\") {\n keys.push(`${k} = ?`);\n values.push(JSON.stringify(v));\n } else {\n keys.push(`${k} = ?`);\n values.push(v);\n }\n }\n }\n\n // Always add updatedAt\n keys.push(\"updatedAt = ?\");\n values.push(now, _id, portalAddress);\n\n const updateChain = keys.join(\", \");\n const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE _id = ? AND portalAddress = ?`;\n\n QueryBuilder.execute(sql, values);\n\n const updated = this.findById(_id, portalAddress);\n if (!updated) {\n throw new Error(\"Failed to update file\");\n }\n return updated;\n }\n\n static softDelete(_id: string): File {\n const now = new Date().toISOString();\n const sql = `\n UPDATE ${this.TABLE} \n SET isDeleted = 1, syncStatus = 'pending', updatedAt = ?\n WHERE _id = ?\n `;\n\n QueryBuilder.execute(sql, [now, _id]);\n\n // Use findByIdIncludingDeleted since the file is now marked as deleted\n const deleted = this.findByIdIncludingDeleted(_id);\n if (!deleted) {\n throw new Error(\"Failed to delete file\");\n }\n return deleted;\n }\n}\n","import { QueryBuilder } from \"../index\";\nimport { uuidv7 } from \"uuidv7\";\nimport type { Portal } from \"../../../types\";\n\nexport type { Portal };\n\nexport class PortalsModel {\n private static readonly TABLE = \"portals\";\n\n static findByPortalAddress(portalAddress: string): Portal | undefined {\n const sql = `SELECT _id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt FROM ${this.TABLE} WHERE portalAddress = ?`;\n return QueryBuilder.selectOne<Portal>(sql, [portalAddress]);\n }\n\n static create(input: { portalAddress: string; portalSeed: string; ownerAddress: string }): Portal {\n const _id = uuidv7();\n const now = new Date().toISOString();\n const sql = `INSERT INTO ${this.TABLE} (_id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)`;\n\n QueryBuilder.execute(sql, [_id, input.portalAddress, input.portalSeed, input.ownerAddress, now, now]);\n\n const created = this.findByPortalAddress(input.portalAddress);\n if (!created) {\n throw new Error(\"Failed to create portal\");\n }\n return created;\n }\n\n static update(\n portalAddress: string,\n input: {\n portalSeed?: string;\n ownerAddress?: string;\n },\n ): Portal {\n const now = new Date().toISOString();\n const keys: string[] = [];\n const values: any[] = [];\n\n for (const [k, v] of Object.entries(input)) {\n if (v !== undefined) {\n keys.push(`${k} = ?`);\n values.push(v);\n }\n }\n\n keys.push(\"updatedAt = ?\");\n values.push(now);\n\n const updateChain = keys.join(\", \");\n const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE portalAddress = ?`;\n values.push(portalAddress);\n QueryBuilder.execute(sql, values);\n\n const updated = this.findByPortalAddress(portalAddress);\n if (!updated) {\n throw new Error(\"Failed to update portal\");\n }\n return updated;\n }\n\n static upsert(input: { portalAddress: string; portalSeed: string; ownerAddress: string }): Portal {\n const existing = this.findByPortalAddress(input.portalAddress);\n if (existing) {\n return this.update(input.portalAddress, {\n portalSeed: input.portalSeed,\n ownerAddress: input.ownerAddress,\n });\n }\n return this.create(input);\n }\n}\n","import { QueryBuilder } from \"../index\";\nimport { uuidv7 } from \"uuidv7\";\nimport type { ApiKey } from \"../../../types\";\n\nexport type { ApiKey };\n\nexport class ApiKeysModel {\n private static readonly TABLE = \"api_keys\";\n\n static create(input: {\n apiKeySeed: string;\n name: string;\n collaboratorAddress: string;\n portalAddress: string;\n }): ApiKey {\n const _id = uuidv7();\n const now = new Date().toISOString();\n const sql = `INSERT INTO ${this.TABLE} (_id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt) \n VALUES (?, ?, ?, ?, ?, ?)`;\n\n const result = QueryBuilder.execute(sql, [\n _id,\n input.apiKeySeed,\n input.name,\n input.collaboratorAddress,\n input.portalAddress,\n now,\n ]);\n\n if (result.changes === 0) {\n throw new Error(\"Failed to create API key\");\n }\n\n const created = this.findById(_id);\n if (!created) {\n throw new Error(\"Failed to create API key\");\n }\n return created;\n }\n\n static findById(_id: string): ApiKey | undefined {\n const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE _id = ? AND isDeleted = 0`;\n return QueryBuilder.selectOne<ApiKey>(sql, [_id]);\n }\n\n static findByCollaboratorAddress(collaboratorAddress: string): ApiKey | undefined {\n const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE collaboratorAddress = ? AND isDeleted = 0 LIMIT 1`;\n return QueryBuilder.selectOne<ApiKey>(sql, [collaboratorAddress]);\n }\n\n static delete(_id: string): void {\n const sql = `UPDATE ${this.TABLE} SET isDeleted = 1 WHERE _id = ?`;\n QueryBuilder.execute(sql, [_id]);\n }\n\n static findByPortalAddress(portalAddress: string): ApiKey | undefined {\n const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE portalAddress = ? AND isDeleted = 0`;\n return QueryBuilder.selectOne<ApiKey>(sql, [portalAddress]);\n }\n\n static findByApiKey(apiKey: string): ApiKey | undefined {\n const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE apiKeySeed = ? AND isDeleted = 0`;\n return QueryBuilder.selectOne<ApiKey>(sql, [apiKey]);\n }\n}\n","import { QueryBuilder } from \"../index\";\nimport { uuidv7 } from \"uuidv7\";\nimport { notifyNewEvent } from \"../../worker/workerSignal\";\nimport type { Event, EventType, EventStatus } from \"../../../types\";\n\nexport type { Event, EventType, EventStatus };\n\nconst RETRY_DELAYS_MS = [5000, 30000, 120000];\n\ninterface EventRow {\n _id: string;\n type: string;\n timestamp: number;\n fileId: string;\n portalAddress: string;\n status: string;\n retryCount: number;\n lastError: string | null;\n lockedAt: number | null;\n nextRetryAt: number | null;\n userOpHash?: string | null;\n pendingPayload?: string | null;\n}\n\nexport class EventsModel {\n private static readonly TABLE = \"events\";\n\n static create(input: { type: EventType; fileId: string; portalAddress: string }): Event {\n const _id = uuidv7();\n const timestamp = Date.now();\n const status: EventStatus = \"pending\";\n\n const sql = `\n INSERT INTO ${this.TABLE} \n (_id, type, timestamp, fileId, portalAddress, status, retryCount, lastError, lockedAt, nextRetryAt) \n VALUES (?, ?, ?, ?, ?, ?, 0, NULL, NULL, NULL)\n `;\n\n QueryBuilder.execute(sql, [_id, input.type, timestamp, input.fileId, input.portalAddress, status]);\n\n notifyNewEvent();\n\n return {\n _id,\n type: input.type,\n timestamp,\n fileId: input.fileId,\n portalAddress: input.portalAddress,\n status,\n retryCount: 0,\n lastError: null,\n lockedAt: null,\n nextRetryAt: null,\n };\n }\n\n static findById(_id: string): Event | undefined {\n const sql = `SELECT * FROM ${this.TABLE} WHERE _id = ?`;\n const row = QueryBuilder.selectOne<EventRow>(sql, [_id]);\n return row ? this.parseEvent(row) : undefined;\n }\n\n static findNextPending(): Event | undefined {\n const sql = `\n SELECT * FROM ${this.TABLE}\n WHERE status = 'pending'\n ORDER BY timestamp ASC\n LIMIT 1\n `;\n const row = QueryBuilder.selectOne<EventRow>(sql, []);\n return row ? this.parseEvent(row) : undefined;\n }\n\n static findNextEligible(lockedFileIds: string[]): Event | undefined {\n const now = Date.now();\n\n const exclusionClause =\n lockedFileIds.length > 0 ? `AND e1.fileId NOT IN (${lockedFileIds.map(() => \"?\").join(\", \")})` : \"\";\n\n const sql = `\n SELECT e1.* FROM ${this.TABLE} e1\n WHERE e1.status = 'pending'\n AND (e1.nextRetryAt IS NULL OR e1.nextRetryAt <= ?)\n ${exclusionClause}\n AND NOT EXISTS (\n SELECT 1 FROM ${this.TABLE} e2\n WHERE e2.fileId = e1.fileId\n AND e2.status = 'pending'\n AND e2.timestamp < e1.timestamp\n )\n ORDER BY e1.timestamp ASC\n LIMIT 1\n `;\n\n const params = [now, ...lockedFileIds];\n const row = QueryBuilder.selectOne<EventRow>(sql, params);\n return row ? this.parseEvent(row) : undefined;\n }\n\n static markProcessing(_id: string): void {\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'processing',\n lockedAt = ?\n WHERE _id = ?\n `;\n QueryBuilder.execute(sql, [Date.now(), _id]);\n }\n\n static markProcessed(_id: string): void {\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'processed',\n lockedAt = NULL\n WHERE _id = ?\n `;\n QueryBuilder.execute(sql, [_id]);\n }\n\n static scheduleRetry(_id: string, errorMsg: string): void {\n const event = this.findById(_id);\n if (!event) return;\n\n const delay = RETRY_DELAYS_MS[Math.min(event.retryCount, RETRY_DELAYS_MS.length - 1)];\n const nextRetryAt = Date.now() + delay;\n\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n retryCount = retryCount + 1,\n lastError = ?,\n nextRetryAt = ?,\n lockedAt = NULL\n WHERE _id = ?\n `;\n QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);\n }\n\n static scheduleRetryAfter(_id: string, errorMsg: string, retryAfterMs: number): void {\n const nextRetryAt = Date.now() + retryAfterMs;\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n lastError = ?,\n nextRetryAt = ?,\n lockedAt = NULL\n WHERE _id = ?\n `;\n QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);\n }\n\n static markFailed(_id: string, errorMsg: string): void {\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'failed',\n lastError = ?,\n lockedAt = NULL\n WHERE _id = ?\n `;\n QueryBuilder.execute(sql, [errorMsg, _id]);\n }\n\n static listFailed(portalAddress?: string): Event[] {\n const portalClause = portalAddress != null ? \"AND portalAddress = ?\" : \"\";\n const sql = `\n SELECT * FROM ${this.TABLE}\n WHERE status = 'failed'\n ${portalClause}\n ORDER BY timestamp ASC\n `;\n const params = portalAddress != null ? [portalAddress] : [];\n const rows = QueryBuilder.select<EventRow>(sql, params);\n return rows.map((row) => this.parseEvent(row));\n }\n\n static resetFailedToPending(_id: string, portalAddress?: string): boolean {\n const portalClause = portalAddress != null ? \"AND portalAddress = ?\" : \"\";\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n retryCount = 0,\n lastError = NULL,\n nextRetryAt = NULL,\n lockedAt = NULL\n WHERE _id = ?\n AND status = 'failed'\n ${portalClause}\n `;\n const params = portalAddress != null ? [_id, portalAddress] : [_id];\n const result = QueryBuilder.execute(sql, params);\n if (result.changes > 0) {\n notifyNewEvent();\n }\n return result.changes > 0;\n }\n\n static resetAllFailedToPending(portalAddress?: string): number {\n const portalClause = portalAddress != null ? \"AND portalAddress = ?\" : \"\";\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n retryCount = 0,\n lastError = NULL,\n nextRetryAt = NULL,\n lockedAt = NULL\n WHERE status = 'failed'\n ${portalClause}\n `;\n const params = portalAddress != null ? [portalAddress] : [];\n const result = QueryBuilder.execute(sql, params);\n if (result.changes > 0) {\n notifyNewEvent();\n }\n return result.changes;\n }\n\n static resetStaleEvents(staleThreshold: number): number {\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n lockedAt = NULL,\n userOpHash = NULL,\n pendingPayload = NULL\n WHERE status = 'processing'\n AND lockedAt IS NOT NULL\n AND lockedAt < ?\n `;\n const result = QueryBuilder.execute(sql, [staleThreshold]);\n return result.changes;\n }\n\n static setEventPendingOp(_id: string, userOpHash: string, payload: Record<string, unknown>): void {\n const sql = `UPDATE ${this.TABLE} SET userOpHash = ?, pendingPayload = ? WHERE _id = ?`;\n QueryBuilder.execute(sql, [userOpHash, JSON.stringify(payload), _id]);\n }\n\n static clearEventPendingOp(_id: string): void {\n const sql = `UPDATE ${this.TABLE} SET userOpHash = NULL, pendingPayload = NULL WHERE _id = ?`;\n QueryBuilder.execute(sql, [_id]);\n }\n\n private static parseEvent(row: EventRow): Event {\n return {\n _id: row._id,\n type: row.type as EventType,\n timestamp: row.timestamp,\n fileId: row.fileId,\n portalAddress: row.portalAddress ?? \"\",\n status: row.status as EventStatus,\n retryCount: row.retryCount,\n lastError: row.lastError,\n lockedAt: row.lockedAt,\n nextRetryAt: row.nextRetryAt,\n userOpHash: row.userOpHash ?? null,\n pendingPayload: row.pendingPayload ?? null,\n };\n }\n}\n","import { EventEmitter } from \"events\";\n\nclass WorkerSignal extends EventEmitter {}\n\nconst workerSignal = new WorkerSignal();\nworkerSignal.setMaxListeners(20);\n\nexport function notifyNewEvent(): void {\n workerSignal.emit(\"newEvent\");\n}\n\nexport function onNewEvent(callback: () => void): () => void {\n workerSignal.on(\"newEvent\", callback);\n return () => workerSignal.off(\"newEvent\", callback);\n}\n","import { Hex } from \"viem\";\nimport { eciesDecrypt, eciesEncrypt, generateECKeyPair } from \"@fileverse/crypto/ecies\";\nimport { AuthTokenProvider } from \"./auth-token-provider\";\n\nexport class KeyStore {\n private portalKeySeed: Uint8Array | undefined;\n private portalAddress: Hex | undefined;\n\n constructor(\n seed: Uint8Array,\n address: Hex,\n private readonly authTokenProvider: AuthTokenProvider,\n ) {\n this.portalKeySeed = seed;\n this.portalAddress = address;\n this.authTokenProvider = authTokenProvider;\n }\n\n getPortalAddress() {\n if (!this.portalAddress) {\n throw new Error(\"Portal address is not set\");\n }\n return this.portalAddress;\n }\n\n private getAppEncryptionKey() {\n if (!this.portalKeySeed) {\n throw new Error(\"Portal key seed is not set\");\n }\n\n const keyPair = generateECKeyPair(this.portalKeySeed);\n return keyPair.publicKey;\n }\n\n private getAppDecryptionKey() {\n if (!this.portalKeySeed) {\n throw new Error(\"Portal key seed is not set\");\n }\n\n const keyPair = generateECKeyPair(this.portalKeySeed);\n return keyPair.privateKey;\n }\n\n encryptData(data: Uint8Array) {\n return eciesEncrypt(this.getAppEncryptionKey(), data);\n }\n\n decryptData(data: string) {\n return eciesDecrypt(this.getAppDecryptionKey(), data);\n }\n\n getAuthToken(audienceDid: string) {\n return this.authTokenProvider.getAuthToken(audienceDid);\n }\n}\n","import * as ucans from \"@ucans/ucans\";\nimport type { Hex } from \"viem\";\n\nexport class AuthTokenProvider {\n private readonly DEFAULT_OPTIONS = {\n namespace: \"file\",\n segment: \"CREATE\",\n scheme: \"storage\",\n };\n private keyPair: ucans.EdKeypair;\n portalAddress: Hex;\n constructor(keyPair: ucans.EdKeypair, portalAddress: Hex) {\n this.keyPair = keyPair;\n this.portalAddress = portalAddress;\n }\n\n async getAuthToken(\n audienceDid: string,\n options: { namespace: string; segment: string; scheme: string } = this.DEFAULT_OPTIONS,\n ): Promise<string> {\n const ucan = await ucans.build({\n audience: audienceDid,\n issuer: this.keyPair,\n lifetimeInSeconds: 7 * 86400,\n capabilities: [\n {\n with: {\n scheme: options.scheme,\n hierPart: this.portalAddress.toLocaleLowerCase(),\n },\n can: { namespace: options.namespace, segments: [options.segment] },\n },\n ],\n });\n\n return ucans.encode(ucan);\n }\n}\n","import { FilesModel, PortalsModel } from \"../../infra/database/models\";\nimport { logger } from \"../../infra\";\nimport { KeyStore } from \"../../sdk/key-store\";\nimport { AuthTokenProvider } from \"../../sdk/auth-token-provider\";\nimport { fromUint8Array, toUint8Array } from \"js-base64\";\nimport { Hex, stringToBytes } from \"viem\";\nimport { deriveHKDFKey } from \"@fileverse/crypto/kdf\";\nimport { generateKeyPairFromSeed } from \"@stablelib/ed25519\";\nimport * as ucans from \"@ucans/ucans\";\nimport { AgentClient } from \"../../sdk/smart-agent\";\nimport { FileManager } from \"../../sdk/file-manager\";\nimport { getRuntimeConfig } from \"../../config\";\n\nimport type { PublishResult } from \"../../types\";\n\ninterface PublishContext {\n file: ReturnType<typeof FilesModel.findByIdIncludingDeleted>;\n portalDetails: NonNullable<ReturnType<typeof PortalsModel.findByPortalAddress>>;\n apiKey: string;\n}\n\nfunction getPortalData(fileId: string): PublishContext {\n const file = FilesModel.findByIdIncludingDeleted(fileId);\n if (!file) {\n throw new Error(`File with _id ${fileId} not found`);\n }\n\n const portalDetails = PortalsModel.findByPortalAddress(file.portalAddress);\n if (!portalDetails) {\n throw new Error(`Portal with address ${file.portalAddress} not found`);\n }\n\n const apiKey = getRuntimeConfig().API_KEY;\n if (!apiKey) {\n throw new Error(\"API key is not set\");\n }\n\n return { file, portalDetails, apiKey };\n}\n\nfunction deriveCollaboratorKeys(apiKeySeed: Uint8Array) {\n const salt = new Uint8Array([0]);\n\n const privateAccountKey = deriveHKDFKey(apiKeySeed, salt, stringToBytes(\"COLLABORATOR_PRIVATE_KEY\"));\n\n const ucanDerivedSecret = deriveHKDFKey(apiKeySeed, salt, stringToBytes(\"COLLABORATOR_UCAN_SECRET\"));\n\n const { secretKey: ucanSecret } = generateKeyPairFromSeed(ucanDerivedSecret);\n\n return { privateAccountKey, ucanSecret };\n}\n\nconst createFileManager = async (\n portalSeed: string,\n portalAddress: Hex,\n ucanSecret: Uint8Array,\n privateAccountKey: Uint8Array,\n): Promise<FileManager> => {\n const keyPair = ucans.EdKeypair.fromSecretKey(fromUint8Array(ucanSecret), {\n exportable: true,\n });\n\n const authTokenProvider = new AuthTokenProvider(keyPair, portalAddress);\n const keyStore = new KeyStore(toUint8Array(portalSeed), portalAddress, authTokenProvider);\n\n const agentClient = new AgentClient(authTokenProvider);\n await agentClient.initializeAgentClient(privateAccountKey);\n\n return new FileManager(keyStore, agentClient);\n};\n\nconst executeOperation = async (\n fileManager: FileManager,\n file: any,\n operation: \"update\" | \"delete\",\n): Promise<PublishResult> => {\n\n if (operation === \"update\") {\n const result = await fileManager.updateFile(file);\n return { success: true, ...result };\n }\n\n if (operation === \"delete\") {\n const result = await fileManager.deleteFile(file);\n return { success: true, ...result };\n }\n\n throw new Error(`Invalid operation: ${operation}`);\n};\n\nexport const handleExistingFileOp = async (fileId: string, operation: \"update\" | \"delete\"): Promise<PublishResult> => {\n try {\n const { file, portalDetails, apiKey } = getPortalData(fileId);\n\n const apiKeySeed = toUint8Array(apiKey);\n const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);\n\n const fileManager = await createFileManager(\n portalDetails.portalSeed,\n portalDetails.portalAddress as Hex,\n ucanSecret,\n privateAccountKey,\n );\n\n return executeOperation(fileManager, file, operation);\n } catch (error: any) {\n logger.error(`Failed to publish file ${fileId}:`, error);\n throw error;\n }\n};\n\nexport const handleNewFileOp = async (\n fileId: string,\n): Promise<{\n userOpHash: string;\n linkKey: string;\n linkKeyNonce: string;\n commentKey: string;\n metadata: Record<string, unknown>;\n}> => {\n const { file, portalDetails, apiKey } = getPortalData(fileId);\n const apiKeySeed = toUint8Array(apiKey);\n const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);\n const fileManager = await createFileManager(\n portalDetails.portalSeed,\n portalDetails.portalAddress as Hex,\n ucanSecret,\n privateAccountKey,\n );\n return fileManager.submitAddFileTrx(file);\n};\n\nexport const getProxyAuthParams = async (\n fileId: string,\n): Promise<{\n authToken: string;\n portalAddress: Hex;\n invokerAddress: Hex;\n}> => {\n const { portalDetails, apiKey } = getPortalData(fileId);\n const apiKeySeed = toUint8Array(apiKey);\n const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);\n const fileManager = await createFileManager(\n portalDetails.portalSeed,\n portalDetails.portalAddress as Hex,\n ucanSecret,\n privateAccountKey,\n );\n return fileManager.getProxyAuthParams();\n};\n","import { Hex, toHex } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { getSmartAccountClient, getNonce, waitForUserOpReceipt } from \"./pimlico-utils\";\nimport { AuthTokenProvider } from \"./auth-token-provider\";\nimport { STATIC_CONFIG } from \"../cli/constants\";\nimport { createSmartAccountClient } from \"permissionless\";\nimport type { IExecuteUserOperationRequest } from \"../types\";\n\nexport type { IExecuteUserOperationRequest };\n\nexport class AgentClient {\n private smartAccountAgent: ReturnType<typeof createSmartAccountClient> | null = null;\n private readonly MAX_CALL_GAS_LIMIT = 500000;\n private readonly authOptions: {\n namespace: string;\n segment: string;\n scheme: string;\n } = { namespace: \"proxy\", segment: \"ACCESS\", scheme: \"pimlico\" };\n\n constructor(private readonly authTokenProvider: AuthTokenProvider) {\n this.authTokenProvider = authTokenProvider;\n }\n\n async initializeAgentClient(keyMaterial: Uint8Array) {\n const agentAccount = privateKeyToAccount(toHex(keyMaterial));\n const authToken = await this.authTokenProvider.getAuthToken(STATIC_CONFIG.PROXY_SERVER_DID, this.authOptions);\n const smartAccountClient = await getSmartAccountClient(\n agentAccount,\n authToken,\n this.authTokenProvider.portalAddress,\n );\n this.smartAccountAgent = smartAccountClient;\n }\n\n getSmartAccountAgent() {\n if (!this.smartAccountAgent) throw new Error(\"Agent client not initialized\");\n\n return this.smartAccountAgent;\n }\n\n getAgentAddress() {\n const smartAccountAgent = this.getSmartAccountAgent();\n if (!smartAccountAgent.account) throw new Error(\"Agent account not found\");\n return smartAccountAgent.account.address;\n }\n\n getAgentAccount() {\n const smartAccountAgent = this.getSmartAccountAgent();\n if (!smartAccountAgent.account) throw new Error(\"Agent account not found\");\n return smartAccountAgent.account;\n }\n\n destroyAgentClient() {\n this.smartAccountAgent = null;\n }\n\n async getCallData(request: IExecuteUserOperationRequest | IExecuteUserOperationRequest[]) {\n const agentAccount = this.getAgentAccount();\n if (Array.isArray(request)) {\n if (request.length === 0 || request.length > 10) throw new Error(\"Request length must be between 1 and 10\");\n\n const encodedCallData = request.map((req) => ({\n to: req.contractAddress,\n data: req.data,\n value: BigInt(0),\n }));\n\n return await agentAccount.encodeCalls(encodedCallData);\n }\n\n return await agentAccount.encodeCalls([\n {\n to: request.contractAddress,\n data: request.data,\n value: BigInt(0),\n },\n ]);\n }\n\n async sendUserOperation(\n request: IExecuteUserOperationRequest | IExecuteUserOperationRequest[],\n customGasLimit?: number,\n ) {\n try {\n const smartAccountAgent = this.getSmartAccountAgent();\n\n const callData = await this.getCallData(request);\n\n return await smartAccountAgent.sendUserOperation({\n callData,\n callGasLimit: BigInt(customGasLimit || this.MAX_CALL_GAS_LIMIT),\n nonce: getNonce(),\n });\n } catch (error) {\n throw error;\n }\n }\n\n async executeUserOperationRequest(\n request: IExecuteUserOperationRequest | IExecuteUserOperationRequest[],\n timeout: number,\n customGasLimit?: number,\n ) {\n const userOpHash = await this.sendUserOperation(request, customGasLimit);\n const { authToken, portalAddress, invokerAddress } = await this.getAuthParams();\n const receipt = await waitForUserOpReceipt(userOpHash, authToken, portalAddress, invokerAddress, timeout);\n if (!receipt.success) throw new Error(`Failed to execute user operation: ${receipt.reason}`);\n return receipt;\n }\n\n async getAuthParams(): Promise<{ authToken: string; portalAddress: Hex; invokerAddress: Hex }> {\n const authToken = await this.authTokenProvider.getAuthToken(STATIC_CONFIG.PROXY_SERVER_DID, this.authOptions);\n return {\n authToken,\n portalAddress: this.authTokenProvider.portalAddress,\n invokerAddress: this.getAgentAddress(),\n };\n }\n}\n","import { createPublicClient, http, hexToBigInt, toHex, toBytes, type PrivateKeyAccount, type Hex } from \"viem\";\n\nimport { createPimlicoClient } from \"permissionless/clients/pimlico\";\nimport { createSmartAccountClient } from \"permissionless\";\nimport { toSafeSmartAccount } from \"permissionless/accounts\";\nimport { entryPoint07Address } from \"viem/account-abstraction\";\nimport { CHAIN, getRpcUrl, getPimlicoUrl } from \"../constants\";\nimport { generatePrivateKey } from \"viem/accounts\";\n\nexport const getPublicClient = () =>\n createPublicClient({\n transport: http(getRpcUrl(), {\n retryCount: 0,\n }),\n chain: CHAIN,\n });\n\nexport const getPimlicoClient = (authToken: string, portalAddress: Hex, invokerAddress: Hex) =>\n createPimlicoClient({\n transport: http(getPimlicoUrl(), {\n retryCount: 0,\n fetchOptions: {\n headers: {\n Authorization: `Bearer ${authToken}`,\n contract: portalAddress,\n invoker: invokerAddress,\n },\n },\n }),\n entryPoint: {\n address: entryPoint07Address,\n version: \"0.7\",\n },\n });\n\nexport const signerToSmartAccount = async (signer: PrivateKeyAccount) =>\n await toSafeSmartAccount({\n client: getPublicClient(),\n owners: [signer],\n entryPoint: {\n address: entryPoint07Address,\n version: \"0.7\",\n },\n version: \"1.4.1\",\n });\n\nexport const getSmartAccountClient = async (signer: PrivateKeyAccount, authToken: string, portalAddress: Hex) => {\n const smartAccount = await signerToSmartAccount(signer);\n const pimlicoClient = getPimlicoClient(authToken, portalAddress, smartAccount.address);\n\n return createSmartAccountClient({\n account: smartAccount,\n chain: CHAIN,\n paymaster: pimlicoClient,\n bundlerTransport: http(getPimlicoUrl(), {\n fetchOptions: {\n headers: {\n Authorization: `Bearer ${authToken}`,\n contract: portalAddress,\n invoker: smartAccount.address,\n },\n },\n retryCount: 0,\n }),\n userOperation: {\n estimateFeesPerGas: async () => (await pimlicoClient.getUserOperationGasPrice()).fast,\n },\n });\n};\n\nexport const getNonce = () =>\n hexToBigInt(\n toHex(toBytes(generatePrivateKey()).slice(0, 24), {\n size: 32,\n }),\n );\n\nexport const waitForUserOpReceipt = async (\n hash: Hex,\n authToken: string,\n portalAddress: Hex,\n invokerAddress: Hex,\n timeout = 120000,\n) => {\n const pimlicoClient = getPimlicoClient(authToken, portalAddress, invokerAddress);\n return pimlicoClient.waitForUserOperationReceipt({\n hash,\n timeout,\n });\n};\n","export { sepolia, gnosis } from \"viem/chains\";\n","import { STATIC_CONFIG } from \"../cli/constants\";\nimport { getRuntimeConfig } from \"../config\";\nimport { gnosis, sepolia } from \"./chains\";\n\nexport const NETWORK_NAME = STATIC_CONFIG.NETWORK_NAME;\nexport const UPLOAD_SERVER_URL = STATIC_CONFIG.API_URL;\n\nexport const getRpcUrl = () => getRuntimeConfig().RPC_URL;\nexport const getPimlicoUrl = () => `${STATIC_CONFIG.PIMLICO_PROXY_URL}api/${NETWORK_NAME}/rpc`;\n\nconst CHAIN_MAP = {\n gnosis: gnosis,\n sepolia: sepolia,\n} as const;\n\nexport const CHAIN = CHAIN_MAP[NETWORK_NAME as keyof typeof CHAIN_MAP];\nexport { DELETED_FILE_EVENT, EDITED_FILE_EVENT, ADDED_FILE_EVENT } from \"./events\";\nexport { DELETED_FILE_ABI, EDIT_FILE_METHOD, ADD_FILE_METHOD } from \"./methods\";\n","import { fromUint8Array, toUint8Array } from \"js-base64\";\nimport { KeyStore } from \"./key-store\";\nimport {\n buildLinklock,\n encryptTitleWithFileKey,\n generateLinkKeyMaterial,\n prepareCallData,\n createEncryptedContentFile,\n buildFileMetadata,\n parseFileEventLog,\n uploadAllFilesToIPFS,\n UploadFileAuthParams,\n prepareDeleteFileCallData,\n} from \"./file-utils\";\nimport { AgentClient } from \"./smart-agent\";\nimport { generateAESKey, exportAESKey } from \"@fileverse/crypto/webcrypto\";\nimport { STATIC_CONFIG } from \"../cli/constants\";\nimport { DELETED_FILE_EVENT, EDITED_FILE_EVENT } from \"../constants\";\nimport { markdownToYjs } from \"@fileverse/content-processor\";\nimport { logger } from \"../infra\";\n\nexport class FileManager {\n private keyStore: KeyStore;\n private agentClient: AgentClient;\n\n constructor(keyStore: KeyStore, agentClient: AgentClient) {\n this.keyStore = keyStore;\n this.agentClient = agentClient;\n }\n\n private createLocks(key: string, encryptedSecretKey: string, commentKey: Uint8Array) {\n const appLock = {\n lockedFileKey: this.keyStore.encryptData(toUint8Array(key)),\n lockedLinkKey: this.keyStore.encryptData(toUint8Array(encryptedSecretKey)),\n lockedChatKey: this.keyStore.encryptData(commentKey),\n };\n return { appLock, ownerLock: { ...appLock } };\n }\n\n private async getAuthParams(): Promise<UploadFileAuthParams> {\n return {\n token: await this.keyStore.getAuthToken(STATIC_CONFIG.SERVER_DID),\n contractAddress: this.keyStore.getPortalAddress(),\n invoker: this.agentClient.getAgentAddress(),\n };\n }\n\n private async executeFileOperation(callData: `0x${string}`) {\n return this.agentClient.executeUserOperationRequest(\n {\n contractAddress: this.keyStore.getPortalAddress(),\n data: callData,\n },\n 1000000,\n );\n }\n\n private async sendFileOperation(callData: `0x${string}`) {\n return this.agentClient.sendUserOperation(\n {\n contractAddress: this.keyStore.getPortalAddress(),\n data: callData,\n },\n 1000000,\n );\n }\n\n async getProxyAuthParams() {\n return this.agentClient.getAuthParams();\n }\n\n async submitAddFileTrx(file: any) {\n logger.debug(`Preparing to add file ${file.ddocId}`);\n const { encryptedSecretKey, nonce, secretKey } = await generateLinkKeyMaterial({\n ddocId: file.ddocId,\n linkKey: file.linkKey,\n linkKeyNonce: file.linkKeyNonce,\n });\n\n const yJSContent = markdownToYjs(file.content);\n const { encryptedFile, key } = await createEncryptedContentFile(yJSContent);\n logger.debug(`Generated encrypted content file for file ${file.ddocId}`);\n const commentKey = await exportAESKey(await generateAESKey(128));\n\n const { appLock, ownerLock } = this.createLocks(key, encryptedSecretKey, commentKey);\n const linkLock = buildLinklock(secretKey, toUint8Array(key), commentKey);\n\n const encryptedTitle = await encryptTitleWithFileKey({\n title: file.title || \"Untitled\",\n key,\n });\n const metadata = buildFileMetadata({\n encryptedTitle,\n encryptedFileSize: encryptedFile.size,\n appLock,\n ownerLock,\n ddocId: file.ddocId,\n nonce: fromUint8Array(nonce),\n owner: this.agentClient.getAgentAddress(),\n });\n\n const authParams = await this.getAuthParams();\n const { metadataHash, contentHash, gateHash } = await uploadAllFilesToIPFS(\n { metadata, encryptedFile, linkLock, ddocId: file.ddocId },\n authParams,\n );\n logger.debug(`Uploaded files to IPFS for file ${file.ddocId}`);\n\n const callData = prepareCallData({\n metadataHash,\n contentHash,\n gateHash,\n appFileId: file.ddocId,\n fileId: file.fileId,\n });\n logger.debug(`Prepared call data for file ${file.ddocId}`);\n\n const userOpHash = await this.sendFileOperation(callData);\n logger.debug(`Submitted user op for file ${file.ddocId}`);\n return {\n userOpHash,\n linkKey: encryptedSecretKey,\n linkKeyNonce: fromUint8Array(nonce),\n commentKey: fromUint8Array(commentKey),\n metadata,\n };\n }\n\n async updateFile(file: any) {\n logger.debug(`Updating file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n const { encryptedSecretKey, nonce, secretKey } = await generateLinkKeyMaterial({\n ddocId: file.ddocId,\n linkKey: file.linkKey,\n linkKeyNonce: file.linkKeyNonce,\n });\n\n logger.debug(`Generating encrypted content file for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n\n const yjsContent = markdownToYjs(file.content);\n const { encryptedFile, key } = await createEncryptedContentFile(yjsContent);\n const commentKey = toUint8Array(file.commentKey);\n\n const { appLock, ownerLock } = this.createLocks(key, encryptedSecretKey, commentKey);\n const linkLock = buildLinklock(secretKey, toUint8Array(key), commentKey);\n\n const encryptedTitle = await encryptTitleWithFileKey({\n title: file.title || \"Untitled\",\n key,\n });\n const metadata = buildFileMetadata({\n encryptedTitle,\n encryptedFileSize: encryptedFile.size,\n appLock,\n ownerLock,\n ddocId: file.ddocId,\n nonce: fromUint8Array(nonce),\n owner: this.agentClient.getAgentAddress(),\n });\n\n const authParams = await this.getAuthParams();\n logger.debug(`Uploading files to IPFS for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n const { metadataHash, contentHash, gateHash } = await uploadAllFilesToIPFS(\n { metadata, encryptedFile, linkLock, ddocId: file.ddocId },\n authParams,\n );\n\n const callData = prepareCallData({\n metadataHash,\n contentHash,\n gateHash,\n appFileId: file.ddocId,\n fileId: file.onChainFileId,\n });\n logger.debug(`Executing file operation for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n\n const { logs } = await this.executeFileOperation(callData);\n const onChainFileId = parseFileEventLog(logs, \"EditedFile\", EDITED_FILE_EVENT);\n\n return { onChainFileId, metadata };\n }\n\n async deleteFile(file: any) {\n logger.debug(`Deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n const callData = prepareDeleteFileCallData({\n onChainFileId: file.onChainFileId,\n });\n logger.debug(`Prepared call data for deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n\n const { logs } = await this.executeFileOperation(callData);\n parseFileEventLog(logs, \"DeletedFile\", DELETED_FILE_EVENT);\n logger.debug(`Executed file operation for deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n return {\n fileId: file.id,\n onChainFileId: file.onChainFileId,\n metadata: file.metadata,\n };\n }\n}\n","import { getArgon2idHash } from \"@fileverse/crypto/argon\";\nimport { bytesToBase64, generateRandomBytes } from \"@fileverse/crypto/utils\";\nimport { derivePBKDF2Key, encryptAesCBC } from \"@fileverse/crypto/kdf\";\nimport { secretBoxEncrypt } from \"@fileverse/crypto/nacl\";\nimport hkdf from \"futoin-hkdf\";\n\nimport tweetnacl from \"tweetnacl\";\nimport { fromUint8Array, toUint8Array } from \"js-base64\";\nimport { gcmEncrypt } from \"./file-encryption\";\nimport { toAESKey, aesEncrypt } from \"@fileverse/crypto/webcrypto\";\nimport axios from \"axios\";\nimport { ADD_FILE_METHOD, DELETED_FILE_ABI, EDIT_FILE_METHOD, UPLOAD_SERVER_URL } from \"../constants\";\nimport type { UploadFileAuthParams, FileMetadataParams, UploadFilesParams } from \"../types\";\nimport { encodeFunctionData, type Hex, parseEventLogs, type Abi } from \"viem\";\n\ninterface LinkKeyMaterialParams {\n ddocId: string;\n linkKey: string | undefined;\n linkKeyNonce: string | undefined;\n}\n\nconst deriveKeyFromAg2Hash = async (pass: string, salt: Uint8Array) => {\n const key = await getArgon2idHash(pass, salt);\n\n return hkdf(Buffer.from(key), tweetnacl.secretbox.keyLength, {\n info: Buffer.from(\"encryptionKey\"),\n });\n};\n\nconst decryptSecretKey = async (docId: string, nonce: string, encryptedSecretKey: string) => {\n const derivedKey = await deriveKeyFromAg2Hash(docId, toUint8Array(nonce));\n\n return tweetnacl.secretbox.open(toUint8Array(encryptedSecretKey), toUint8Array(nonce), derivedKey);\n};\n\nconst getExistingEncryptionMaterial = async (\n existingEncryptedSecretKey: string,\n existingNonce: string,\n docId: string,\n) => {\n const secretKey = await decryptSecretKey(docId, existingNonce, existingEncryptedSecretKey);\n return {\n encryptedSecretKey: existingEncryptedSecretKey,\n nonce: toUint8Array(existingNonce),\n secretKey,\n };\n};\n\nconst getNaclSecretKey = async (ddocId: string) => {\n const { secretKey } = tweetnacl.box.keyPair();\n const nonce = tweetnacl.randomBytes(tweetnacl.secretbox.nonceLength);\n\n const derivedKey = await deriveKeyFromAg2Hash(ddocId, nonce);\n\n const encryptedSecretKey = fromUint8Array(tweetnacl.secretbox(secretKey, nonce, derivedKey), true);\n\n return { nonce, encryptedSecretKey, secretKey };\n};\n\nexport const generateLinkKeyMaterial = async (params: LinkKeyMaterialParams) => {\n if (params.linkKeyNonce && params.linkKey) {\n const { encryptedSecretKey, nonce, secretKey } = await getExistingEncryptionMaterial(\n params.linkKey,\n params.linkKeyNonce,\n params.ddocId,\n );\n if (secretKey) return { encryptedSecretKey, nonce, secretKey };\n }\n const { secretKey, nonce, encryptedSecretKey } = await getNaclSecretKey(params.ddocId);\n\n return { secretKey, nonce, encryptedSecretKey };\n};\n\nexport const jsonToFile = (json: any, fileName: string) => {\n const blob = new Blob([JSON.stringify(json)], {\n type: \"application/json\",\n });\n\n const file = new File([blob], fileName, {\n type: \"application/json\",\n });\n\n return file;\n};\n\nconst appendAuthTagIvToBlob = async (blob: Blob, authTag: Uint8Array, iv: Uint8Array) => {\n const encryptedFileBytes = await blob.arrayBuffer();\n const encryptedBytes = new Uint8Array(encryptedFileBytes);\n const combinedLength = encryptedBytes.length + authTag.length + iv.length;\n const combinedArray = new Uint8Array(combinedLength);\n\n let offset = 0;\n combinedArray.set(encryptedBytes, offset);\n offset += encryptedBytes.length;\n\n combinedArray.set(authTag, offset);\n offset += authTag.length;\n\n combinedArray.set(iv, offset);\n\n return new Blob([combinedArray], { type: blob.type });\n};\n\nexport const encryptFile = async (file: File) => {\n const arrayBuffer = await file.arrayBuffer();\n\n const plaintext = new Uint8Array(arrayBuffer);\n\n const { ciphertext, authTag, key, iv } = gcmEncrypt(plaintext);\n\n const encryptedBlob = new Blob([ciphertext], { type: file.type });\n\n const encryptedBlobWithAuthTagIv = await appendAuthTagIvToBlob(\n encryptedBlob,\n toUint8Array(authTag),\n toUint8Array(iv),\n );\n\n return {\n encryptedFile: new File([encryptedBlobWithAuthTagIv], file.name),\n key,\n };\n};\n\nexport const getNonceAppendedCipherText = (nonce: Uint8Array, cipherText: Uint8Array) => {\n return fromUint8Array(nonce, true) + \"__n__\" + fromUint8Array(cipherText, true);\n};\n\nexport const jsonToBytes = (json: Record<string, any>) => new TextEncoder().encode(JSON.stringify(json));\n\nexport const buildLinklock = (key: Uint8Array, fileKey: Uint8Array, commentKey: Uint8Array) => {\n const ikm = generateRandomBytes();\n const kdfSalt = generateRandomBytes();\n const derivedEphermalKey = derivePBKDF2Key(ikm, kdfSalt);\n\n const { iv, cipherText } = encryptAesCBC(\n {\n key: derivedEphermalKey,\n message: fileKey,\n },\n \"base64\",\n );\n\n const { iv: commentIv, cipherText: commentCipherText } = encryptAesCBC(\n {\n key: derivedEphermalKey,\n message: commentKey,\n },\n \"base64\",\n );\n\n const encryptedIkm = secretBoxEncrypt(ikm, key);\n\n const lockedFileKey = iv + \"__n__\" + cipherText;\n\n const lockedChatKey = commentIv + \"__n__\" + commentCipherText;\n\n const keyMaterial = bytesToBase64(kdfSalt) + \"__n__\" + encryptedIkm;\n\n const fileKeyNonce = generateRandomBytes(24);\n const encryptedFileKey = tweetnacl.secretbox(jsonToBytes({ key: fromUint8Array(fileKey) }), fileKeyNonce, key);\n\n const chatKeyNonce = generateRandomBytes(24);\n const encryptedChatKey = tweetnacl.secretbox(commentKey, chatKeyNonce, key);\n\n return {\n lockedFileKey: getNonceAppendedCipherText(fileKeyNonce, encryptedFileKey),\n lockedChatKey: getNonceAppendedCipherText(chatKeyNonce, encryptedChatKey),\n lockedFileKey_v2: lockedFileKey,\n lockedChatKey_v2: lockedChatKey,\n keyMaterial,\n };\n};\n\nexport const encryptTitleWithFileKey = async (args: { title: string; key: string }) => {\n const key = await toAESKey(toUint8Array(args.key));\n if (!key) throw new Error(\"Key is undefined\");\n\n const titleBytes = new TextEncoder().encode(args.title);\n\n const encryptedTitle = await aesEncrypt(key, titleBytes, \"base64\");\n\n return encryptedTitle;\n};\n\ninterface UploadFileParams {\n file: File;\n ipfsType: string;\n appFileId: string;\n}\n\nexport type { UploadFileAuthParams };\n\nexport const uploadFileToIPFS = async (fileParams: UploadFileParams, authParams: UploadFileAuthParams) => {\n const { file, ipfsType, appFileId } = fileParams;\n const { token, invoker, contractAddress } = authParams;\n\n const body = new FormData();\n body.append(\"file\", file);\n body.append(\"ipfsType\", ipfsType);\n body.append(\"appFileId\", appFileId);\n\n body.append(\"sourceApp\", \"ddoc\");\n const uploadEndpoint = UPLOAD_SERVER_URL + \"upload\";\n const response = await axios.post(uploadEndpoint, body, {\n headers: {\n Authorization: `Bearer ${token}`,\n contract: contractAddress,\n invoker: invoker,\n chain: process.env.chainId,\n },\n });\n\n return response.data.ipfsHash;\n};\n\nconst getEditFileTrxCalldata = (args: {\n fileId: number;\n appFileId: string;\n metadataHash: string;\n contentHash: string;\n gateHash: string;\n}) => {\n return encodeFunctionData({\n abi: EDIT_FILE_METHOD,\n functionName: \"editFile\",\n args: [BigInt(args.fileId), args.appFileId, args.metadataHash, args.contentHash, args.gateHash, 2, BigInt(0)],\n });\n};\n\nconst getAddFileTrxCalldata = (args: {\n appFileId: string;\n metadataHash: string;\n contentHash: string;\n gateHash: string;\n}) => {\n return encodeFunctionData({\n abi: ADD_FILE_METHOD,\n functionName: \"addFile\",\n args: [args.appFileId, 2, args.metadataHash, args.contentHash, args.gateHash, BigInt(0)],\n });\n};\n\nexport const prepareCallData = (args: {\n metadataHash: string;\n contentHash: string;\n gateHash: string;\n appFileId: string;\n fileId?: number;\n}) => {\n if (args.fileId) {\n return getEditFileTrxCalldata({\n fileId: args.fileId,\n appFileId: args.appFileId,\n metadataHash: args.metadataHash,\n contentHash: args.contentHash,\n gateHash: args.gateHash,\n });\n }\n return getAddFileTrxCalldata(args);\n};\n\nexport const prepareDeleteFileCallData = (args: { onChainFileId: number }) => {\n return encodeFunctionData({\n abi: DELETED_FILE_ABI,\n functionName: \"deleteFile\",\n args: [BigInt(args.onChainFileId)],\n });\n};\n\nexport const createEncryptedContentFile = async (content: any) => {\n const contentFile = jsonToFile(\n { file: content, source: \"ddoc\" },\n `${fromUint8Array(generateRandomBytes(16))}-CONTENT`,\n );\n return encryptFile(contentFile);\n};\n\nexport type { FileMetadataParams };\n\nexport const buildFileMetadata = (params: FileMetadataParams) => ({\n title: params.encryptedTitle,\n size: params.encryptedFileSize,\n mimeType: \"application/json\",\n appLock: params.appLock,\n ownerLock: params.ownerLock,\n ddocId: params.ddocId,\n nonce: params.nonce,\n owner: params.owner,\n version: \"4\",\n sourceApp: \"fileverse-api\",\n});\n\nexport const parseFileEventLog = (logs: any[], eventName: string, abi: Abi): number => {\n const [parsedLog] = parseEventLogs({ abi, logs, eventName });\n\n if (!parsedLog) throw new Error(`${eventName} event not found`);\n\n const fileId = (parsedLog as any).args.fileId;\n\n if (fileId === undefined || fileId === null) throw new Error(\"FileId not found in event logs\");\n\n return Number(fileId);\n};\n\nexport type { UploadFilesParams };\n\nexport const uploadAllFilesToIPFS = async (params: UploadFilesParams, authParams: UploadFileAuthParams) => {\n const { metadata, encryptedFile, linkLock, ddocId } = params;\n\n const [metadataHash, contentHash, gateHash] = await Promise.all([\n uploadFileToIPFS(\n {\n file: jsonToFile(metadata, `${fromUint8Array(generateRandomBytes(16))}-METADATA`),\n ipfsType: \"METADATA\",\n appFileId: ddocId,\n },\n authParams,\n ),\n uploadFileToIPFS(\n {\n file: encryptedFile,\n ipfsType: \"CONTENT\",\n appFileId: ddocId,\n },\n authParams,\n ),\n uploadFileToIPFS(\n {\n file: jsonToFile(linkLock, `${fromUint8Array(generateRandomBytes(16))}-GATE`),\n ipfsType: \"GATE\",\n appFileId: ddocId,\n },\n authParams,\n ),\n ]);\n\n return { metadataHash, contentHash, gateHash };\n};\n","import { gcm } from \"@noble/ciphers/aes.js\";\nimport { generateRandomBytes } from \"@fileverse/crypto/utils\";\n\nconst KEY_LEN = 32;\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nconst b64ToBytes = (b64: string) => Uint8Array.from(Buffer.from(b64, \"base64\"));\nconst bytesToB64 = (b: Uint8Array) => Buffer.from(b).toString(\"base64\");\n\nimport type { DecryptionOptions } from \"../types\";\nexport type { DecryptionOptions };\n\nexport function gcmEncrypt(plaintext: Uint8Array) {\n const key = generateRandomBytes(KEY_LEN);\n const iv = generateRandomBytes(IV_LEN);\n if (key.length !== KEY_LEN) throw new Error(\"key must be 32 bytes\");\n if (iv.length !== IV_LEN) throw new Error(\"iv must be 12 bytes\");\n\n const out = gcm(key, iv).encrypt(plaintext);\n const ciphertext = out.subarray(0, out.length - TAG_LEN);\n const authTag = out.subarray(out.length - TAG_LEN);\n\n return {\n ciphertext,\n authTag: bytesToB64(authTag),\n key: bytesToB64(key),\n iv: bytesToB64(iv),\n };\n}\n\nexport function gcmDecrypt(ciphertext: Uint8Array, opts: DecryptionOptions) {\n const key = b64ToBytes(opts.key);\n const iv = b64ToBytes(opts.iv);\n const tag = b64ToBytes(opts.authTag);\n if (key.length !== KEY_LEN) throw new Error(\"key must be 32 bytes\");\n if (iv.length !== IV_LEN) throw new Error(\"iv must be 12 bytes\");\n if (tag.length !== TAG_LEN) throw new Error(\"authTag must be 16 bytes\");\n\n const combined = new Uint8Array(ciphertext.length + TAG_LEN);\n combined.set(ciphertext, 0);\n combined.set(tag, ciphertext.length);\n\n return gcm(key, iv).decrypt(combined);\n}\n","import { HttpRequestError } from \"viem\";\n\nexport class RateLimitError extends Error {\n readonly retryAfterSeconds: number;\n\n constructor(retryAfterSeconds: number, message = \"Rate limit exceeded\") {\n super(message);\n this.name = \"RateLimitError\";\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\nconst MAX_RETRY_AFTER_SECONDS = 300;\nconst DEFAULT_RETRY_AFTER_SECONDS = 3600;\n\nfunction parseRetryAfterRaw(raw: string | null): number {\n if (!raw) return DEFAULT_RETRY_AFTER_SECONDS;\n const parsed = parseInt(raw, 10);\n if (!Number.isNaN(parsed) && parsed >= 0) return Math.min(parsed, MAX_RETRY_AFTER_SECONDS);\n const date = Date.parse(raw);\n if (!Number.isNaN(date)) {\n const seconds = Math.max(0, Math.ceil((date - Date.now()) / 1000));\n return Math.min(seconds, MAX_RETRY_AFTER_SECONDS);\n }\n return DEFAULT_RETRY_AFTER_SECONDS;\n}\n\nexport const parseRetryAfterSeconds = (response: Response): number =>\n parseRetryAfterRaw(response.headers.get(\"Retry-After\"));\n\nexport const parseRetryAfterFromHeaders = (headers?: Headers): number =>\n parseRetryAfterRaw(headers?.get(\"Retry-After\") ?? null);\n\nexport function normalizeRateLimitError(error: unknown): unknown {\n if (!(error instanceof HttpRequestError) || error.status !== 429) return error;\n const retryAfter = parseRetryAfterFromHeaders(error.headers);\n const message = \"Beta API rate limit reached. Try again in an hour please!\"\n return new RateLimitError(retryAfter, message);\n}\n","import { logger } from \"../index\";\nimport { processEvent } from \"./eventProcessor\";\nimport { onNewEvent } from \"./workerSignal\";\nimport { EventsModel } from \"../database/models\";\nimport type { Event } from \"../database/models\";\nimport { RateLimitError } from \"../../errors/rate-limit\";\n\nconst DEFAULT_CONCURRENCY = 5;\nconst STALE_THRESHOLD_MS = 5 * 60 * 1000;\nconst SIGNAL_RETRY_DELAY_MS = 50;\nconst FALLBACK_POLL_MS = 30000;\nconst MAX_RETRIES = 10;\n\nexport class FileEventsWorker {\n private isRunning = false;\n private concurrency: number;\n private activeProcessors = new Map<string, Promise<void>>();\n private signalCleanup: (() => void) | null = null;\n private pendingSignal = false;\n private wakeResolver: (() => void) | null = null;\n\n constructor(concurrency: number = DEFAULT_CONCURRENCY) {\n this.concurrency = concurrency;\n }\n\n start(): void {\n if (this.isRunning) {\n logger.warn(\"Worker is already running\");\n return;\n }\n this.isRunning = true;\n\n const staleCount = this.recoverStaleEvents();\n if (staleCount > 0) {\n logger.info(`Recovered ${staleCount} stale event(s)`);\n }\n\n this.signalCleanup = onNewEvent(() => {\n this.pendingSignal = true;\n this.wakeUp();\n });\n\n logger.debug(`File events worker started (concurrency: ${this.concurrency})`);\n this.run();\n }\n\n private async run(): Promise<void> {\n while (this.isRunning) {\n const foundEvents = await this.fillSlots();\n logger.debug(`Found ${foundEvents ? \"events\" : \"no events\"} to process`);\n if (this.activeProcessors.size === 0) {\n if (this.pendingSignal && !foundEvents) {\n this.pendingSignal = false;\n await this.sleep(SIGNAL_RETRY_DELAY_MS);\n continue;\n }\n\n this.pendingSignal = false;\n await this.waitForSignalOrTimeout(FALLBACK_POLL_MS);\n } else {\n await Promise.race(this.activeProcessors.values());\n }\n }\n }\n\n private async fillSlots(): Promise<boolean> {\n let foundAny = false;\n\n while (this.activeProcessors.size < this.concurrency && this.isRunning) {\n const lockedFileIds = Array.from(this.activeProcessors.keys());\n const event = EventsModel.findNextEligible(lockedFileIds);\n\n if (!event) break;\n\n foundAny = true;\n EventsModel.markProcessing(event._id);\n const processor = this.processEventWrapper(event);\n this.activeProcessors.set(event.fileId, processor);\n }\n\n logger.debug(`Slots filled: ${this.activeProcessors.size}`);\n return foundAny;\n }\n\n private async processEventWrapper(event: Event): Promise<void> {\n try {\n const result = await processEvent(event);\n if (result.success) {\n EventsModel.markProcessed(event._id);\n } else {\n this.handleFailure(event, result.error);\n }\n } catch (err) {\n this.handleFailure(event, err);\n } finally {\n this.activeProcessors.delete(event.fileId);\n }\n }\n\n private handleFailure(event: Event, error: unknown): void {\n const errorMsg = error instanceof Error ? error.message : String(error);\n if (error instanceof RateLimitError) {\n const retryAfterMs = error.retryAfterSeconds * 1000;\n EventsModel.scheduleRetryAfter(event._id, errorMsg, retryAfterMs);\n logger.warn(`Event ${event._id} rate limited; retry after ${error.retryAfterSeconds}s`);\n return;\n }\n if (event.retryCount < MAX_RETRIES) {\n EventsModel.scheduleRetry(event._id, errorMsg);\n logger.warn(`Event ${event._id} failed (retry ${event.retryCount + 1}/${MAX_RETRIES}): ${errorMsg}`);\n } else {\n EventsModel.markFailed(event._id, errorMsg);\n logger.error(`Event ${event._id} permanently failed after ${MAX_RETRIES} retries: ${errorMsg}`);\n }\n }\n\n private recoverStaleEvents(): number {\n const staleThreshold = Date.now() - STALE_THRESHOLD_MS;\n return EventsModel.resetStaleEvents(staleThreshold);\n }\n\n private wakeUp(): void {\n if (this.wakeResolver) {\n this.wakeResolver();\n this.wakeResolver = null;\n }\n }\n\n private waitForSignalOrTimeout(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timeout = setTimeout(() => {\n this.wakeResolver = null;\n resolve();\n }, ms);\n\n this.wakeResolver = () => {\n clearTimeout(timeout);\n resolve();\n };\n });\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n async close(): Promise<void> {\n if (!this.isRunning) {\n return;\n }\n logger.info(\"Closing worker gracefully...\");\n this.isRunning = false;\n\n if (this.signalCleanup) {\n this.signalCleanup();\n this.signalCleanup = null;\n }\n\n this.wakeUp();\n this.wakeResolver = null;\n\n if (this.activeProcessors.size > 0) {\n logger.info(`Waiting for ${this.activeProcessors.size} active processor(s) to complete...`);\n await Promise.all(this.activeProcessors.values());\n }\n\n logger.info(\"Worker closed\");\n }\n\n isActive(): boolean {\n return this.isRunning;\n }\n\n getActiveCount(): number {\n return this.activeProcessors.size;\n }\n}\n\nexport function createWorker(concurrency: number = DEFAULT_CONCURRENCY): FileEventsWorker {\n return new FileEventsWorker(concurrency);\n}\n","import { createWorker, type FileEventsWorker } from \"./infra/worker\";\n\nconst DEFAULT_CONCURRENCY = 5;\n\nlet worker: FileEventsWorker | null = null;\n\nexport function startWorker(concurrency: number = DEFAULT_CONCURRENCY): void {\n if (worker?.isActive()) {\n return;\n }\n worker = createWorker(concurrency);\n worker.start();\n}\n\nexport async function closeWorker(): Promise<void> {\n if (worker) {\n await worker.close();\n worker = null;\n }\n}\n\nexport function isWorkerActive(): boolean {\n return worker?.isActive() ?? false;\n}\n\nexport function getWorkerActiveCount(): number {\n return worker?.getActiveCount() ?? 0;\n}\n","// Error reporting service\n// Example: Slack, Sentry, etc.\n\nclass Reporter {\n async reportError(message: string): Promise<void> {\n // Implement your error reporting logic\n console.error(\"Error reported:\", message);\n }\n}\n\nexport default new Reporter();\n","/**\n * Default limit for listing files/ddocs\n * Used by both API and CLI to ensure consistent behavior\n */\nexport const DEFAULT_LIST_LIMIT = 10;\n","import { databaseConnectionManager } from \"./connection\";\nimport type { QueryOptions } from \"../../types\";\nimport { DEFAULT_LIST_LIMIT } from \"../../domain/file/constants\";\n\nfunction getDb() {\n return databaseConnectionManager.getConnection();\n}\n\nexport class QueryBuilder {\n static select<T = any>(sql: string, params: any[] = []): T[] {\n const stmt = getDb().prepare(sql);\n return stmt.all(params) as T[];\n }\n\n static selectOne<T = any>(sql: string, params: any[] = []): T | undefined {\n const stmt = getDb().prepare(sql);\n return stmt.get(params) as T | undefined;\n }\n\n static execute(\n sql: string,\n params: any[] = [],\n ): {\n changes: number;\n lastInsertRowid: number | bigint;\n } {\n const stmt = getDb().prepare(sql);\n const result = stmt.run(params);\n return {\n changes: result.changes,\n lastInsertRowid: result.lastInsertRowid,\n };\n }\n\n static transaction<T>(callback: () => T): T {\n return getDb().transaction(callback)();\n }\n\n static paginate(sql: string, options: QueryOptions = {}): string {\n let query = sql;\n\n if (options.orderBy) {\n query += ` ORDER BY ${options.orderBy} ${options.orderDirection || \"ASC\"}`;\n }\n\n const hasOffset = (options.offset ?? 0) > 0;\n const limit = options.limit ?? (hasOffset ? DEFAULT_LIST_LIMIT : undefined);\n\n if (limit) {\n query += ` LIMIT ${limit}`;\n }\n\n if (hasOffset) {\n query += ` OFFSET ${options.offset}`;\n }\n\n return query;\n }\n}\n","import { databaseConnectionManager } from \"./connection\";\nimport { QueryBuilder } from \"./query-builder\";\n\nfunction getDb() {\n return databaseConnectionManager.getConnection();\n}\n\nconst closeDatabase = async (): Promise<void> => {\n await databaseConnectionManager.close();\n};\n\nexport default getDb;\nexport { getDb, closeDatabase, QueryBuilder };\n","import getDb from \"../index\";\nimport { logger } from \"../../\";\n\nconst STABLE_SCHEMA = `\nCREATE TABLE IF NOT EXISTS files (\n _id TEXT PRIMARY KEY,\n ddocId TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n localVersion INTEGER NOT NULL DEFAULT 1,\n onchainVersion INTEGER NOT NULL DEFAULT 0,\n syncStatus TEXT NOT NULL DEFAULT 'pending',\n createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,\n updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,\n isDeleted INTEGER NOT NULL DEFAULT 0,\n portalAddress TEXT NOT NULL,\n metadata TEXT DEFAULT '{}',\n onChainFileId INTEGER,\n commentKey TEXT,\n linkKey TEXT,\n linkKeyNonce TEXT,\n link TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_files_createdAt ON files(createdAt);\nCREATE INDEX IF NOT EXISTS idx_files_syncStatus ON files(syncStatus);\nCREATE INDEX IF NOT EXISTS idx_files_title ON files(title);\nCREATE INDEX IF NOT EXISTS idx_files_portalAddress ON files(portalAddress);\n\nCREATE TABLE IF NOT EXISTS portals (\n _id TEXT PRIMARY KEY,\n portalAddress TEXT NOT NULL UNIQUE,\n portalSeed TEXT NOT NULL UNIQUE,\n ownerAddress TEXT NOT NULL,\n createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,\n updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS api_keys (\n _id TEXT PRIMARY KEY,\n apiKeySeed TEXT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n collaboratorAddress TEXT NOT NULL UNIQUE,\n portalAddress TEXT NOT NULL,\n createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,\n isDeleted INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS events (\n _id TEXT PRIMARY KEY,\n type TEXT NOT NULL CHECK (type IN ('create', 'update', 'delete')),\n timestamp INTEGER NOT NULL,\n fileId TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'processed', 'failed')),\n retryCount INTEGER NOT NULL DEFAULT 0,\n lastError TEXT,\n lockedAt INTEGER,\n nextRetryAt INTEGER,\n userOpHash TEXT,\n pendingPayload TEXT,\n portalAddress TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_events_pending_eligible ON events (status, nextRetryAt, timestamp) WHERE status = 'pending';\nCREATE INDEX IF NOT EXISTS idx_events_file_pending_ts ON events (fileId, status, timestamp) WHERE status = 'pending';\nCREATE INDEX IF NOT EXISTS idx_events_processing_locked ON events (status, lockedAt) WHERE status = 'processing';\nCREATE INDEX IF NOT EXISTS idx_events_failed_portal ON events (portalAddress, status) WHERE status = 'failed';\n\nCREATE TABLE IF NOT EXISTS folders (\n _id TEXT PRIMARY KEY,\n onchainFileId INTEGER NOT NULL,\n folderId TEXT NOT NULL,\n folderRef TEXT NOT NULL,\n folderName TEXT NOT NULL,\n portalAddress TEXT NOT NULL,\n metadataIPFSHash TEXT NOT NULL,\n contentIPFSHash TEXT NOT NULL,\n isDeleted INTEGER NOT NULL DEFAULT 0,\n lastTransactionHash TEXT,\n lastTransactionBlockNumber INTEGER NOT NULL,\n lastTransactionBlockTimestamp INTEGER NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\nCREATE INDEX IF NOT EXISTS idx_folders_folderRef_folderId ON folders(folderRef, folderId);\nCREATE INDEX IF NOT EXISTS idx_folders_folderRef ON folders(folderRef);\nCREATE INDEX IF NOT EXISTS idx_folders_created_at ON folders(created_at);\n`;\n\nexport function runMigrations(): void {\n const db = getDb();\n db.exec(STABLE_SCHEMA);\n logger.debug(\"Database schema ready\");\n}\n","import { Command } from \"commander\";\nimport Table from \"cli-table3\";\nimport { listFiles } from \"../domain/file\";\nimport type { GetFileResult } from \"../types\";\nimport { formatDate, getElapsedTime, columnNames, columnWidth, validateApiKey } from \"./utils/util\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\n\nexport const listCommand = new Command()\n .name(\"list\")\n .description(\"List all ddocs\")\n .option(\"-l, --limit <number>\", \"Limit the number of results\", parseInt)\n .option(\"-s, --skip <number>\", \"Skip the first N results\", parseInt)\n .action(async (options) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n const params = {\n limit: options.limit,\n skip: options.skip,\n portalAddress,\n };\n\n const result = listFiles(params);\n if (result.ddocs.length === 0) {\n console.log(\"No ddocs found.\");\n return;\n }\n\n const table = new Table({\n head: [\n columnNames.index,\n columnNames.ddocId,\n columnNames.title,\n columnNames.status,\n columnNames.local,\n columnNames.onchain,\n columnNames.created,\n columnNames.lastModified,\n ],\n colWidths: [\n columnWidth[columnNames.index],\n columnWidth[columnNames.ddocId],\n columnWidth[columnNames.title],\n columnWidth[columnNames.status],\n columnWidth[columnNames.local],\n columnWidth[columnNames.onchain],\n columnWidth[columnNames.created],\n columnWidth[columnNames.lastModified],\n ],\n style: { head: [] },\n });\n\n result.ddocs.forEach((ddoc: GetFileResult, index: number) => {\n const ddocId = (ddoc as any).ddocId || \"N/A\";\n table.push([\n index + 1,\n ddocId,\n ddoc.title.length > 23 ? ddoc.title.substring(0, 20) + \"...\" : ddoc.title,\n ddoc.syncStatus,\n ddoc.localVersion,\n ddoc.onchainVersion,\n formatDate(ddoc.createdAt),\n getElapsedTime(ddoc.updatedAt),\n ]);\n });\n\n console.log(`\\nFound ${result.total} ddoc(s):\\n`);\n console.log(table.toString());\n if (result.hasNext) {\n console.log(\"\\n(More results available. Use --skip and --limit for pagination)\");\n }\n } catch (error: any) {\n console.error(\"Error listing ddocs:\", error.message);\n throw error;\n }\n });\n","import { generate } from \"short-uuid\";\n\nimport { EventsModel, FilesModel } from \"../../infra/database/models\";\nimport type {\n File,\n ListFilesParams,\n ListFilesResult,\n CreateFileInput,\n UpdateFileInput,\n UpdateFilePayload,\n GetFileResult,\n} from \"../../types\";\nimport { DEFAULT_LIST_LIMIT } from \"./constants\";\n\nfunction listFiles(params: ListFilesParams): ListFilesResult {\n const { limit, skip, portalAddress } = params;\n const effectiveLimit = limit || DEFAULT_LIST_LIMIT;\n\n const result = FilesModel.findAll(portalAddress, effectiveLimit, skip);\n\n const processedFiles = result.files.map((file) => ({\n ddocId: file.ddocId,\n link: file.link,\n title: file.title,\n content: file.content,\n localVersion: file.localVersion,\n onchainVersion: file.onchainVersion,\n syncStatus: file.syncStatus,\n isDeleted: file.isDeleted,\n onChainFileId: file.onChainFileId,\n portalAddress: file.portalAddress,\n createdAt: file.createdAt,\n updatedAt: file.updatedAt,\n }));\n\n return {\n ddocs: processedFiles,\n total: result.total,\n hasNext: result.hasNext,\n };\n}\n\nfunction getFile(ddocId: string, portalAddress: string): GetFileResult | null {\n if (!ddocId) {\n throw new Error(\"ddocId is required\");\n }\n\n const file = FilesModel.findByDDocId(ddocId, portalAddress);\n\n if (!file) {\n return null;\n }\n\n return {\n ddocId: file.ddocId,\n link: file.link || \"\",\n title: file.title,\n content: file.content,\n localVersion: file.localVersion,\n onchainVersion: file.onchainVersion,\n syncStatus: file.syncStatus,\n isDeleted: file.isDeleted,\n onChainFileId: file.onChainFileId,\n portalAddress: file.portalAddress,\n createdAt: file.createdAt,\n updatedAt: file.updatedAt,\n };\n}\n\nconst createFile = async (input: CreateFileInput): Promise<File> => {\n if (!input.title || !input.content || !input.portalAddress) {\n throw new Error(\"title, content, and portalAddress are required\");\n }\n\n const ddocId = generate();\n const file = FilesModel.create({\n title: input.title,\n content: input.content,\n ddocId: ddocId,\n portalAddress: input.portalAddress,\n });\n\n EventsModel.create({ type: \"create\", fileId: file._id, portalAddress: file.portalAddress });\n return file;\n};\n\nconst updateFile = async (ddocId: string, payload: UpdateFileInput, portalAddress: string): Promise<Partial<File>> => {\n if (!ddocId) {\n throw new Error(\"ddocId is required\");\n }\n\n if (!payload.title && !payload.content) {\n throw new Error(\"At least one field is required: Either provide title, content, or both\");\n }\n\n const existingFile = FilesModel.findByDDocId(ddocId, portalAddress);\n if (!existingFile) {\n throw new Error(`File with ddocId ${ddocId} not found`);\n }\n\n const updatePayload: UpdateFilePayload = {\n ...payload,\n localVersion: existingFile.localVersion + 1,\n syncStatus: \"pending\", // since the update is done in local db, it's not on the chain yet. hence pending\n };\n const updatedFile = FilesModel.update(existingFile._id, updatePayload, portalAddress);\n\n EventsModel.create({ type: \"update\", fileId: updatedFile._id, portalAddress: updatedFile.portalAddress });\n return {\n ddocId: updatedFile.ddocId,\n link: updatedFile.link,\n title: updatedFile.title,\n content: updatedFile.content,\n localVersion: updatedFile.localVersion,\n onchainVersion: updatedFile.onchainVersion,\n syncStatus: updatedFile.syncStatus,\n isDeleted: updatedFile.isDeleted,\n onChainFileId: updatedFile.onChainFileId,\n portalAddress: updatedFile.portalAddress,\n createdAt: updatedFile.createdAt,\n updatedAt: updatedFile.updatedAt,\n };\n};\n\nconst deleteFile = async (ddocId: string, portalAddress: string): Promise<File> => {\n if (!ddocId) {\n throw new Error(\"ddocId is required\");\n }\n\n const existingFile = FilesModel.findByDDocId(ddocId, portalAddress);\n if (!existingFile) {\n throw new Error(`File with ddocId ${ddocId} not found`);\n }\n\n const deletedFile = FilesModel.softDelete(existingFile._id);\n\n EventsModel.create({ type: \"delete\", fileId: deletedFile._id, portalAddress: deletedFile.portalAddress });\n return deletedFile;\n};\n\nexport { listFiles, getFile, createFile, updateFile, deleteFile };\nexport type { CreateFileInput, UpdateFileInput, ListFilesParams, ListFilesResult, GetFileResult } from \"../../types\";\n","export const columnNames = {\n index: \"#\",\n ddocId: \"DDoc ID\",\n title: \"Title\",\n status: \"Status\",\n local: \"Local version\",\n onchain: \"On-chain version\",\n deleted: \"Deleted\",\n created: \"Created\",\n lastModified: \"Last modified\",\n} as const;\n\nexport const columnWidth: Record<string, number> = {\n [columnNames.index]: 3,\n [columnNames.ddocId]: 25,\n [columnNames.title]: 25,\n [columnNames.status]: 10,\n [columnNames.local]: 16,\n [columnNames.onchain]: 18,\n [columnNames.deleted]: 10,\n [columnNames.created]: 12,\n [columnNames.lastModified]: 20,\n};\n\nexport function formatDate(date: Date | string): string {\n const d = typeof date === \"string\" ? new Date(date) : date;\n const day = String(d.getDate()).padStart(2, \"0\");\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const year = d.getFullYear();\n return `${day}-${month}-${year}`;\n}\n\nexport function getElapsedTime(date: Date | string): string {\n const now = new Date();\n const past = typeof date === \"string\" ? new Date(date) : date;\n const diffMs = now.getTime() - past.getTime();\n\n if (diffMs < 0) {\n return \"just now\";\n }\n\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffWeeks = Math.floor(diffDays / 7);\n const diffMonths = Math.floor(diffDays / 30);\n const diffYears = Math.floor(diffDays / 365);\n\n const units = [\n { value: diffSeconds, max: 60, name: \"second\" },\n { value: diffMinutes, max: 60, name: \"minute\" },\n { value: diffHours, max: 24, name: \"hour\" },\n { value: diffDays, max: 7, name: \"day\" },\n { value: diffWeeks, max: 4, name: \"week\" },\n { value: diffMonths, max: 12, name: \"month\" },\n { value: diffYears, max: Infinity, name: \"year\" },\n ];\n\n const unit = units.find((u) => u.value < u.max);\n if (unit) {\n const label = unit.value === 1 ? unit.name : `${unit.name}s`;\n return `${unit.value} ${label} ago`;\n }\n\n return \"just now\";\n}\n\nexport function validateApiKey(apiKey: string | undefined): asserts apiKey is string {\n if (!apiKey) {\n const API_KEY_SETUP_MESSAGE = `\nAPI key is not configured.\n\nTo set up your API key, run:\n fileverse-api --apiKey <your-api-key> --rpcUrl <rpc-url>\n\nThis will configure your Fileverse API instance and save your credentials.\nAfter setup, you can use ddctl commands.\n`;\n console.error(API_KEY_SETUP_MESSAGE);\n process.exit(1);\n }\n}\n","import { Command } from \"commander\";\nimport Table from \"cli-table3\";\nimport { getFile } from \"../domain/file\";\nimport { formatDate, getElapsedTime, columnNames, columnWidth, validateApiKey } from \"./utils/util\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\n\nexport const getCommand = new Command()\n .name(\"get\")\n .description(\"Get a ddoc by its ID\")\n .argument(\"<ddocId>\", \"The ddoc ID to retrieve\")\n .action(async (ddocId: string) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n const file = getFile(ddocId, portalAddress);\n if (!file) {\n console.error(`Ddoc with ID \"${ddocId}\" not found.`);\n return;\n }\n\n const table = new Table({\n head: [\n columnNames.ddocId,\n columnNames.title,\n columnNames.status,\n columnNames.local,\n columnNames.onchain,\n columnNames.deleted,\n columnNames.created,\n columnNames.lastModified,\n ],\n colWidths: [\n columnWidth[columnNames.ddocId],\n columnWidth[columnNames.title],\n columnWidth[columnNames.status],\n columnWidth[columnNames.local],\n columnWidth[columnNames.onchain],\n columnWidth[columnNames.deleted],\n columnWidth[columnNames.created],\n columnWidth[columnNames.lastModified],\n ],\n style: { head: [] },\n });\n\n const fileDdocId = (file as any).ddocId || \"N/A\";\n table.push([\n fileDdocId,\n file.title.length > 23 ? file.title.substring(0, 20) + \"...\" : file.title,\n file.syncStatus,\n file.localVersion,\n file.onchainVersion,\n file.isDeleted ? \"True\" : \"False\",\n formatDate(file.createdAt),\n getElapsedTime(file.updatedAt),\n ]);\n\n console.log(\"\\nDdoc details:\\n\");\n console.log(table.toString());\n if (file.link) {\n console.log(`\\nLink: ${file.link}\\n`);\n }\n } catch (error: any) {\n console.error(\"Error getting ddoc:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport { createFile } from \"../domain/file\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport Table from \"cli-table3\";\nimport { formatDate, getElapsedTime, columnNames, columnWidth, validateApiKey } from \"./utils/util\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\n\nexport const createCommand = new Command()\n .name(\"create\")\n .description(\"Create a new ddoc from a file\")\n .argument(\"<filepath>\", \"Path to the file to create ddoc from\")\n .action(async (filepath: string) => {\n try {\n if (!fs.existsSync(filepath)) {\n throw new Error(`File not found: ${filepath}`);\n }\n\n const runtimeConfig = getRuntimeConfig();\n\n const apiKey = runtimeConfig.API_KEY;\n\n validateApiKey(apiKey);\n\n const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress as string;\n\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n const content = fs.readFileSync(filepath, \"utf-8\");\n if (!content || content.trim().length === 0) {\n console.error(\"Error creating ddoc: File content cannot be empty. Add some content to the file and try again.\");\n process.exit(1);\n }\n\n const basename = path.basename(filepath);\n const lastDotIndex = basename.lastIndexOf(\".\");\n const title = lastDotIndex > 0 ? basename.substring(0, lastDotIndex) : basename;\n const file = await createFile({ title, content, portalAddress });\n\n console.log(\"\\nDdoc created successfully!\\n\");\n const table = new Table({\n head: [\n columnNames.ddocId,\n columnNames.title,\n columnNames.status,\n columnNames.local,\n columnNames.onchain,\n columnNames.created,\n columnNames.lastModified,\n ],\n colWidths: [\n columnWidth[columnNames.ddocId],\n columnWidth[columnNames.title],\n columnWidth[columnNames.status],\n columnWidth[columnNames.local],\n columnWidth[columnNames.onchain],\n columnWidth[columnNames.created],\n columnWidth[columnNames.lastModified],\n ],\n style: { head: [] },\n });\n\n const ddocId = (file as any).ddocId || \"N/A\";\n table.push([\n ddocId,\n file.title.length > 23 ? file.title.substring(0, 20) + \"...\" : file.title,\n file.syncStatus,\n file.localVersion,\n file.onchainVersion,\n formatDate(file.createdAt),\n getElapsedTime(file.updatedAt),\n ]);\n\n console.log(table.toString());\n } catch (error: any) {\n console.error(\"Error creating ddoc:\", error.message);\n process.exit(1);\n }\n });\n","import * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\n\nimport { Command } from \"commander\";\nimport { updateFile, getFile, type UpdateFileInput } from \"../domain/file\";\nimport { spawnSync } from \"child_process\";\nimport Table from \"cli-table3\";\nimport { formatDate, getElapsedTime, columnNames, columnWidth, validateApiKey } from \"./utils/util\";\nimport { ApiKeysModel } from \"../infra/database/models\";\nimport { getRuntimeConfig } from \"../config\";\n\nfunction showTable(updatedFile: any) {\n const table = new Table({\n head: [\n columnNames.ddocId,\n columnNames.title,\n columnNames.status,\n columnNames.local,\n columnNames.onchain,\n columnNames.created,\n columnNames.lastModified,\n ],\n colWidths: [\n columnWidth[columnNames.ddocId],\n columnWidth[columnNames.title],\n columnWidth[columnNames.status],\n columnWidth[columnNames.local],\n columnWidth[columnNames.onchain],\n columnWidth[columnNames.created],\n columnWidth[columnNames.lastModified],\n ],\n style: { head: [] },\n });\n\n const fileDdocId = (updatedFile as any).ddocId || \"N/A\";\n table.push([\n fileDdocId,\n updatedFile.title.length > 23 ? updatedFile.title.substring(0, 20) + \"...\" : updatedFile.title,\n updatedFile.syncStatus,\n updatedFile.localVersion,\n updatedFile.onchainVersion,\n formatDate(updatedFile.createdAt),\n getElapsedTime(updatedFile.updatedAt),\n ]);\n\n console.log(table.toString());\n}\n\nexport const updateCommand = new Command()\n .name(\"update\")\n .description(\"Update an existing ddoc. Use -f to update from a file, or omit -f to edit in vi editor\")\n .argument(\"<ddocId>\", \"The ddoc ID to update\")\n .option(\"-f, --file <file_path>\", \"path to file to update ddoc from (if omitted, opens vi editor)\")\n .action(async (ddocId: string, options: { file?: string }) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n const file = getFile(ddocId, portalAddress);\n if (!file) {\n throw new Error(`ddoc with ${ddocId} not found.`);\n }\n\n const filePath = options?.file ?? \"\";\n if (filePath) {\n const content = fs.readFileSync(filePath, \"utf-8\");\n if (!content || content.trim().length === 0) {\n throw new Error(`file content cannot be empty`);\n }\n\n const basename = path.basename(filePath);\n const lastDotIndex = basename.lastIndexOf(\".\");\n const title = lastDotIndex > 0 ? basename.substring(0, lastDotIndex) : basename;\n const payload: UpdateFileInput = {\n title,\n content,\n };\n const updatedFile = await updateFile(ddocId, payload, portalAddress);\n console.log(\"\\n✓ Ddoc updated successfully!\\n\");\n showTable(updatedFile);\n return;\n }\n\n // vi-editor flow\n const tmpFilePath = path.join(os.tmpdir(), `tmp-${ddocId}-${Date.now()}.txt`);\n fs.writeFileSync(tmpFilePath, file.content);\n\n const editor = process.env.EDITOR || \"vi\";\n const result = spawnSync(editor, [tmpFilePath], { stdio: \"inherit\" });\n if (result.status === 0) {\n const newContent = fs.readFileSync(tmpFilePath, \"utf-8\");\n if (newContent === file.content) {\n console.log(`No changes made. Update cancelled.`);\n fs.unlinkSync(tmpFilePath);\n return;\n }\n\n const payload: UpdateFileInput = {\n title: file.title, // keeping same title as current\n content: newContent,\n };\n const updatedFile = await updateFile(ddocId, payload, portalAddress);\n console.log(\"\\n✓ Ddoc updated successfully!\\n\");\n showTable(updatedFile);\n }\n\n fs.unlinkSync(tmpFilePath);\n } catch (error: any) {\n console.error(\"Error updating ddoc:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport { deleteFile } from \"../domain/file\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\nimport { validateApiKey } from \"./utils/util\";\n\nexport const deleteCommand = new Command()\n .name(\"delete\")\n .description(\"Delete one or more ddocs by their IDs\")\n .argument(\"<ddocIds...>\", \"One or more ddoc IDs to delete (space-separated)\")\n .action(async (ddocIds: string[]) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n\n const apiKey = runtimeConfig.API_KEY;\n\n validateApiKey(apiKey);\n\n const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress as string;\n\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n for (const ddocId of ddocIds) {\n try {\n await deleteFile(ddocId, portalAddress);\n console.log(`ddoc ${ddocId} deleted successfully`);\n } catch (error: any) {\n console.error(`Error deleting ddoc ${ddocId}:`, error.message);\n // Continue with next ddoc instead of stopping\n }\n }\n } catch (error: any) {\n console.error(\"Error:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport * as fs from \"fs\";\nimport { getFile } from \"../domain/file\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\nimport { validateApiKey } from \"./utils/util\";\n\nexport const downloadCommand = new Command()\n .name(\"download\")\n .description(\"Download a ddoc to a local file\")\n .argument(\"<ddocId>\", \"The ddoc ID to download\")\n .option(\"-o, --output <filename>\", \"Output filename (only supports markdown)\")\n .action(async (ddocId: string, options: { output?: string }) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n const file = getFile(ddocId, portalAddress);\n if (!file) {\n console.error(`Ddoc with ID \"${ddocId}\" not found.`);\n return;\n }\n\n let outputFilename: string = file.title;\n if (options.output) {\n outputFilename = options.output.endsWith(\".md\") ? options.output : `${options.output}.md`;\n }\n\n fs.writeFileSync(outputFilename, file.content, \"utf-8\");\n\n console.log(`\\n✓ Ddoc downloaded successfully to: ${outputFilename}\\n`);\n } catch (error: any) {\n console.error(\"Error downloading ddoc:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport { getFile } from \"../domain/file\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\nimport { validateApiKey } from \"./utils/util\";\n\nexport const viewCommand = new Command()\n .name(\"view\")\n .description(\"View content preview of a ddoc\")\n .argument(\"<ddocId>\", \"The ddoc ID to view\")\n .option(\"-n, --lines <number>\", \"Number of lines to preview (default: 10)\", \"10\")\n .action(async (ddocId: string, options: { lines?: string }) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n const file = getFile(ddocId, portalAddress);\n if (!file) {\n console.error(`Ddoc with ID \"${ddocId}\" not found.`);\n return;\n }\n\n const content = file.content || \"\";\n const contentLines = content.split(\"\\n\");\n const totalLines = contentLines.length;\n const previewLines = Math.max(1, parseInt(options.lines || \"10\", 10));\n const linesToShow = Math.min(previewLines, totalLines);\n\n if (content.trim().length === 0) {\n console.log(\"\\nContent preview:\\n\");\n console.log(\"=\".repeat(80));\n console.log(\"(empty)\");\n console.log(\"=\".repeat(80));\n } else {\n const preview = contentLines.slice(0, linesToShow).join(\"\\n\");\n\n console.log(\"\\nContent preview:\\n\");\n console.log(\"=\".repeat(80));\n console.log(preview);\n if (totalLines > linesToShow) {\n console.log(`\\n... (${totalLines - linesToShow} more line${totalLines - linesToShow === 1 ? \"\" : \"s\"})`);\n }\n console.log(\"=\".repeat(80));\n console.log(`\\nShowing ${linesToShow} of ${totalLines} line${totalLines === 1 ? \"\" : \"s\"}\\n`);\n }\n } catch (error: any) {\n console.error(\"Error viewing ddoc:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport Table from \"cli-table3\";\nimport { EventsModel } from \"../infra/database/models\";\nimport { formatDate } from \"./utils/util\";\n\nconst MAX_ERROR_LEN = 60;\n\nexport const eventsCommand = new Command().name(\"events\").description(\"Worker event operations (list failed, retry)\");\n\neventsCommand\n .command(\"list-failed\")\n .description(\"List all failed events\")\n .action(async () => {\n try {\n const events = EventsModel.listFailed();\n if (events.length === 0) {\n console.log(\"No failed events.\");\n return;\n }\n const table = new Table({\n head: [\"ID\", \"File ID\", \"Portal\", \"Type\", \"Timestamp\", \"Last Error\"],\n colWidths: [28, 12, 14, 10, 12, MAX_ERROR_LEN],\n style: { head: [] },\n });\n events.forEach((e) => {\n const err = e.lastError ?? \"\";\n table.push([\n e._id,\n e.fileId,\n e.portalAddress || \"—\",\n e.type,\n formatDate(new Date(e.timestamp)),\n err.length > MAX_ERROR_LEN ? err.slice(0, MAX_ERROR_LEN - 3) + \"...\" : err,\n ]);\n });\n console.log(`\\nFailed events (${events.length}):\\n`);\n console.log(table.toString());\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\"Error listing failed events:\", msg);\n throw error;\n }\n });\n\neventsCommand\n .command(\"retry <eventId>\")\n .description(\"Retry a single failed event by ID\")\n .action(async (eventId: string) => {\n try {\n const updated = EventsModel.resetFailedToPending(eventId);\n if (updated) {\n console.log(`Event ${eventId} reset to pending. Worker will pick it up.`);\n } else {\n console.error(`Event not found or not in failed state: ${eventId}`);\n process.exitCode = 1;\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\"Error retrying event:\", msg);\n throw error;\n }\n });\n\neventsCommand\n .command(\"retry-all\")\n .description(\"Retry all failed events\")\n .action(async () => {\n try {\n const count = EventsModel.resetAllFailedToPending();\n console.log(`Reset ${count} failed event(s) to pending. Worker will pick them up.`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\"Error retrying failed events:\", msg);\n throw error;\n }\n });\n"],"mappings":"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","names":["Command","uuidv7","uuidv7","uuidv7","uuidv7","fromUint8Array","toUint8Array","ucans","toHex","fromUint8Array","toUint8Array","generateRandomBytes","getDb","Command","Table","Command","Table","Command","fs","path","Table","Command","basename","Table","fs","path","os","Command","Table","Table","Command","basename","Command","Command","Command","fs","Command","Command","Command","Command","Table","Command","Table","Command"]}
|
|
1
|
+
{"version":3,"sources":["../../node_modules/tsup/assets/esm_shims.js","../../src/cli/constants.generated.ts","../../src/cli/constants.ts","../../src/config/index.ts","../../src/infra/logger.ts","../../src/infra/asyncHandler.ts","../../src/domain/file/constants.ts","../../src/infra/database/query-builder.ts","../../src/infra/database/index.ts","../../src/infra/database/models/files.model.ts","../../src/infra/database/models/portals.model.ts","../../src/infra/database/models/apikeys.model.ts","../../src/infra/database/models/folders.model.ts","../../src/infra/worker/workerSignal.ts","../../src/infra/database/models/events.model.ts","../../src/infra/database/models/index.ts","../../src/sdk/key-store.ts","../../src/sdk/auth-token-provider.ts","../../src/constants/chains.ts","../../src/constants/events.ts","../../src/constants/methods.ts","../../src/constants/index.ts","../../src/sdk/pimlico-utils.ts","../../src/sdk/smart-agent.ts","../../src/sdk/file-encryption.ts","../../src/sdk/file-utils.ts","../../src/sdk/file-manager.ts","../../src/domain/portal/publish.ts","../../src/domain/portal/savePortal.ts","../../src/domain/portal/saveApiKey.ts","../../src/domain/portal/removeApiKey.ts","../../src/domain/portal/index.ts","../../src/errors/rate-limit.ts","../../src/infra/worker/eventProcessor.ts","../../src/infra/worker/worker.ts","../../src/infra/worker/index.ts","../../src/appWorker.ts","../../src/infra/reporter.ts","../../src/infra/index.ts","../../src/infra/database/adapters/sql-compat.ts","../../src/infra/database/adapters/postgres-adapter.ts","../../src/infra/database/adapters/sqlite-adapter.ts","../../src/infra/database/connection.ts","../../src/commands/index.ts","../../src/infra/database/migrations/index.ts","../../src/commands/listCommand.ts","../../src/domain/file/index.ts","../../src/commands/utils/util.ts","../../src/commands/getCommand.ts","../../src/commands/createCommand.ts","../../src/commands/updateCommand.ts","../../src/commands/deleteCommand.ts","../../src/commands/downloadCommand.ts","../../src/commands/viewCommand.ts","../../src/commands/eventsCommand.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","export const STATIC_CONFIG = {\n API_URL: 'https://prod-apps-storage-5cdacc06ff79.herokuapp.com/',\n SERVER_DID: 'did:key:z6Mkroj9bxTin6Z5S9qwx2G2b87NPrCX7S85FhCpmBGPcDCz',\n PROXY_SERVER_DID: 'did:key:z6MkrZSmq8D6vQG87YbjUQatXeptaCCXWdTx8fYaWxWbRUHB',\n NETWORK_NAME: 'gnosis',\n DEFAULT_PORT: '8001',\n DEFAULT_RPC_URL: 'https://rpc.gnosischain.com',\n PIMLICO_PROXY_URL: 'https://pimlico-proxy-0a326da116f8.herokuapp.com/',\n SERVICE_NAME: 'fileverse-api',\n LOG_LEVEL: 'info',\n FRONTEND_URL: 'https://docs.fileverse.io'\n} as const;\n\nexport const BASE_CONFIG = STATIC_CONFIG;\n","export { STATIC_CONFIG, BASE_CONFIG } from \"./constants.generated.js\";\n","import dotenv from \"dotenv\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport os from \"os\";\nimport { STATIC_CONFIG } from \"../cli/constants.js\";\n\nconst projectEnvPath = path.join(process.cwd(), \"config\", \".env\");\nconst userEnvPath = path.join(os.homedir(), \".fileverse\", \".env\");\n\nfunction getEnvPath(): string {\n if (fs.existsSync(projectEnvPath)) {\n return projectEnvPath;\n }\n return userEnvPath;\n}\n\nexport function loadConfig(override = true): void {\n const envPath = getEnvPath();\n dotenv.config({ path: envPath, override });\n}\n\nloadConfig(false);\n\nexport function getRuntimeConfig() {\n return {\n get API_KEY() {\n return process.env.API_KEY;\n },\n get RPC_URL() {\n return process.env.RPC_URL || STATIC_CONFIG.DEFAULT_RPC_URL;\n },\n get DB_PATH() {\n return process.env.DB_PATH;\n },\n get DATABASE_URL() {\n return process.env.DATABASE_URL;\n },\n get PORT() {\n return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;\n },\n get NODE_ENV() {\n return process.env.NODE_ENV || \"production\";\n },\n get FRONTEND_URL() {\n return process.env.FRONTEND_URL || STATIC_CONFIG.FRONTEND_URL;\n },\n };\n}\n\nexport function validateDbConfig(): void {\n const databaseUrl = process.env.DATABASE_URL;\n const dbPath = process.env.DB_PATH;\n\n // If DATABASE_URL is set, skip file path validation (PostgreSQL mode)\n if (databaseUrl) {\n return;\n }\n\n if (!dbPath) {\n console.error(\"Error: DB_PATH or DATABASE_URL environment variable is required\");\n console.error(\"Please set DB_PATH or DATABASE_URL in your .env file (config/.env or ~/.fileverse/.env) or run the CLI first\");\n process.exit(1);\n }\n\n const dbDir = path.dirname(dbPath.trim());\n if (!fs.existsSync(dbDir)) {\n fs.mkdirSync(dbDir, { recursive: true });\n }\n}\n\n// Keep backwards-compatible alias\nexport const validateDbPath = validateDbConfig;\n\nconst config: Record<string, string | undefined> = {\n ...STATIC_CONFIG,\n get SERVICE_NAME() {\n return STATIC_CONFIG.SERVICE_NAME;\n },\n get LOG_LEVEL() {\n return STATIC_CONFIG.LOG_LEVEL;\n },\n get NETWORK_NAME() {\n return STATIC_CONFIG.NETWORK_NAME;\n },\n get UPLOAD_SERVER_URL() {\n return STATIC_CONFIG.API_URL;\n },\n get UPLOAD_SERVER_DID() {\n return STATIC_CONFIG.SERVER_DID;\n },\n get API_KEY() {\n return process.env.API_KEY;\n },\n get RPC_URL() {\n return process.env.RPC_URL || STATIC_CONFIG.DEFAULT_RPC_URL;\n },\n get DB_PATH() {\n return process.env.DB_PATH;\n },\n get DATABASE_URL() {\n return process.env.DATABASE_URL;\n },\n get PORT() {\n return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;\n },\n get NODE_ENV() {\n return process.env.NODE_ENV || \"production\";\n },\n get IP() {\n return process.env.IP || \"0.0.0.0\";\n },\n get FRONTEND_URL() {\n return process.env.FRONTEND_URL || STATIC_CONFIG.FRONTEND_URL;\n },\n};\n\nexport { config };\n","import pino, { Logger as PinoLogger, Level } from \"pino\";\nimport { STATIC_CONFIG } from \"../cli/constants\";\nimport { config } from \"../config\";\n\nconst isProduction = config.NODE_ENV === \"production\";\n\nconst pinoInstance = pino({\n name: STATIC_CONFIG.SERVICE_NAME,\n level: STATIC_CONFIG.LOG_LEVEL,\n formatters: {\n bindings: (bindings) => ({ name: bindings.name }),\n level: (label) => ({ level: label }),\n },\n serializers: {\n err(err: Error | undefined) {\n if (!err) return err;\n if (isProduction) {\n return { type: err.name, message: err.message };\n }\n return {\n type: err.name,\n message: err.message,\n stack: err.stack,\n };\n },\n },\n transport:\n config.NODE_ENV !== \"production\"\n ? {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"SYS:standard\",\n ignore: \"pid,hostname\",\n errorProps: \"*\",\n errorLikeObjectKeys: [\"err\", \"error\"],\n },\n }\n : undefined,\n});\n\ntype LogFn = {\n (msg: string, ...args: unknown[]): void;\n (obj: object, msg?: string, ...args: unknown[]): void;\n};\n\nconst createLogMethod = (level: Level): LogFn => {\n return (...args: unknown[]) => {\n const [first, ...rest] = args;\n const log = pinoInstance[level].bind(pinoInstance) as (...a: unknown[]) => void;\n\n if (typeof first === \"object\" && first !== null && !(first instanceof Error)) {\n log(first, ...rest);\n return;\n }\n\n if (rest.length > 0) {\n const last = rest[rest.length - 1];\n if (last instanceof Error) {\n log({ err: last }, first, ...rest.slice(0, -1));\n return;\n }\n }\n\n if (first instanceof Error) {\n log({ err: first }, first.message);\n return;\n }\n\n log(first, ...rest);\n };\n};\n\ninterface Logger {\n trace: LogFn;\n debug: LogFn;\n info: LogFn;\n warn: LogFn;\n error: LogFn;\n fatal: LogFn;\n level: Level;\n child: PinoLogger[\"child\"];\n}\n\nexport const logger: Logger = {\n trace: createLogMethod(\"trace\"),\n debug: createLogMethod(\"debug\"),\n info: createLogMethod(\"info\"),\n warn: createLogMethod(\"warn\"),\n error: createLogMethod(\"error\"),\n fatal: createLogMethod(\"fatal\"),\n get level() {\n return pinoInstance.level as Level;\n },\n set level(lvl: Level) {\n pinoInstance.level = lvl;\n },\n child: pinoInstance.child.bind(pinoInstance),\n};\n","import { Request, Response, NextFunction } from \"express\";\n\nconst asyncHandler =\n (fn: (req: Request, res: Response, next: NextFunction) => Promise<void>) =>\n (req: Request, res: Response, next: NextFunction) =>\n Promise.resolve(fn(req, res, next)).catch(next);\n\nconst asyncHandlerArray = (resolvers: any) => {\n return resolvers.map(asyncHandler);\n};\n\nexport { asyncHandler, asyncHandlerArray };\n","/**\n * Default limit for listing files/ddocs\n * Used by both API and CLI to ensure consistent behavior\n */\nexport const DEFAULT_LIST_LIMIT = 10;\n","import { getAdapter } from \"./connection.js\";\nimport type { QueryOptions } from \"../../types\";\nimport type { ExecuteResult } from \"./adapters/index.js\";\nimport { DEFAULT_LIST_LIMIT } from \"../../domain/file/constants\";\n\nexport class QueryBuilder {\n static async select<T = any>(sql: string, params: any[] = []): Promise<T[]> {\n const adapter = await getAdapter();\n return adapter.select<T>(sql, params);\n }\n\n static async selectOne<T = any>(sql: string, params: any[] = []): Promise<T | undefined> {\n const adapter = await getAdapter();\n return adapter.selectOne<T>(sql, params);\n }\n\n static async execute(sql: string, params: any[] = []): Promise<ExecuteResult> {\n const adapter = await getAdapter();\n return adapter.execute(sql, params);\n }\n\n static async transaction<T>(callback: () => Promise<T>): Promise<T> {\n const adapter = await getAdapter();\n return adapter.transaction(callback);\n }\n\n static paginate(sql: string, options: QueryOptions = {}): string {\n let query = sql;\n\n if (options.orderBy) {\n query += ` ORDER BY ${options.orderBy} ${options.orderDirection || \"ASC\"}`;\n }\n\n const hasOffset = (options.offset ?? 0) > 0;\n const limit = options.limit ?? (hasOffset ? DEFAULT_LIST_LIMIT : undefined);\n\n if (limit) {\n query += ` LIMIT ${limit}`;\n }\n\n if (hasOffset) {\n query += ` OFFSET ${options.offset}`;\n }\n\n return query;\n }\n}\n","import { getAdapter, closeAdapter, initializeAdapter } from \"./connection.js\";\nimport { QueryBuilder } from \"./query-builder.js\";\n\nconst closeDatabase = async (): Promise<void> => {\n await closeAdapter();\n};\n\nexport { getAdapter, initializeAdapter, closeAdapter, closeDatabase, QueryBuilder };\n","import { QueryBuilder } from \"../index.js\";\nimport { uuidv7 } from \"uuidv7\";\nimport type { File, FileListResponse, UpdateFilePayload } from \"../../../types\";\n\nexport type { File, FileListResponse };\n\nexport class FilesModel {\n private static readonly TABLE = \"files\";\n\n private static parseFile(fileRaw: any): File {\n let metadata: Record<string, unknown> = {};\n try {\n if (fileRaw.metadata) {\n metadata = typeof fileRaw.metadata === \"string\" ? JSON.parse(fileRaw.metadata) : fileRaw.metadata;\n }\n } catch (e) {\n // If parsing fails, use empty object\n metadata = {};\n }\n\n return {\n _id: fileRaw._id,\n ddocId: fileRaw.ddocId,\n title: fileRaw.title,\n content: fileRaw.content,\n localVersion: fileRaw.localVersion,\n onchainVersion: fileRaw.onchainVersion,\n syncStatus: fileRaw.syncStatus,\n isDeleted: fileRaw.isDeleted,\n onChainFileId: fileRaw.onChainFileId ?? null,\n portalAddress: fileRaw.portalAddress,\n metadata: metadata || {},\n createdAt: fileRaw.createdAt,\n updatedAt: fileRaw.updatedAt,\n linkKey: fileRaw.linkKey,\n linkKeyNonce: fileRaw.linkKeyNonce,\n commentKey: fileRaw.commentKey,\n link: fileRaw.link,\n };\n }\n\n static async findAll(\n portalAddress: string,\n limit?: number,\n skip?: number,\n ): Promise<{ files: File[]; total: number; hasNext: boolean }> {\n const whereClause = \"isDeleted = 0 AND portalAddress = ?\";\n const params: any[] = [portalAddress];\n\n const countSql = `\n SELECT COUNT(*) as count\n FROM ${this.TABLE}\n WHERE ${whereClause}\n `;\n const totalResult = await QueryBuilder.selectOne<{ count: number }>(countSql, params);\n const total = totalResult?.count || 0;\n const sql = `\n SELECT *\n FROM ${this.TABLE}\n WHERE ${whereClause}\n `;\n const completeSql = QueryBuilder.paginate(sql, {\n limit,\n offset: skip,\n orderBy: \"createdAt\",\n orderDirection: \"DESC\",\n });\n\n const filesRaw = await QueryBuilder.select<any>(completeSql, params);\n const files = filesRaw.map(this.parseFile);\n const hasNext = skip !== undefined && limit !== undefined ? skip + limit < total : false;\n return { files, total, hasNext };\n }\n\n static async findById(_id: string, portalAddress: string): Promise<File | undefined> {\n const sql = `\n SELECT *\n FROM ${this.TABLE}\n WHERE _id = ? AND isDeleted = 0 AND portalAddress = ?\n `;\n const result = await QueryBuilder.selectOne<any>(sql, [_id, portalAddress]);\n return result ? this.parseFile(result) : undefined;\n }\n\n static async findByIdIncludingDeleted(_id: string): Promise<File | undefined> {\n const sql = `\n SELECT *\n FROM ${this.TABLE}\n WHERE _id = ?\n `;\n const result = await QueryBuilder.selectOne<any>(sql, [_id]);\n return result ? this.parseFile(result) : undefined;\n }\n\n static async findByIdExcludingDeleted(_id: string): Promise<File | undefined> {\n const sql = `\n SELECT *\n FROM ${this.TABLE}\n WHERE _id = ? AND isDeleted = 0\n `;\n const result = await QueryBuilder.selectOne<any>(sql, [_id]);\n return result ? this.parseFile(result) : undefined;\n }\n\n static async findByDDocId(ddocId: string, portalAddress: string): Promise<File | undefined> {\n const sql = `\n SELECT *\n FROM ${this.TABLE}\n WHERE ddocId = ? AND isDeleted = 0 AND portalAddress = ?\n `;\n const result = await QueryBuilder.selectOne<any>(sql, [ddocId, portalAddress]);\n return result ? this.parseFile(result) : undefined;\n }\n\n static async searchByTitle(searchTerm: string, portalAddress: string, limit?: number, skip?: number): Promise<File[]> {\n const sql = `\n SELECT *\n FROM ${this.TABLE}\n WHERE LOWER(title) LIKE LOWER(?) AND isDeleted = 0 AND portalAddress = ?\n `;\n const completeSql = QueryBuilder.paginate(sql, {\n limit,\n offset: skip,\n orderBy: \"createdAt\",\n orderDirection: \"DESC\",\n });\n const filesRaw = await QueryBuilder.select<any>(completeSql, [`%${searchTerm}%`, portalAddress]);\n return filesRaw.map(this.parseFile);\n }\n\n static async create(input: { title: string; content: string; ddocId: string; portalAddress: string }): Promise<File> {\n const _id = uuidv7();\n const sql = `\n INSERT INTO ${this.TABLE}\n (_id, title, content, ddocId, portalAddress)\n VALUES (?, ?, ?, ?, ?)\n `;\n\n await QueryBuilder.execute(sql, [_id, input.title, input.content, input.ddocId, input.portalAddress]);\n // NOTE: default values while file creation: localVersion = 1, onchainVersion = 0, syncStatus = 'pending'\n\n const created = await this.findById(_id, input.portalAddress);\n if (!created) {\n throw new Error(\"Failed to create file\");\n }\n return created;\n }\n\n static async update(_id: string, payload: UpdateFilePayload, portalAddress: string): Promise<File> {\n const now = new Date().toISOString();\n\n const keys: string[] = [];\n const values: any[] = [];\n for (const [k, v] of Object.entries(payload)) {\n if (v !== undefined) {\n // Handle metadata specially - convert to JSON string\n if (k === \"metadata\" && typeof v === \"object\") {\n keys.push(`${k} = ?`);\n values.push(JSON.stringify(v));\n } else {\n keys.push(`${k} = ?`);\n values.push(v);\n }\n }\n }\n\n // Always add updatedAt\n keys.push(\"updatedAt = ?\");\n values.push(now, _id, portalAddress);\n\n const updateChain = keys.join(\", \");\n const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE _id = ? AND portalAddress = ?`;\n\n await QueryBuilder.execute(sql, values);\n\n const updated = await this.findById(_id, portalAddress);\n if (!updated) {\n throw new Error(\"Failed to update file\");\n }\n return updated;\n }\n\n static async softDelete(_id: string): Promise<File> {\n const now = new Date().toISOString();\n const sql = `\n UPDATE ${this.TABLE}\n SET isDeleted = 1, syncStatus = 'pending', updatedAt = ?\n WHERE _id = ?\n `;\n\n await QueryBuilder.execute(sql, [now, _id]);\n\n // Use findByIdIncludingDeleted since the file is now marked as deleted\n const deleted = await this.findByIdIncludingDeleted(_id);\n if (!deleted) {\n throw new Error(\"Failed to delete file\");\n }\n return deleted;\n }\n}\n","import { QueryBuilder } from \"../index.js\";\nimport { uuidv7 } from \"uuidv7\";\nimport type { Portal } from \"../../../types\";\n\nexport type { Portal };\n\nexport class PortalsModel {\n private static readonly TABLE = \"portals\";\n\n static async findByPortalAddress(portalAddress: string): Promise<Portal | undefined> {\n const sql = `SELECT _id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt FROM ${this.TABLE} WHERE portalAddress = ?`;\n return QueryBuilder.selectOne<Portal>(sql, [portalAddress]);\n }\n\n static async create(input: { portalAddress: string; portalSeed: string; ownerAddress: string }): Promise<Portal> {\n const _id = uuidv7();\n const now = new Date().toISOString();\n const sql = `INSERT INTO ${this.TABLE} (_id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)`;\n\n await QueryBuilder.execute(sql, [_id, input.portalAddress, input.portalSeed, input.ownerAddress, now, now]);\n\n const created = await this.findByPortalAddress(input.portalAddress);\n if (!created) {\n throw new Error(\"Failed to create portal\");\n }\n return created;\n }\n\n static async update(\n portalAddress: string,\n input: {\n portalSeed?: string;\n ownerAddress?: string;\n },\n ): Promise<Portal> {\n const now = new Date().toISOString();\n const keys: string[] = [];\n const values: any[] = [];\n\n for (const [k, v] of Object.entries(input)) {\n if (v !== undefined) {\n keys.push(`${k} = ?`);\n values.push(v);\n }\n }\n\n keys.push(\"updatedAt = ?\");\n values.push(now);\n\n const updateChain = keys.join(\", \");\n const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE portalAddress = ?`;\n values.push(portalAddress);\n await QueryBuilder.execute(sql, values);\n\n const updated = await this.findByPortalAddress(portalAddress);\n if (!updated) {\n throw new Error(\"Failed to update portal\");\n }\n return updated;\n }\n\n static async upsert(input: { portalAddress: string; portalSeed: string; ownerAddress: string }): Promise<Portal> {\n const existing = await this.findByPortalAddress(input.portalAddress);\n if (existing) {\n return this.update(input.portalAddress, {\n portalSeed: input.portalSeed,\n ownerAddress: input.ownerAddress,\n });\n }\n return this.create(input);\n }\n}\n","import { QueryBuilder } from \"../index.js\";\nimport { uuidv7 } from \"uuidv7\";\nimport type { ApiKey } from \"../../../types\";\n\nexport type { ApiKey };\n\nexport class ApiKeysModel {\n private static readonly TABLE = \"api_keys\";\n\n static async create(input: {\n apiKeySeed: string;\n name: string;\n collaboratorAddress: string;\n portalAddress: string;\n }): Promise<ApiKey> {\n const _id = uuidv7();\n const now = new Date().toISOString();\n const sql = `INSERT INTO ${this.TABLE} (_id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt)\n VALUES (?, ?, ?, ?, ?, ?)`;\n\n const result = await QueryBuilder.execute(sql, [\n _id,\n input.apiKeySeed,\n input.name,\n input.collaboratorAddress,\n input.portalAddress,\n now,\n ]);\n\n if (result.changes === 0) {\n throw new Error(\"Failed to create API key\");\n }\n\n const created = await this.findById(_id);\n if (!created) {\n throw new Error(\"Failed to create API key\");\n }\n return created;\n }\n\n static async findById(_id: string): Promise<ApiKey | undefined> {\n const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE _id = ? AND isDeleted = 0`;\n return QueryBuilder.selectOne<ApiKey>(sql, [_id]);\n }\n\n static async findByCollaboratorAddress(collaboratorAddress: string): Promise<ApiKey | undefined> {\n const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE collaboratorAddress = ? AND isDeleted = 0 LIMIT 1`;\n return QueryBuilder.selectOne<ApiKey>(sql, [collaboratorAddress]);\n }\n\n static async delete(_id: string): Promise<void> {\n const sql = `UPDATE ${this.TABLE} SET isDeleted = 1 WHERE _id = ?`;\n await QueryBuilder.execute(sql, [_id]);\n }\n\n static async findByPortalAddress(portalAddress: string): Promise<ApiKey | undefined> {\n const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE portalAddress = ? AND isDeleted = 0`;\n return QueryBuilder.selectOne<ApiKey>(sql, [portalAddress]);\n }\n\n static async findByApiKey(apiKey: string): Promise<ApiKey | undefined> {\n const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE apiKeySeed = ? AND isDeleted = 0`;\n return QueryBuilder.selectOne<ApiKey>(sql, [apiKey]);\n }\n}\n","import { QueryBuilder } from \"../index.js\";\nimport type { File, Folder, FolderWithDDocs, FolderListResponse } from \"../../../types\";\n\nexport type { Folder, FolderWithDDocs, FolderListResponse };\n\nexport class FoldersModel {\n private static readonly TABLE = \"folders\";\n\n static async findAll(limit?: number, skip?: number): Promise<{ folders: Folder[]; total: number; hasNext: boolean }> {\n // Get total count\n const countSql = `SELECT COUNT(*) as count FROM ${this.TABLE} WHERE isDeleted = 0`;\n const totalResult = await QueryBuilder.selectOne<{ count: number }>(countSql);\n const total = totalResult?.count || 0;\n\n // Get paginated results\n const sql = QueryBuilder.paginate(`SELECT * FROM ${this.TABLE} WHERE isDeleted = 0`, {\n limit,\n offset: skip,\n orderBy: \"created_at\",\n orderDirection: \"DESC\",\n });\n\n const foldersRaw = await QueryBuilder.select<any>(sql);\n const folders = foldersRaw.map((folderRaw) => ({\n ...folderRaw,\n isDeleted: Boolean(folderRaw.isDeleted),\n }));\n\n const hasNext = skip !== undefined && limit !== undefined ? skip + limit < total : false;\n\n return { folders, total, hasNext };\n }\n\n static async findByFolderRefAndId(folderRef: string, folderId: string): Promise<FolderWithDDocs | undefined> {\n const sql = `SELECT * FROM ${this.TABLE} WHERE folderRef = ? AND folderId = ? AND isDeleted = 0`;\n const folderRaw = await QueryBuilder.selectOne<any>(sql, [folderRef, folderId]);\n\n if (!folderRaw) {\n return undefined;\n }\n\n const parsedFolder: Folder = {\n ...folderRaw,\n isDeleted: Boolean(folderRaw.isDeleted),\n };\n\n // Get ddocs in this folder\n // Note: FolderRef functionality removed in simplified schema, returning empty array\n const ddocs: File[] = [];\n\n return {\n ...parsedFolder,\n ddocs,\n };\n }\n\n static async findByFolderRef(folderRef: string): Promise<Folder | undefined> {\n const sql = `SELECT * FROM ${this.TABLE} WHERE folderRef = ? AND isDeleted = 0 LIMIT 1`;\n const folderRaw = await QueryBuilder.selectOne<any>(sql, [folderRef]);\n\n if (!folderRaw) {\n return undefined;\n }\n\n return {\n ...folderRaw,\n isDeleted: Boolean(folderRaw.isDeleted),\n };\n }\n\n static async searchByName(searchTerm: string, limit?: number, skip?: number): Promise<Folder[]> {\n const sql = QueryBuilder.paginate(\n `SELECT * FROM ${this.TABLE}\n WHERE isDeleted = 0 AND LOWER(folderName) LIKE LOWER(?)`,\n {\n limit,\n offset: skip,\n orderBy: \"created_at\",\n orderDirection: \"DESC\",\n },\n );\n\n const foldersRaw = await QueryBuilder.select<any>(sql, [`%${searchTerm}%`]);\n return foldersRaw.map((folderRaw) => ({\n ...folderRaw,\n isDeleted: Boolean(folderRaw.isDeleted),\n }));\n }\n\n static async create(input: {\n _id?: string;\n onchainFileId: number;\n folderId: string;\n folderRef: string;\n folderName: string;\n portalAddress: string;\n metadataIPFSHash: string;\n contentIPFSHash: string;\n lastTransactionHash?: string;\n lastTransactionBlockNumber: number;\n lastTransactionBlockTimestamp: number;\n }): Promise<Folder> {\n const _id = input._id || `folder_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n const now = new Date().toISOString();\n\n const sql = `INSERT INTO ${this.TABLE} (\n _id, onchainFileId, folderId, folderRef, folderName, portalAddress, metadataIPFSHash,\n contentIPFSHash, isDeleted, lastTransactionHash, lastTransactionBlockNumber,\n lastTransactionBlockTimestamp, created_at, updated_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;\n\n await QueryBuilder.execute(sql, [\n _id,\n input.onchainFileId,\n input.folderId,\n input.folderRef,\n input.folderName,\n input.portalAddress,\n input.metadataIPFSHash,\n input.contentIPFSHash,\n 0, // isDeleted\n input.lastTransactionHash || null,\n input.lastTransactionBlockNumber,\n input.lastTransactionBlockTimestamp,\n now,\n now,\n ]);\n\n // Fetch the created folder (without ddocs)\n const selectSql = `SELECT * FROM ${this.TABLE} WHERE folderRef = ? AND folderId = ? AND isDeleted = 0`;\n const folderRaw = await QueryBuilder.selectOne<any>(selectSql, [input.folderRef, input.folderId]);\n\n if (!folderRaw) {\n throw new Error(\"Failed to create folder\");\n }\n\n return {\n ...folderRaw,\n isDeleted: Boolean(folderRaw.isDeleted),\n };\n }\n}\n","import { EventEmitter } from \"events\";\n\nclass WorkerSignal extends EventEmitter {}\n\nconst workerSignal = new WorkerSignal();\nworkerSignal.setMaxListeners(20);\n\nexport function notifyNewEvent(): void {\n workerSignal.emit(\"newEvent\");\n}\n\nexport function onNewEvent(callback: () => void): () => void {\n workerSignal.on(\"newEvent\", callback);\n return () => workerSignal.off(\"newEvent\", callback);\n}\n","import { QueryBuilder } from \"../index.js\";\nimport { uuidv7 } from \"uuidv7\";\nimport { notifyNewEvent } from \"../../worker/workerSignal\";\nimport type { Event, EventType, EventStatus } from \"../../../types\";\n\nexport type { Event, EventType, EventStatus };\n\nconst RETRY_DELAYS_MS = [5000, 30000, 120000];\n\ninterface EventRow {\n _id: string;\n type: string;\n timestamp: number;\n fileId: string;\n portalAddress: string;\n status: string;\n retryCount: number;\n lastError: string | null;\n lockedAt: number | null;\n nextRetryAt: number | null;\n userOpHash?: string | null;\n pendingPayload?: string | null;\n}\n\nexport class EventsModel {\n private static readonly TABLE = \"events\";\n\n static async create(input: { type: EventType; fileId: string; portalAddress: string }): Promise<Event> {\n const _id = uuidv7();\n const timestamp = Date.now();\n const status: EventStatus = \"pending\";\n\n const sql = `\n INSERT INTO ${this.TABLE}\n (_id, type, timestamp, fileId, portalAddress, status, retryCount, lastError, lockedAt, nextRetryAt)\n VALUES (?, ?, ?, ?, ?, ?, 0, NULL, NULL, NULL)\n `;\n\n await QueryBuilder.execute(sql, [_id, input.type, timestamp, input.fileId, input.portalAddress, status]);\n\n notifyNewEvent();\n\n return {\n _id,\n type: input.type,\n timestamp,\n fileId: input.fileId,\n portalAddress: input.portalAddress,\n status,\n retryCount: 0,\n lastError: null,\n lockedAt: null,\n nextRetryAt: null,\n };\n }\n\n static async findById(_id: string): Promise<Event | undefined> {\n const sql = `SELECT * FROM ${this.TABLE} WHERE _id = ?`;\n const row = await QueryBuilder.selectOne<EventRow>(sql, [_id]);\n return row ? this.parseEvent(row) : undefined;\n }\n\n static async findNextPending(): Promise<Event | undefined> {\n const sql = `\n SELECT * FROM ${this.TABLE}\n WHERE status = 'pending'\n ORDER BY timestamp ASC\n LIMIT 1\n `;\n const row = await QueryBuilder.selectOne<EventRow>(sql, []);\n return row ? this.parseEvent(row) : undefined;\n }\n\n static async findNextEligible(lockedFileIds: string[]): Promise<Event | undefined> {\n const now = Date.now();\n\n const exclusionClause =\n lockedFileIds.length > 0 ? `AND e1.fileId NOT IN (${lockedFileIds.map(() => \"?\").join(\", \")})` : \"\";\n\n const sql = `\n SELECT e1.* FROM ${this.TABLE} e1\n WHERE e1.status = 'pending'\n AND (e1.nextRetryAt IS NULL OR e1.nextRetryAt <= ?)\n ${exclusionClause}\n AND NOT EXISTS (\n SELECT 1 FROM ${this.TABLE} e2\n WHERE e2.fileId = e1.fileId\n AND e2.status = 'pending'\n AND e2.timestamp < e1.timestamp\n )\n ORDER BY e1.timestamp ASC\n LIMIT 1\n `;\n\n const params = [now, ...lockedFileIds];\n const row = await QueryBuilder.selectOne<EventRow>(sql, params);\n return row ? this.parseEvent(row) : undefined;\n }\n\n static async markProcessing(_id: string): Promise<void> {\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'processing',\n lockedAt = ?\n WHERE _id = ?\n `;\n await QueryBuilder.execute(sql, [Date.now(), _id]);\n }\n\n static async markProcessed(_id: string): Promise<void> {\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'processed',\n lockedAt = NULL\n WHERE _id = ?\n `;\n await QueryBuilder.execute(sql, [_id]);\n }\n\n static async scheduleRetry(_id: string, errorMsg: string): Promise<void> {\n const event = await this.findById(_id);\n if (!event) return;\n\n const delay = RETRY_DELAYS_MS[Math.min(event.retryCount, RETRY_DELAYS_MS.length - 1)];\n const nextRetryAt = Date.now() + delay;\n\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n retryCount = retryCount + 1,\n lastError = ?,\n nextRetryAt = ?,\n lockedAt = NULL\n WHERE _id = ?\n `;\n await QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);\n }\n\n static async scheduleRetryAfter(_id: string, errorMsg: string, retryAfterMs: number): Promise<void> {\n const nextRetryAt = Date.now() + retryAfterMs;\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n lastError = ?,\n nextRetryAt = ?,\n lockedAt = NULL\n WHERE _id = ?\n `;\n await QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);\n }\n\n static async markFailed(_id: string, errorMsg: string): Promise<void> {\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'failed',\n lastError = ?,\n lockedAt = NULL\n WHERE _id = ?\n `;\n await QueryBuilder.execute(sql, [errorMsg, _id]);\n }\n\n static async listFailed(portalAddress?: string): Promise<Event[]> {\n const portalClause = portalAddress != null ? \"AND portalAddress = ?\" : \"\";\n const sql = `\n SELECT * FROM ${this.TABLE}\n WHERE status = 'failed'\n ${portalClause}\n ORDER BY timestamp ASC\n `;\n const params = portalAddress != null ? [portalAddress] : [];\n const rows = await QueryBuilder.select<EventRow>(sql, params);\n return rows.map((row) => this.parseEvent(row));\n }\n\n static async resetFailedToPending(_id: string, portalAddress?: string): Promise<boolean> {\n const portalClause = portalAddress != null ? \"AND portalAddress = ?\" : \"\";\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n retryCount = 0,\n lastError = NULL,\n nextRetryAt = NULL,\n lockedAt = NULL\n WHERE _id = ?\n AND status = 'failed'\n ${portalClause}\n `;\n const params = portalAddress != null ? [_id, portalAddress] : [_id];\n const result = await QueryBuilder.execute(sql, params);\n if (result.changes > 0) {\n notifyNewEvent();\n }\n return result.changes > 0;\n }\n\n static async resetAllFailedToPending(portalAddress?: string): Promise<number> {\n const portalClause = portalAddress != null ? \"AND portalAddress = ?\" : \"\";\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n retryCount = 0,\n lastError = NULL,\n nextRetryAt = NULL,\n lockedAt = NULL\n WHERE status = 'failed'\n ${portalClause}\n `;\n const params = portalAddress != null ? [portalAddress] : [];\n const result = await QueryBuilder.execute(sql, params);\n if (result.changes > 0) {\n notifyNewEvent();\n }\n return result.changes;\n }\n\n static async resetStaleEvents(staleThreshold: number): Promise<number> {\n const sql = `\n UPDATE ${this.TABLE}\n SET status = 'pending',\n lockedAt = NULL,\n userOpHash = NULL,\n pendingPayload = NULL\n WHERE status = 'processing'\n AND lockedAt IS NOT NULL\n AND lockedAt < ?\n `;\n const result = await QueryBuilder.execute(sql, [staleThreshold]);\n return result.changes;\n }\n\n static async setEventPendingOp(_id: string, userOpHash: string, payload: Record<string, unknown>): Promise<void> {\n const sql = `UPDATE ${this.TABLE} SET userOpHash = ?, pendingPayload = ? WHERE _id = ?`;\n await QueryBuilder.execute(sql, [userOpHash, JSON.stringify(payload), _id]);\n }\n\n static async clearEventPendingOp(_id: string): Promise<void> {\n const sql = `UPDATE ${this.TABLE} SET userOpHash = NULL, pendingPayload = NULL WHERE _id = ?`;\n await QueryBuilder.execute(sql, [_id]);\n }\n\n private static parseEvent(row: EventRow): Event {\n return {\n _id: row._id,\n type: row.type as EventType,\n timestamp: row.timestamp,\n fileId: row.fileId,\n portalAddress: row.portalAddress ?? \"\",\n status: row.status as EventStatus,\n retryCount: row.retryCount,\n lastError: row.lastError,\n lockedAt: row.lockedAt,\n nextRetryAt: row.nextRetryAt,\n userOpHash: row.userOpHash ?? null,\n pendingPayload: row.pendingPayload ?? null,\n };\n }\n}\n","import { type File, FilesModel, type FileListResponse } from \"./files.model\";\nimport { PortalsModel, type Portal } from \"./portals.model\";\nimport { ApiKeysModel, type ApiKey } from \"./apikeys.model\";\nimport { type Folder, type FolderWithDDocs, type FolderListResponse, FoldersModel } from \"./folders.model\";\nimport { EventsModel, type Event, type EventType, type EventStatus } from \"./events.model\";\n\nexport { FilesModel, PortalsModel, ApiKeysModel, FoldersModel, EventsModel };\nexport type {\n File,\n FileListResponse,\n Portal,\n ApiKey,\n Folder,\n FolderWithDDocs,\n FolderListResponse,\n Event,\n EventType,\n EventStatus,\n};\n","import { Hex } from \"viem\";\nimport { eciesDecrypt, eciesEncrypt, generateECKeyPair } from \"@fileverse/crypto/ecies\";\nimport { AuthTokenProvider } from \"./auth-token-provider\";\n\nexport class KeyStore {\n private portalKeySeed: Uint8Array | undefined;\n private portalAddress: Hex | undefined;\n\n constructor(\n seed: Uint8Array,\n address: Hex,\n private readonly authTokenProvider: AuthTokenProvider,\n ) {\n this.portalKeySeed = seed;\n this.portalAddress = address;\n this.authTokenProvider = authTokenProvider;\n }\n\n getPortalAddress() {\n if (!this.portalAddress) {\n throw new Error(\"Portal address is not set\");\n }\n return this.portalAddress;\n }\n\n private getAppEncryptionKey() {\n if (!this.portalKeySeed) {\n throw new Error(\"Portal key seed is not set\");\n }\n\n const keyPair = generateECKeyPair(this.portalKeySeed);\n return keyPair.publicKey;\n }\n\n private getAppDecryptionKey() {\n if (!this.portalKeySeed) {\n throw new Error(\"Portal key seed is not set\");\n }\n\n const keyPair = generateECKeyPair(this.portalKeySeed);\n return keyPair.privateKey;\n }\n\n encryptData(data: Uint8Array) {\n return eciesEncrypt(this.getAppEncryptionKey(), data);\n }\n\n decryptData(data: string) {\n return eciesDecrypt(this.getAppDecryptionKey(), data);\n }\n\n getAuthToken(audienceDid: string) {\n return this.authTokenProvider.getAuthToken(audienceDid);\n }\n}\n","import * as ucans from \"@ucans/ucans\";\nimport type { Hex } from \"viem\";\n\nexport class AuthTokenProvider {\n private readonly DEFAULT_OPTIONS = {\n namespace: \"file\",\n segment: \"CREATE\",\n scheme: \"storage\",\n };\n private keyPair: ucans.EdKeypair;\n portalAddress: Hex;\n constructor(keyPair: ucans.EdKeypair, portalAddress: Hex) {\n this.keyPair = keyPair;\n this.portalAddress = portalAddress;\n }\n\n async getAuthToken(\n audienceDid: string,\n options: { namespace: string; segment: string; scheme: string } = this.DEFAULT_OPTIONS,\n ): Promise<string> {\n const ucan = await ucans.build({\n audience: audienceDid,\n issuer: this.keyPair,\n lifetimeInSeconds: 7 * 86400,\n capabilities: [\n {\n with: {\n scheme: options.scheme,\n hierPart: this.portalAddress.toLocaleLowerCase(),\n },\n can: { namespace: options.namespace, segments: [options.segment] },\n },\n ],\n });\n\n return ucans.encode(ucan);\n }\n}\n","export { sepolia, gnosis } from \"viem/chains\";\n","export const ADDED_FILE_EVENT = [\n {\n anonymous: false,\n inputs: [\n {\n indexed: true,\n internalType: \"uint256\",\n name: \"fileId\",\n type: \"uint256\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"appFileId\",\n type: \"string\",\n },\n {\n indexed: false,\n internalType: \"enum FileverseApp.FileType\",\n name: \"fileType\",\n type: \"uint8\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"metadataIPFSHash\",\n type: \"string\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"contentIPFSHash\",\n type: \"string\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"gateIPFSHash\",\n type: \"string\",\n },\n {\n indexed: false,\n internalType: \"uint256\",\n name: \"version\",\n type: \"uint256\",\n },\n {\n indexed: true,\n internalType: \"address\",\n name: \"by\",\n type: \"address\",\n },\n ],\n name: \"AddedFile\",\n type: \"event\",\n },\n] as const;\n\nexport const EDITED_FILE_EVENT = [\n {\n anonymous: false,\n inputs: [\n {\n indexed: true,\n internalType: \"uint256\",\n name: \"fileId\",\n type: \"uint256\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"appFileId\",\n type: \"string\",\n },\n {\n indexed: false,\n internalType: \"enum FileverseApp.FileType\",\n name: \"fileType\",\n type: \"uint8\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"metadataIPFSHash\",\n type: \"string\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"contentIPFSHash\",\n type: \"string\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"gateIPFSHash\",\n type: \"string\",\n },\n {\n indexed: false,\n internalType: \"uint256\",\n name: \"version\",\n type: \"uint256\",\n },\n {\n indexed: true,\n internalType: \"address\",\n name: \"by\",\n type: \"address\",\n },\n ],\n name: \"EditedFile\",\n type: \"event\",\n },\n] as const;\n\nexport const DELETED_FILE_EVENT = [\n {\n anonymous: false,\n inputs: [\n {\n indexed: true,\n internalType: \"uint256\",\n name: \"fileId\",\n type: \"uint256\",\n },\n {\n indexed: false,\n internalType: \"string\",\n name: \"appFileId\",\n type: \"string\",\n },\n {\n indexed: true,\n internalType: \"address\",\n name: \"by\",\n type: \"address\",\n },\n ],\n name: \"DeletedFile\",\n type: \"event\",\n },\n] as const;\n","export const ADD_FILE_METHOD = [\n {\n inputs: [\n {\n internalType: \"string\",\n name: \"_appFileId\",\n type: \"string\",\n },\n {\n internalType: \"enum FileverseApp.FileType\",\n name: \"fileType\",\n type: \"uint8\",\n },\n {\n internalType: \"string\",\n name: \"_metadataIPFSHash\",\n type: \"string\",\n },\n {\n internalType: \"string\",\n name: \"_contentIPFSHash\",\n type: \"string\",\n },\n {\n internalType: \"string\",\n name: \"_gateIPFSHash\",\n type: \"string\",\n },\n {\n internalType: \"uint256\",\n name: \"version\",\n type: \"uint256\",\n },\n ],\n name: \"addFile\",\n outputs: [],\n stateMutability: \"nonpayable\",\n type: \"function\",\n },\n] as const;\n\nexport const EDIT_FILE_METHOD = [\n {\n inputs: [\n {\n internalType: \"uint256\",\n name: \"fileId\",\n type: \"uint256\",\n },\n {\n internalType: \"string\",\n name: \"_appFileId\",\n type: \"string\",\n },\n {\n internalType: \"string\",\n name: \"_metadataIPFSHash\",\n type: \"string\",\n },\n {\n internalType: \"string\",\n name: \"_contentIPFSHash\",\n type: \"string\",\n },\n {\n internalType: \"string\",\n name: \"_gateIPFSHash\",\n type: \"string\",\n },\n {\n internalType: \"enum FileverseApp.FileType\",\n name: \"fileType\",\n type: \"uint8\",\n },\n {\n internalType: \"uint256\",\n name: \"version\",\n type: \"uint256\",\n },\n ],\n name: \"editFile\",\n outputs: [],\n stateMutability: \"nonpayable\",\n type: \"function\",\n },\n] as const;\n\nexport const DELETED_FILE_ABI = [\n {\n inputs: [\n {\n internalType: \"uint256\",\n name: \"fileId\",\n type: \"uint256\",\n },\n ],\n name: \"deleteFile\",\n outputs: [],\n stateMutability: \"nonpayable\",\n type: \"function\",\n },\n] as const;\n","import { STATIC_CONFIG } from \"../cli/constants\";\nimport { getRuntimeConfig } from \"../config\";\nimport { gnosis, sepolia } from \"./chains\";\n\nexport const NETWORK_NAME = STATIC_CONFIG.NETWORK_NAME;\nexport const UPLOAD_SERVER_URL = STATIC_CONFIG.API_URL;\n\nexport const getRpcUrl = () => getRuntimeConfig().RPC_URL;\nexport const getPimlicoUrl = () => `${STATIC_CONFIG.PIMLICO_PROXY_URL}api/${NETWORK_NAME}/rpc`;\n\nconst CHAIN_MAP = {\n gnosis: gnosis,\n sepolia: sepolia,\n} as const;\n\nexport const CHAIN = CHAIN_MAP[NETWORK_NAME as keyof typeof CHAIN_MAP];\nexport { DELETED_FILE_EVENT, EDITED_FILE_EVENT, ADDED_FILE_EVENT } from \"./events\";\nexport { DELETED_FILE_ABI, EDIT_FILE_METHOD, ADD_FILE_METHOD } from \"./methods\";\n","import { createPublicClient, http, hexToBigInt, toHex, toBytes, type PrivateKeyAccount, type Hex } from \"viem\";\n\nimport { createPimlicoClient } from \"permissionless/clients/pimlico\";\nimport { createSmartAccountClient } from \"permissionless\";\nimport { toSafeSmartAccount } from \"permissionless/accounts\";\nimport { entryPoint07Address } from \"viem/account-abstraction\";\nimport { CHAIN, getRpcUrl, getPimlicoUrl } from \"../constants\";\nimport { generatePrivateKey } from \"viem/accounts\";\n\nexport const getPublicClient = () =>\n createPublicClient({\n transport: http(getRpcUrl(), {\n retryCount: 0,\n }),\n chain: CHAIN,\n });\n\nexport const getPimlicoClient = (authToken: string, portalAddress: Hex, invokerAddress: Hex) =>\n createPimlicoClient({\n transport: http(getPimlicoUrl(), {\n retryCount: 0,\n fetchOptions: {\n headers: {\n Authorization: `Bearer ${authToken}`,\n contract: portalAddress,\n invoker: invokerAddress,\n },\n },\n }),\n entryPoint: {\n address: entryPoint07Address,\n version: \"0.7\",\n },\n });\n\nexport const signerToSmartAccount = async (signer: PrivateKeyAccount) =>\n await toSafeSmartAccount({\n client: getPublicClient(),\n owners: [signer],\n entryPoint: {\n address: entryPoint07Address,\n version: \"0.7\",\n },\n version: \"1.4.1\",\n });\n\nexport const getSmartAccountClient = async (signer: PrivateKeyAccount, authToken: string, portalAddress: Hex) => {\n const smartAccount = await signerToSmartAccount(signer);\n const pimlicoClient = getPimlicoClient(authToken, portalAddress, smartAccount.address);\n\n return createSmartAccountClient({\n account: smartAccount,\n chain: CHAIN,\n paymaster: pimlicoClient,\n bundlerTransport: http(getPimlicoUrl(), {\n fetchOptions: {\n headers: {\n Authorization: `Bearer ${authToken}`,\n contract: portalAddress,\n invoker: smartAccount.address,\n },\n },\n retryCount: 0,\n }),\n userOperation: {\n estimateFeesPerGas: async () => (await pimlicoClient.getUserOperationGasPrice()).fast,\n },\n });\n};\n\nexport const getNonce = () =>\n hexToBigInt(\n toHex(toBytes(generatePrivateKey()).slice(0, 24), {\n size: 32,\n }),\n );\n\nexport const waitForUserOpReceipt = async (\n hash: Hex,\n authToken: string,\n portalAddress: Hex,\n invokerAddress: Hex,\n timeout = 120000,\n) => {\n const pimlicoClient = getPimlicoClient(authToken, portalAddress, invokerAddress);\n return pimlicoClient.waitForUserOperationReceipt({\n hash,\n timeout,\n });\n};\n","import { Hex, toHex } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { getSmartAccountClient, getNonce, waitForUserOpReceipt } from \"./pimlico-utils\";\nimport { AuthTokenProvider } from \"./auth-token-provider\";\nimport { STATIC_CONFIG } from \"../cli/constants\";\nimport { createSmartAccountClient } from \"permissionless\";\nimport type { IExecuteUserOperationRequest } from \"../types\";\n\nexport type { IExecuteUserOperationRequest };\n\nexport class AgentClient {\n private smartAccountAgent: ReturnType<typeof createSmartAccountClient> | null = null;\n private readonly MAX_CALL_GAS_LIMIT = 500000;\n private readonly authOptions: {\n namespace: string;\n segment: string;\n scheme: string;\n } = { namespace: \"proxy\", segment: \"ACCESS\", scheme: \"pimlico\" };\n\n constructor(private readonly authTokenProvider: AuthTokenProvider) {\n this.authTokenProvider = authTokenProvider;\n }\n\n async initializeAgentClient(keyMaterial: Uint8Array) {\n const agentAccount = privateKeyToAccount(toHex(keyMaterial));\n const authToken = await this.authTokenProvider.getAuthToken(STATIC_CONFIG.PROXY_SERVER_DID, this.authOptions);\n const smartAccountClient = await getSmartAccountClient(\n agentAccount,\n authToken,\n this.authTokenProvider.portalAddress,\n );\n this.smartAccountAgent = smartAccountClient;\n }\n\n getSmartAccountAgent() {\n if (!this.smartAccountAgent) throw new Error(\"Agent client not initialized\");\n\n return this.smartAccountAgent;\n }\n\n getAgentAddress() {\n const smartAccountAgent = this.getSmartAccountAgent();\n if (!smartAccountAgent.account) throw new Error(\"Agent account not found\");\n return smartAccountAgent.account.address;\n }\n\n getAgentAccount() {\n const smartAccountAgent = this.getSmartAccountAgent();\n if (!smartAccountAgent.account) throw new Error(\"Agent account not found\");\n return smartAccountAgent.account;\n }\n\n destroyAgentClient() {\n this.smartAccountAgent = null;\n }\n\n async getCallData(request: IExecuteUserOperationRequest | IExecuteUserOperationRequest[]) {\n const agentAccount = this.getAgentAccount();\n if (Array.isArray(request)) {\n if (request.length === 0 || request.length > 10) throw new Error(\"Request length must be between 1 and 10\");\n\n const encodedCallData = request.map((req) => ({\n to: req.contractAddress,\n data: req.data,\n value: BigInt(0),\n }));\n\n return await agentAccount.encodeCalls(encodedCallData);\n }\n\n return await agentAccount.encodeCalls([\n {\n to: request.contractAddress,\n data: request.data,\n value: BigInt(0),\n },\n ]);\n }\n\n async sendUserOperation(\n request: IExecuteUserOperationRequest | IExecuteUserOperationRequest[],\n customGasLimit?: number,\n ) {\n try {\n const smartAccountAgent = this.getSmartAccountAgent();\n\n const callData = await this.getCallData(request);\n\n return await smartAccountAgent.sendUserOperation({\n callData,\n callGasLimit: BigInt(customGasLimit || this.MAX_CALL_GAS_LIMIT),\n nonce: getNonce(),\n });\n } catch (error) {\n throw error;\n }\n }\n\n async executeUserOperationRequest(\n request: IExecuteUserOperationRequest | IExecuteUserOperationRequest[],\n timeout: number,\n customGasLimit?: number,\n ) {\n const userOpHash = await this.sendUserOperation(request, customGasLimit);\n const { authToken, portalAddress, invokerAddress } = await this.getAuthParams();\n const receipt = await waitForUserOpReceipt(userOpHash, authToken, portalAddress, invokerAddress, timeout);\n if (!receipt.success) throw new Error(`Failed to execute user operation: ${receipt.reason}`);\n return receipt;\n }\n\n async getAuthParams(): Promise<{ authToken: string; portalAddress: Hex; invokerAddress: Hex }> {\n const authToken = await this.authTokenProvider.getAuthToken(STATIC_CONFIG.PROXY_SERVER_DID, this.authOptions);\n return {\n authToken,\n portalAddress: this.authTokenProvider.portalAddress,\n invokerAddress: this.getAgentAddress(),\n };\n }\n}\n","import { gcm } from \"@noble/ciphers/aes.js\";\nimport { generateRandomBytes } from \"@fileverse/crypto/utils\";\n\nconst KEY_LEN = 32;\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nconst b64ToBytes = (b64: string) => Uint8Array.from(Buffer.from(b64, \"base64\"));\nconst bytesToB64 = (b: Uint8Array) => Buffer.from(b).toString(\"base64\");\n\nimport type { DecryptionOptions } from \"../types\";\nexport type { DecryptionOptions };\n\nexport function gcmEncrypt(plaintext: Uint8Array) {\n const key = generateRandomBytes(KEY_LEN);\n const iv = generateRandomBytes(IV_LEN);\n if (key.length !== KEY_LEN) throw new Error(\"key must be 32 bytes\");\n if (iv.length !== IV_LEN) throw new Error(\"iv must be 12 bytes\");\n\n const out = gcm(key, iv).encrypt(plaintext);\n const ciphertext = out.subarray(0, out.length - TAG_LEN);\n const authTag = out.subarray(out.length - TAG_LEN);\n\n return {\n ciphertext,\n authTag: bytesToB64(authTag),\n key: bytesToB64(key),\n iv: bytesToB64(iv),\n };\n}\n\nexport function gcmDecrypt(ciphertext: Uint8Array, opts: DecryptionOptions) {\n const key = b64ToBytes(opts.key);\n const iv = b64ToBytes(opts.iv);\n const tag = b64ToBytes(opts.authTag);\n if (key.length !== KEY_LEN) throw new Error(\"key must be 32 bytes\");\n if (iv.length !== IV_LEN) throw new Error(\"iv must be 12 bytes\");\n if (tag.length !== TAG_LEN) throw new Error(\"authTag must be 16 bytes\");\n\n const combined = new Uint8Array(ciphertext.length + TAG_LEN);\n combined.set(ciphertext, 0);\n combined.set(tag, ciphertext.length);\n\n return gcm(key, iv).decrypt(combined);\n}\n","import { getArgon2idHash } from \"@fileverse/crypto/argon\";\nimport { bytesToBase64, generateRandomBytes } from \"@fileverse/crypto/utils\";\nimport { derivePBKDF2Key, encryptAesCBC } from \"@fileverse/crypto/kdf\";\nimport { secretBoxEncrypt } from \"@fileverse/crypto/nacl\";\nimport hkdf from \"futoin-hkdf\";\n\nimport tweetnacl from \"tweetnacl\";\nimport { fromUint8Array, toUint8Array } from \"js-base64\";\nimport { gcmEncrypt } from \"./file-encryption\";\nimport { toAESKey, aesEncrypt } from \"@fileverse/crypto/webcrypto\";\nimport axios from \"axios\";\nimport { ADD_FILE_METHOD, DELETED_FILE_ABI, EDIT_FILE_METHOD, UPLOAD_SERVER_URL } from \"../constants\";\nimport type { UploadFileAuthParams, FileMetadataParams, UploadFilesParams } from \"../types\";\nimport { encodeFunctionData, type Hex, parseEventLogs, type Abi } from \"viem\";\n\ninterface LinkKeyMaterialParams {\n ddocId: string;\n linkKey: string | undefined;\n linkKeyNonce: string | undefined;\n}\n\nconst deriveKeyFromAg2Hash = async (pass: string, salt: Uint8Array) => {\n const key = await getArgon2idHash(pass, salt);\n\n return hkdf(Buffer.from(key), tweetnacl.secretbox.keyLength, {\n info: Buffer.from(\"encryptionKey\"),\n });\n};\n\nconst decryptSecretKey = async (docId: string, nonce: string, encryptedSecretKey: string) => {\n const derivedKey = await deriveKeyFromAg2Hash(docId, toUint8Array(nonce));\n\n return tweetnacl.secretbox.open(toUint8Array(encryptedSecretKey), toUint8Array(nonce), derivedKey);\n};\n\nconst getExistingEncryptionMaterial = async (\n existingEncryptedSecretKey: string,\n existingNonce: string,\n docId: string,\n) => {\n const secretKey = await decryptSecretKey(docId, existingNonce, existingEncryptedSecretKey);\n return {\n encryptedSecretKey: existingEncryptedSecretKey,\n nonce: toUint8Array(existingNonce),\n secretKey,\n };\n};\n\nconst getNaclSecretKey = async (ddocId: string) => {\n const { secretKey } = tweetnacl.box.keyPair();\n const nonce = tweetnacl.randomBytes(tweetnacl.secretbox.nonceLength);\n\n const derivedKey = await deriveKeyFromAg2Hash(ddocId, nonce);\n\n const encryptedSecretKey = fromUint8Array(tweetnacl.secretbox(secretKey, nonce, derivedKey), true);\n\n return { nonce, encryptedSecretKey, secretKey };\n};\n\nexport const generateLinkKeyMaterial = async (params: LinkKeyMaterialParams) => {\n if (params.linkKeyNonce && params.linkKey) {\n const { encryptedSecretKey, nonce, secretKey } = await getExistingEncryptionMaterial(\n params.linkKey,\n params.linkKeyNonce,\n params.ddocId,\n );\n if (secretKey) return { encryptedSecretKey, nonce, secretKey };\n }\n const { secretKey, nonce, encryptedSecretKey } = await getNaclSecretKey(params.ddocId);\n\n return { secretKey, nonce, encryptedSecretKey };\n};\n\nexport const jsonToFile = (json: any, fileName: string) => {\n const blob = new Blob([JSON.stringify(json)], {\n type: \"application/json\",\n });\n\n const file = new File([blob], fileName, {\n type: \"application/json\",\n });\n\n return file;\n};\n\nconst appendAuthTagIvToBlob = async (blob: Blob, authTag: Uint8Array, iv: Uint8Array) => {\n const encryptedFileBytes = await blob.arrayBuffer();\n const encryptedBytes = new Uint8Array(encryptedFileBytes);\n const combinedLength = encryptedBytes.length + authTag.length + iv.length;\n const combinedArray = new Uint8Array(combinedLength);\n\n let offset = 0;\n combinedArray.set(encryptedBytes, offset);\n offset += encryptedBytes.length;\n\n combinedArray.set(authTag, offset);\n offset += authTag.length;\n\n combinedArray.set(iv, offset);\n\n return new Blob([combinedArray], { type: blob.type });\n};\n\nexport const encryptFile = async (file: File) => {\n const arrayBuffer = await file.arrayBuffer();\n\n const plaintext = new Uint8Array(arrayBuffer);\n\n const { ciphertext, authTag, key, iv } = gcmEncrypt(plaintext);\n\n const encryptedBlob = new Blob([ciphertext], { type: file.type });\n\n const encryptedBlobWithAuthTagIv = await appendAuthTagIvToBlob(\n encryptedBlob,\n toUint8Array(authTag),\n toUint8Array(iv),\n );\n\n return {\n encryptedFile: new File([encryptedBlobWithAuthTagIv], file.name),\n key,\n };\n};\n\nexport const getNonceAppendedCipherText = (nonce: Uint8Array, cipherText: Uint8Array) => {\n return fromUint8Array(nonce, true) + \"__n__\" + fromUint8Array(cipherText, true);\n};\n\nexport const jsonToBytes = (json: Record<string, any>) => new TextEncoder().encode(JSON.stringify(json));\n\nexport const buildLinklock = (key: Uint8Array, fileKey: Uint8Array, commentKey: Uint8Array) => {\n const ikm = generateRandomBytes();\n const kdfSalt = generateRandomBytes();\n const derivedEphermalKey = derivePBKDF2Key(ikm, kdfSalt);\n\n const { iv, cipherText } = encryptAesCBC(\n {\n key: derivedEphermalKey,\n message: fileKey,\n },\n \"base64\",\n );\n\n const { iv: commentIv, cipherText: commentCipherText } = encryptAesCBC(\n {\n key: derivedEphermalKey,\n message: commentKey,\n },\n \"base64\",\n );\n\n const encryptedIkm = secretBoxEncrypt(ikm, key);\n\n const lockedFileKey = iv + \"__n__\" + cipherText;\n\n const lockedChatKey = commentIv + \"__n__\" + commentCipherText;\n\n const keyMaterial = bytesToBase64(kdfSalt) + \"__n__\" + encryptedIkm;\n\n const fileKeyNonce = generateRandomBytes(24);\n const encryptedFileKey = tweetnacl.secretbox(jsonToBytes({ key: fromUint8Array(fileKey) }), fileKeyNonce, key);\n\n const chatKeyNonce = generateRandomBytes(24);\n const encryptedChatKey = tweetnacl.secretbox(commentKey, chatKeyNonce, key);\n\n return {\n lockedFileKey: getNonceAppendedCipherText(fileKeyNonce, encryptedFileKey),\n lockedChatKey: getNonceAppendedCipherText(chatKeyNonce, encryptedChatKey),\n lockedFileKey_v2: lockedFileKey,\n lockedChatKey_v2: lockedChatKey,\n keyMaterial,\n };\n};\n\nexport const encryptTitleWithFileKey = async (args: { title: string; key: string }) => {\n const key = await toAESKey(toUint8Array(args.key));\n if (!key) throw new Error(\"Key is undefined\");\n\n const titleBytes = new TextEncoder().encode(args.title);\n\n const encryptedTitle = await aesEncrypt(key, titleBytes, \"base64\");\n\n return encryptedTitle;\n};\n\ninterface UploadFileParams {\n file: File;\n ipfsType: string;\n appFileId: string;\n}\n\nexport type { UploadFileAuthParams };\n\nexport const uploadFileToIPFS = async (fileParams: UploadFileParams, authParams: UploadFileAuthParams) => {\n const { file, ipfsType, appFileId } = fileParams;\n const { token, invoker, contractAddress } = authParams;\n\n const body = new FormData();\n body.append(\"file\", file);\n body.append(\"ipfsType\", ipfsType);\n body.append(\"appFileId\", appFileId);\n\n body.append(\"sourceApp\", \"ddoc\");\n const uploadEndpoint = UPLOAD_SERVER_URL + \"upload\";\n const response = await axios.post(uploadEndpoint, body, {\n headers: {\n Authorization: `Bearer ${token}`,\n contract: contractAddress,\n invoker: invoker,\n chain: process.env.chainId,\n },\n });\n\n return response.data.ipfsHash;\n};\n\nconst getEditFileTrxCalldata = (args: {\n fileId: number;\n appFileId: string;\n metadataHash: string;\n contentHash: string;\n gateHash: string;\n}) => {\n return encodeFunctionData({\n abi: EDIT_FILE_METHOD,\n functionName: \"editFile\",\n args: [BigInt(args.fileId), args.appFileId, args.metadataHash, args.contentHash, args.gateHash, 2, BigInt(0)],\n });\n};\n\nconst getAddFileTrxCalldata = (args: {\n appFileId: string;\n metadataHash: string;\n contentHash: string;\n gateHash: string;\n}) => {\n return encodeFunctionData({\n abi: ADD_FILE_METHOD,\n functionName: \"addFile\",\n args: [args.appFileId, 2, args.metadataHash, args.contentHash, args.gateHash, BigInt(0)],\n });\n};\n\nexport const prepareCallData = (args: {\n metadataHash: string;\n contentHash: string;\n gateHash: string;\n appFileId: string;\n fileId?: number;\n}) => {\n if (args.fileId) {\n return getEditFileTrxCalldata({\n fileId: args.fileId,\n appFileId: args.appFileId,\n metadataHash: args.metadataHash,\n contentHash: args.contentHash,\n gateHash: args.gateHash,\n });\n }\n return getAddFileTrxCalldata(args);\n};\n\nexport const prepareDeleteFileCallData = (args: { onChainFileId: number }) => {\n return encodeFunctionData({\n abi: DELETED_FILE_ABI,\n functionName: \"deleteFile\",\n args: [BigInt(args.onChainFileId)],\n });\n};\n\nexport const createEncryptedContentFile = async (content: any) => {\n const contentFile = jsonToFile(\n { file: content, source: \"ddoc\" },\n `${fromUint8Array(generateRandomBytes(16))}-CONTENT`,\n );\n return encryptFile(contentFile);\n};\n\nexport type { FileMetadataParams };\n\nexport const buildFileMetadata = (params: FileMetadataParams) => ({\n title: params.encryptedTitle,\n size: params.encryptedFileSize,\n mimeType: \"application/json\",\n appLock: params.appLock,\n ownerLock: params.ownerLock,\n ddocId: params.ddocId,\n nonce: params.nonce,\n owner: params.owner,\n version: \"4\",\n sourceApp: \"fileverse-api\",\n});\n\nexport const parseFileEventLog = (logs: any[], eventName: string, abi: Abi): number => {\n const [parsedLog] = parseEventLogs({ abi, logs, eventName });\n\n if (!parsedLog) throw new Error(`${eventName} event not found`);\n\n const fileId = (parsedLog as any).args.fileId;\n\n if (fileId === undefined || fileId === null) throw new Error(\"FileId not found in event logs\");\n\n return Number(fileId);\n};\n\nexport type { UploadFilesParams };\n\nexport const uploadAllFilesToIPFS = async (params: UploadFilesParams, authParams: UploadFileAuthParams) => {\n const { metadata, encryptedFile, linkLock, ddocId } = params;\n\n const [metadataHash, contentHash, gateHash] = await Promise.all([\n uploadFileToIPFS(\n {\n file: jsonToFile(metadata, `${fromUint8Array(generateRandomBytes(16))}-METADATA`),\n ipfsType: \"METADATA\",\n appFileId: ddocId,\n },\n authParams,\n ),\n uploadFileToIPFS(\n {\n file: encryptedFile,\n ipfsType: \"CONTENT\",\n appFileId: ddocId,\n },\n authParams,\n ),\n uploadFileToIPFS(\n {\n file: jsonToFile(linkLock, `${fromUint8Array(generateRandomBytes(16))}-GATE`),\n ipfsType: \"GATE\",\n appFileId: ddocId,\n },\n authParams,\n ),\n ]);\n\n return { metadataHash, contentHash, gateHash };\n};\n","import { fromUint8Array, toUint8Array } from \"js-base64\";\nimport { KeyStore } from \"./key-store\";\nimport {\n buildLinklock,\n encryptTitleWithFileKey,\n generateLinkKeyMaterial,\n prepareCallData,\n createEncryptedContentFile,\n buildFileMetadata,\n parseFileEventLog,\n uploadAllFilesToIPFS,\n UploadFileAuthParams,\n prepareDeleteFileCallData,\n} from \"./file-utils\";\nimport { AgentClient } from \"./smart-agent\";\nimport { generateAESKey, exportAESKey } from \"@fileverse/crypto/webcrypto\";\nimport { STATIC_CONFIG } from \"../cli/constants\";\nimport { DELETED_FILE_EVENT, EDITED_FILE_EVENT } from \"../constants\";\nimport { markdownToYjs } from \"@fileverse/content-processor\";\nimport { logger } from \"../infra\";\n\nexport class FileManager {\n private keyStore: KeyStore;\n private agentClient: AgentClient;\n\n constructor(keyStore: KeyStore, agentClient: AgentClient) {\n this.keyStore = keyStore;\n this.agentClient = agentClient;\n }\n\n private createLocks(key: string, encryptedSecretKey: string, commentKey: Uint8Array) {\n const appLock = {\n lockedFileKey: this.keyStore.encryptData(toUint8Array(key)),\n lockedLinkKey: this.keyStore.encryptData(toUint8Array(encryptedSecretKey)),\n lockedChatKey: this.keyStore.encryptData(commentKey),\n };\n return { appLock, ownerLock: { ...appLock } };\n }\n\n private async getAuthParams(): Promise<UploadFileAuthParams> {\n return {\n token: await this.keyStore.getAuthToken(STATIC_CONFIG.SERVER_DID),\n contractAddress: this.keyStore.getPortalAddress(),\n invoker: this.agentClient.getAgentAddress(),\n };\n }\n\n private async executeFileOperation(callData: `0x${string}`) {\n return this.agentClient.executeUserOperationRequest(\n {\n contractAddress: this.keyStore.getPortalAddress(),\n data: callData,\n },\n 1000000,\n );\n }\n\n private async sendFileOperation(callData: `0x${string}`) {\n return this.agentClient.sendUserOperation(\n {\n contractAddress: this.keyStore.getPortalAddress(),\n data: callData,\n },\n 1000000,\n );\n }\n\n async getProxyAuthParams() {\n return this.agentClient.getAuthParams();\n }\n\n async submitAddFileTrx(file: any) {\n logger.debug(`Preparing to add file ${file.ddocId}`);\n const { encryptedSecretKey, nonce, secretKey } = await generateLinkKeyMaterial({\n ddocId: file.ddocId,\n linkKey: file.linkKey,\n linkKeyNonce: file.linkKeyNonce,\n });\n\n const yJSContent = markdownToYjs(file.content);\n const { encryptedFile, key } = await createEncryptedContentFile(yJSContent);\n logger.debug(`Generated encrypted content file for file ${file.ddocId}`);\n const commentKey = await exportAESKey(await generateAESKey(128));\n\n const { appLock, ownerLock } = this.createLocks(key, encryptedSecretKey, commentKey);\n const linkLock = buildLinklock(secretKey, toUint8Array(key), commentKey);\n\n const encryptedTitle = await encryptTitleWithFileKey({\n title: file.title || \"Untitled\",\n key,\n });\n const metadata = buildFileMetadata({\n encryptedTitle,\n encryptedFileSize: encryptedFile.size,\n appLock,\n ownerLock,\n ddocId: file.ddocId,\n nonce: fromUint8Array(nonce),\n owner: this.agentClient.getAgentAddress(),\n });\n\n const authParams = await this.getAuthParams();\n const { metadataHash, contentHash, gateHash } = await uploadAllFilesToIPFS(\n { metadata, encryptedFile, linkLock, ddocId: file.ddocId },\n authParams,\n );\n logger.debug(`Uploaded files to IPFS for file ${file.ddocId}`);\n\n const callData = prepareCallData({\n metadataHash,\n contentHash,\n gateHash,\n appFileId: file.ddocId,\n fileId: file.fileId,\n });\n logger.debug(`Prepared call data for file ${file.ddocId}`);\n\n const userOpHash = await this.sendFileOperation(callData);\n logger.debug(`Submitted user op for file ${file.ddocId}`);\n return {\n userOpHash,\n linkKey: encryptedSecretKey,\n linkKeyNonce: fromUint8Array(nonce),\n commentKey: fromUint8Array(commentKey),\n metadata,\n };\n }\n\n async updateFile(file: any) {\n logger.debug(`Updating file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n const { encryptedSecretKey, nonce, secretKey } = await generateLinkKeyMaterial({\n ddocId: file.ddocId,\n linkKey: file.linkKey,\n linkKeyNonce: file.linkKeyNonce,\n });\n\n logger.debug(`Generating encrypted content file for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n\n const yjsContent = markdownToYjs(file.content);\n const { encryptedFile, key } = await createEncryptedContentFile(yjsContent);\n const commentKey = toUint8Array(file.commentKey);\n\n const { appLock, ownerLock } = this.createLocks(key, encryptedSecretKey, commentKey);\n const linkLock = buildLinklock(secretKey, toUint8Array(key), commentKey);\n\n const encryptedTitle = await encryptTitleWithFileKey({\n title: file.title || \"Untitled\",\n key,\n });\n const metadata = buildFileMetadata({\n encryptedTitle,\n encryptedFileSize: encryptedFile.size,\n appLock,\n ownerLock,\n ddocId: file.ddocId,\n nonce: fromUint8Array(nonce),\n owner: this.agentClient.getAgentAddress(),\n });\n\n const authParams = await this.getAuthParams();\n logger.debug(`Uploading files to IPFS for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n const { metadataHash, contentHash, gateHash } = await uploadAllFilesToIPFS(\n { metadata, encryptedFile, linkLock, ddocId: file.ddocId },\n authParams,\n );\n\n const callData = prepareCallData({\n metadataHash,\n contentHash,\n gateHash,\n appFileId: file.ddocId,\n fileId: file.onChainFileId,\n });\n logger.debug(`Executing file operation for file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n\n const { logs } = await this.executeFileOperation(callData);\n const onChainFileId = parseFileEventLog(logs, \"EditedFile\", EDITED_FILE_EVENT);\n\n return { onChainFileId, metadata };\n }\n\n async deleteFile(file: any) {\n logger.debug(`Deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n const callData = prepareDeleteFileCallData({\n onChainFileId: file.onChainFileId,\n });\n logger.debug(`Prepared call data for deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n\n const { logs } = await this.executeFileOperation(callData);\n parseFileEventLog(logs, \"DeletedFile\", DELETED_FILE_EVENT);\n logger.debug(`Executed file operation for deleting file ${file.ddocId} with onChainFileId ${file.onChainFileId}`);\n return {\n fileId: file.id,\n onChainFileId: file.onChainFileId,\n metadata: file.metadata,\n };\n }\n}\n","import { FilesModel, PortalsModel } from \"../../infra/database/models\";\nimport { logger } from \"../../infra\";\nimport { KeyStore } from \"../../sdk/key-store\";\nimport { AuthTokenProvider } from \"../../sdk/auth-token-provider\";\nimport { fromUint8Array, toUint8Array } from \"js-base64\";\nimport { Hex, stringToBytes } from \"viem\";\nimport { deriveHKDFKey } from \"@fileverse/crypto/kdf\";\nimport { generateKeyPairFromSeed } from \"@stablelib/ed25519\";\nimport * as ucans from \"@ucans/ucans\";\nimport { AgentClient } from \"../../sdk/smart-agent\";\nimport { FileManager } from \"../../sdk/file-manager\";\nimport { getRuntimeConfig } from \"../../config\";\n\nimport type { PublishResult } from \"../../types\";\nimport type { File, Portal } from \"../../types\";\n\ninterface PublishContext {\n file: File | undefined;\n portalDetails: Portal;\n apiKey: string;\n}\n\nasync function getPortalData(fileId: string): Promise<PublishContext> {\n const file = await FilesModel.findByIdIncludingDeleted(fileId);\n if (!file) {\n throw new Error(`File with _id ${fileId} not found`);\n }\n\n const portalDetails = await PortalsModel.findByPortalAddress(file.portalAddress);\n if (!portalDetails) {\n throw new Error(`Portal with address ${file.portalAddress} not found`);\n }\n\n const apiKey = getRuntimeConfig().API_KEY;\n if (!apiKey) {\n throw new Error(\"API key is not set\");\n }\n\n return { file, portalDetails, apiKey };\n}\n\nfunction deriveCollaboratorKeys(apiKeySeed: Uint8Array) {\n const salt = new Uint8Array([0]);\n\n const privateAccountKey = deriveHKDFKey(apiKeySeed, salt, stringToBytes(\"COLLABORATOR_PRIVATE_KEY\"));\n\n const ucanDerivedSecret = deriveHKDFKey(apiKeySeed, salt, stringToBytes(\"COLLABORATOR_UCAN_SECRET\"));\n\n const { secretKey: ucanSecret } = generateKeyPairFromSeed(ucanDerivedSecret);\n\n return { privateAccountKey, ucanSecret };\n}\n\nconst createFileManager = async (\n portalSeed: string,\n portalAddress: Hex,\n ucanSecret: Uint8Array,\n privateAccountKey: Uint8Array,\n): Promise<FileManager> => {\n const keyPair = ucans.EdKeypair.fromSecretKey(fromUint8Array(ucanSecret), {\n exportable: true,\n });\n\n const authTokenProvider = new AuthTokenProvider(keyPair, portalAddress);\n const keyStore = new KeyStore(toUint8Array(portalSeed), portalAddress, authTokenProvider);\n\n const agentClient = new AgentClient(authTokenProvider);\n await agentClient.initializeAgentClient(privateAccountKey);\n\n return new FileManager(keyStore, agentClient);\n};\n\nconst executeOperation = async (\n fileManager: FileManager,\n file: any,\n operation: \"update\" | \"delete\",\n): Promise<PublishResult> => {\n\n if (operation === \"update\") {\n const result = await fileManager.updateFile(file);\n return { success: true, ...result };\n }\n\n if (operation === \"delete\") {\n const result = await fileManager.deleteFile(file);\n return { success: true, ...result };\n }\n\n throw new Error(`Invalid operation: ${operation}`);\n};\n\nexport const handleExistingFileOp = async (fileId: string, operation: \"update\" | \"delete\"): Promise<PublishResult> => {\n try {\n const { file, portalDetails, apiKey } = await getPortalData(fileId);\n\n const apiKeySeed = toUint8Array(apiKey);\n const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);\n\n const fileManager = await createFileManager(\n portalDetails.portalSeed,\n portalDetails.portalAddress as Hex,\n ucanSecret,\n privateAccountKey,\n );\n\n return executeOperation(fileManager, file, operation);\n } catch (error: any) {\n logger.error(`Failed to publish file ${fileId}:`, error);\n throw error;\n }\n};\n\nexport const handleNewFileOp = async (\n fileId: string,\n): Promise<{\n userOpHash: string;\n linkKey: string;\n linkKeyNonce: string;\n commentKey: string;\n metadata: Record<string, unknown>;\n}> => {\n const { file, portalDetails, apiKey } = await getPortalData(fileId);\n const apiKeySeed = toUint8Array(apiKey);\n const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);\n const fileManager = await createFileManager(\n portalDetails.portalSeed,\n portalDetails.portalAddress as Hex,\n ucanSecret,\n privateAccountKey,\n );\n return fileManager.submitAddFileTrx(file);\n};\n\nexport const getProxyAuthParams = async (\n fileId: string,\n): Promise<{\n authToken: string;\n portalAddress: Hex;\n invokerAddress: Hex;\n}> => {\n const { portalDetails, apiKey } = await getPortalData(fileId);\n const apiKeySeed = toUint8Array(apiKey);\n const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);\n const fileManager = await createFileManager(\n portalDetails.portalSeed,\n portalDetails.portalAddress as Hex,\n ucanSecret,\n privateAccountKey,\n );\n return fileManager.getProxyAuthParams();\n};\n","import { PortalsModel } from \"../../infra/database/models\";\nimport type { Portal, SavePortalInput } from \"../../types\";\n\nexport async function savePortal(input: SavePortalInput): Promise<Portal> {\n if (!input.portalAddress || !input.portalSeed || !input.ownerAddress) {\n throw new Error(\"portalAddress, portalSeed, and ownerAddress are required\");\n }\n\n return PortalsModel.upsert(input);\n}\n","import { ApiKeysModel, PortalsModel } from \"../../infra/database/models\";\nimport type { AddApiKeyInput, ApiKey } from \"../../types\";\n\nexport async function addApiKey(input: AddApiKeyInput): Promise<ApiKey> {\n if (!input.apiKeySeed || !input.name || !input.collaboratorAddress || !input.portalAddress) {\n throw new Error(\"apiKeySeed, name, collaboratorAddress, and portalAddress are required\");\n }\n\n const portal = await PortalsModel.findByPortalAddress(input.portalAddress);\n if (!portal) {\n throw new Error(`Portal with address ${input.portalAddress} does not exist`);\n }\n\n return ApiKeysModel.create(input);\n}\n","import { ApiKeysModel, type ApiKey } from \"../../infra/database/models\";\n\nexport async function removeApiKey(collaboratorAddress: string): Promise<ApiKey> {\n if (!collaboratorAddress) {\n throw new Error(\"collaboratorAddress is required\");\n }\n\n const apiKey = await ApiKeysModel.findByCollaboratorAddress(collaboratorAddress);\n if (!apiKey) {\n throw new Error(\"API key not found\");\n }\n\n await ApiKeysModel.delete(apiKey._id);\n return { ...apiKey, isDeleted: 1 };\n}\n","import { handleExistingFileOp, handleNewFileOp, getProxyAuthParams } from \"./publish\";\nimport { savePortal } from \"./savePortal\";\nimport { addApiKey } from \"./saveApiKey\";\nimport { removeApiKey } from \"./removeApiKey\";\n\nexport { handleExistingFileOp, handleNewFileOp, getProxyAuthParams, savePortal, addApiKey, removeApiKey };\n","import { HttpRequestError } from \"viem\";\n\nexport class RateLimitError extends Error {\n readonly retryAfterSeconds: number;\n\n constructor(retryAfterSeconds: number, message = \"Rate limit exceeded\") {\n super(message);\n this.name = \"RateLimitError\";\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\nconst MAX_RETRY_AFTER_SECONDS = 300;\nconst DEFAULT_RETRY_AFTER_SECONDS = 3600;\n\nfunction parseRetryAfterRaw(raw: string | null): number {\n if (!raw) return DEFAULT_RETRY_AFTER_SECONDS;\n const parsed = parseInt(raw, 10);\n if (!Number.isNaN(parsed) && parsed >= 0) return Math.min(parsed, MAX_RETRY_AFTER_SECONDS);\n const date = Date.parse(raw);\n if (!Number.isNaN(date)) {\n const seconds = Math.max(0, Math.ceil((date - Date.now()) / 1000));\n return Math.min(seconds, MAX_RETRY_AFTER_SECONDS);\n }\n return DEFAULT_RETRY_AFTER_SECONDS;\n}\n\nexport const parseRetryAfterSeconds = (response: Response): number =>\n parseRetryAfterRaw(response.headers.get(\"Retry-After\"));\n\nexport const parseRetryAfterFromHeaders = (headers?: Headers): number =>\n parseRetryAfterRaw(headers?.get(\"Retry-After\") ?? null);\n\nexport function normalizeRateLimitError(error: unknown): unknown {\n if (!(error instanceof HttpRequestError) || error.status !== 429) return error;\n const retryAfter = parseRetryAfterFromHeaders(error.headers);\n const message = \"Beta API rate limit reached. Try again in an hour please!\"\n return new RateLimitError(retryAfter, message);\n}\n","import { getRuntimeConfig } from \"../../config\";\nimport { handleNewFileOp, getProxyAuthParams, handleExistingFileOp } from \"../../domain/portal\";\nimport { FilesModel, EventsModel } from \"../database/models\";\nimport type { Event, ProcessResult, UpdateFilePayload } from \"../../types\";\nimport { logger } from \"../index\";\nimport { waitForUserOpReceipt } from \"../../sdk/pimlico-utils\";\nimport { parseFileEventLog } from \"../../sdk/file-utils\";\nimport { ADDED_FILE_EVENT } from \"../../constants\";\nimport { RateLimitError, normalizeRateLimitError } from \"../../errors/rate-limit\";\n\nexport type { ProcessResult };\n\nexport const processEvent = async (event: Event): Promise<ProcessResult> => {\n const { fileId, type } = event;\n\n try {\n switch (type) {\n case \"create\":\n await processCreateEvent(event);\n break;\n case \"update\":\n await processUpdateEvent(event);\n break;\n case \"delete\":\n await processDeleteEvent(event);\n break;\n default:\n throw new Error(`Unknown event type: ${type}`);\n }\n return { success: true };\n } catch (error) {\n const normalized = normalizeRateLimitError(error);\n if (normalized instanceof RateLimitError) throw normalized;\n const errorMsg = error instanceof Error ? error.message : String(error);\n logger.error(`Error processing ${type} event for file ${fileId}:`, errorMsg);\n return { success: false, error: errorMsg };\n }\n};\n\nconst onTransactionSuccess = async (\n fileId: string,\n file: Awaited<ReturnType<typeof FilesModel.findByIdIncludingDeleted>>,\n onChainFileId: number,\n pending: { linkKey: string; linkKeyNonce: string; commentKey: string; metadata: Record<string, unknown> },\n): Promise<void> => {\n const frontendUrl = getRuntimeConfig().FRONTEND_URL;\n const payload: UpdateFilePayload = {\n onchainVersion: file!.localVersion,\n onChainFileId,\n linkKey: pending.linkKey,\n linkKeyNonce: pending.linkKeyNonce,\n commentKey: pending.commentKey,\n metadata: pending.metadata,\n link: `${frontendUrl}/${file!.portalAddress}/${onChainFileId}#key=${pending.linkKey}`,\n };\n const updatedFile = await FilesModel.update(fileId, payload, file!.portalAddress);\n if (updatedFile.localVersion === updatedFile.onchainVersion) {\n await FilesModel.update(fileId, { syncStatus: \"synced\" }, file!.portalAddress);\n }\n};\n\nconst processCreateEvent = async (event: Event): Promise<void> => {\n const { fileId } = event;\n\n const file = await FilesModel.findByIdIncludingDeleted(fileId);\n if (!file) {\n throw new Error(`File ${fileId} not found`);\n }\n\n if (file.isDeleted === 1) {\n logger.info(`File ${fileId} is deleted, skipping create event`);\n return;\n }\n\n const waitContext = await getProxyAuthParams(fileId);\n const timeout = 120000;\n\n if (event.userOpHash) {\n const receipt = await waitForUserOpReceipt(\n event.userOpHash as `0x${string}`,\n waitContext.authToken,\n waitContext.portalAddress,\n waitContext.invokerAddress,\n timeout,\n );\n if (!receipt.success) {\n await EventsModel.clearEventPendingOp(event._id);\n throw new Error(`User operation failed: ${receipt.reason}`);\n }\n const onChainFileId = parseFileEventLog(receipt.logs, \"AddedFile\", ADDED_FILE_EVENT);\n const pending = JSON.parse(event.pendingPayload!) as {\n linkKey: string;\n linkKeyNonce: string;\n commentKey: string;\n metadata: Record<string, unknown>;\n };\n await onTransactionSuccess(fileId, file, onChainFileId, pending);\n await EventsModel.clearEventPendingOp(event._id);\n logger.info(`File ${file.ddocId} created and published successfully (resumed from pending op)`);\n return;\n }\n\n const result = await handleNewFileOp(fileId);\n await EventsModel.setEventPendingOp(event._id, result.userOpHash, {\n linkKey: result.linkKey,\n linkKeyNonce: result.linkKeyNonce,\n commentKey: result.commentKey,\n metadata: result.metadata,\n });\n\n const receipt = await waitForUserOpReceipt(\n result.userOpHash as `0x${string}`,\n waitContext.authToken,\n waitContext.portalAddress,\n waitContext.invokerAddress,\n timeout,\n );\n if (!receipt.success) {\n await EventsModel.clearEventPendingOp(event._id);\n throw new Error(`User operation failed: ${receipt.reason}`);\n }\n const onChainFileId = parseFileEventLog(receipt.logs, \"AddedFile\", ADDED_FILE_EVENT);\n await onTransactionSuccess(fileId, file, onChainFileId, {\n linkKey: result.linkKey,\n linkKeyNonce: result.linkKeyNonce,\n commentKey: result.commentKey,\n metadata: result.metadata,\n });\n await EventsModel.clearEventPendingOp(event._id);\n logger.info(`File ${file.ddocId} created and published successfully`);\n};\n\nconst processUpdateEvent = async (event: Event): Promise<void> => {\n const { fileId } = event;\n\n const file = await FilesModel.findByIdExcludingDeleted(fileId);\n if (!file) {\n return;\n }\n\n if (file.localVersion <= file.onchainVersion) {\n return;\n }\n\n const result = await handleExistingFileOp(fileId, \"update\");\n if (!result.success) {\n throw new Error(`Publish failed for file ${fileId}`);\n }\n\n const payload: UpdateFilePayload = {\n onchainVersion: file.localVersion,\n metadata: result.metadata,\n };\n const updatedFile = await FilesModel.update(fileId, payload, file.portalAddress);\n\n if (updatedFile.localVersion === updatedFile.onchainVersion) {\n await FilesModel.update(fileId, { syncStatus: \"synced\" }, file.portalAddress);\n }\n logger.info(`File ${file.ddocId} updated and published successfully`);\n};\n\nconst processDeleteEvent = async (event: Event): Promise<void> => {\n const { fileId } = event;\n\n const file = await FilesModel.findByIdIncludingDeleted(fileId);\n if (!file) {\n return;\n }\n\n if (file.isDeleted === 1 && file.syncStatus === \"synced\") {\n logger.info(`File ${fileId} deletion already synced, skipping`);\n return;\n }\n\n const payload: UpdateFilePayload = {\n syncStatus: \"synced\",\n isDeleted: 1,\n };\n\n if (file.onChainFileId !== null || file.onChainFileId !== undefined) {\n const result = await handleExistingFileOp(fileId, \"delete\");\n if (!result.success) {\n throw new Error(`Publish failed for file ${fileId}`);\n }\n\n payload.onchainVersion = file.localVersion;\n payload.metadata = result.metadata;\n payload.isDeleted = 1;\n }\n\n await FilesModel.update(fileId, payload, file.portalAddress);\n\n logger.info(`File ${fileId} delete event processed (syncStatus set to synced)`);\n};\n","import { logger } from \"../index\";\nimport { processEvent } from \"./eventProcessor\";\nimport { onNewEvent } from \"./workerSignal\";\nimport { EventsModel } from \"../database/models\";\nimport type { Event } from \"../database/models\";\nimport { RateLimitError } from \"../../errors/rate-limit\";\n\nconst DEFAULT_CONCURRENCY = 5;\nconst STALE_THRESHOLD_MS = 5 * 60 * 1000;\nconst SIGNAL_RETRY_DELAY_MS = 50;\nconst FALLBACK_POLL_MS = 30000;\nconst MAX_RETRIES = 10;\n\nexport class FileEventsWorker {\n private isRunning = false;\n private concurrency: number;\n private activeProcessors = new Map<string, Promise<void>>();\n private signalCleanup: (() => void) | null = null;\n private pendingSignal = false;\n private wakeResolver: (() => void) | null = null;\n\n constructor(concurrency: number = DEFAULT_CONCURRENCY) {\n this.concurrency = concurrency;\n }\n\n async start(): Promise<void> {\n if (this.isRunning) {\n logger.warn(\"Worker is already running\");\n return;\n }\n this.isRunning = true;\n\n const staleCount = await this.recoverStaleEvents();\n if (staleCount > 0) {\n logger.info(`Recovered ${staleCount} stale event(s)`);\n }\n\n this.signalCleanup = onNewEvent(() => {\n this.pendingSignal = true;\n this.wakeUp();\n });\n\n logger.debug(`File events worker started (concurrency: ${this.concurrency})`);\n this.run();\n }\n\n private async run(): Promise<void> {\n while (this.isRunning) {\n const foundEvents = await this.fillSlots();\n logger.debug(`Found ${foundEvents ? \"events\" : \"no events\"} to process`);\n if (this.activeProcessors.size === 0) {\n if (this.pendingSignal && !foundEvents) {\n this.pendingSignal = false;\n await this.sleep(SIGNAL_RETRY_DELAY_MS);\n continue;\n }\n\n this.pendingSignal = false;\n await this.waitForSignalOrTimeout(FALLBACK_POLL_MS);\n } else {\n await Promise.race(this.activeProcessors.values());\n }\n }\n }\n\n private async fillSlots(): Promise<boolean> {\n let foundAny = false;\n\n while (this.activeProcessors.size < this.concurrency && this.isRunning) {\n const lockedFileIds = Array.from(this.activeProcessors.keys());\n const event = await EventsModel.findNextEligible(lockedFileIds);\n\n if (!event) break;\n\n foundAny = true;\n await EventsModel.markProcessing(event._id);\n const processor = this.processEventWrapper(event);\n this.activeProcessors.set(event.fileId, processor);\n }\n\n logger.debug(`Slots filled: ${this.activeProcessors.size}`);\n return foundAny;\n }\n\n private async processEventWrapper(event: Event): Promise<void> {\n try {\n const result = await processEvent(event);\n if (result.success) {\n await EventsModel.markProcessed(event._id);\n } else {\n await this.handleFailure(event, result.error);\n }\n } catch (err) {\n await this.handleFailure(event, err);\n } finally {\n this.activeProcessors.delete(event.fileId);\n }\n }\n\n private async handleFailure(event: Event, error: unknown): Promise<void> {\n const errorMsg = error instanceof Error ? error.message : String(error);\n if (error instanceof RateLimitError) {\n const retryAfterMs = error.retryAfterSeconds * 1000;\n await EventsModel.scheduleRetryAfter(event._id, errorMsg, retryAfterMs);\n logger.warn(`Event ${event._id} rate limited; retry after ${error.retryAfterSeconds}s`);\n return;\n }\n if (event.retryCount < MAX_RETRIES) {\n await EventsModel.scheduleRetry(event._id, errorMsg);\n logger.warn(`Event ${event._id} failed (retry ${event.retryCount + 1}/${MAX_RETRIES}): ${errorMsg}`);\n } else {\n await EventsModel.markFailed(event._id, errorMsg);\n logger.error(`Event ${event._id} permanently failed after ${MAX_RETRIES} retries: ${errorMsg}`);\n }\n }\n\n private async recoverStaleEvents(): Promise<number> {\n const staleThreshold = Date.now() - STALE_THRESHOLD_MS;\n return EventsModel.resetStaleEvents(staleThreshold);\n }\n\n private wakeUp(): void {\n if (this.wakeResolver) {\n this.wakeResolver();\n this.wakeResolver = null;\n }\n }\n\n private waitForSignalOrTimeout(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timeout = setTimeout(() => {\n this.wakeResolver = null;\n resolve();\n }, ms);\n\n this.wakeResolver = () => {\n clearTimeout(timeout);\n resolve();\n };\n });\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n async close(): Promise<void> {\n if (!this.isRunning) {\n return;\n }\n logger.info(\"Closing worker gracefully...\");\n this.isRunning = false;\n\n if (this.signalCleanup) {\n this.signalCleanup();\n this.signalCleanup = null;\n }\n\n this.wakeUp();\n this.wakeResolver = null;\n\n if (this.activeProcessors.size > 0) {\n logger.info(`Waiting for ${this.activeProcessors.size} active processor(s) to complete...`);\n await Promise.all(this.activeProcessors.values());\n }\n\n logger.info(\"Worker closed\");\n }\n\n isActive(): boolean {\n return this.isRunning;\n }\n\n getActiveCount(): number {\n return this.activeProcessors.size;\n }\n}\n\nexport function createWorker(concurrency: number = DEFAULT_CONCURRENCY): FileEventsWorker {\n return new FileEventsWorker(concurrency);\n}\n","export { createWorker, FileEventsWorker } from \"./worker\";\nexport { notifyNewEvent } from \"./workerSignal\";\n","import { createWorker, type FileEventsWorker } from \"./infra/worker\";\n\nconst DEFAULT_CONCURRENCY = 5;\n\nlet worker: FileEventsWorker | null = null;\n\nexport async function startWorker(concurrency: number = DEFAULT_CONCURRENCY): Promise<void> {\n if (worker?.isActive()) {\n return;\n }\n worker = createWorker(concurrency);\n await worker.start();\n}\n\nexport async function closeWorker(): Promise<void> {\n if (worker) {\n await worker.close();\n worker = null;\n }\n}\n\nexport function isWorkerActive(): boolean {\n return worker?.isActive() ?? false;\n}\n\nexport function getWorkerActiveCount(): number {\n return worker?.getActiveCount() ?? 0;\n}\n","// Error reporting service\n// Example: Slack, Sentry, etc.\n\nclass Reporter {\n async reportError(message: string): Promise<void> {\n // Implement your error reporting logic\n console.error(\"Error reported:\", message);\n }\n}\n\nexport default new Reporter();\n","import { logger } from \"./logger\";\nimport { asyncHandler, asyncHandlerArray } from \"./asyncHandler\";\nimport { closeWorker } from \"../appWorker\";\nimport { closeDatabase } from \"./database\";\n\nimport reporter from \"./reporter\";\n\nexport { logger, asyncHandler, asyncHandlerArray, reporter, closeWorker, closeDatabase };\n","/**\n * Converts SQLite-style `?` placeholders to PostgreSQL-style `$1, $2, ...`\n * Skips placeholders inside single-quoted string literals.\n */\nexport function sqliteToPostgres(sql: string): string {\n let result = \"\";\n let paramIndex = 0;\n let inString = false;\n\n for (let i = 0; i < sql.length; i++) {\n const ch = sql[i];\n\n if (ch === \"'\") {\n // Handle escaped single quotes ('')\n if (inString && i + 1 < sql.length && sql[i + 1] === \"'\") {\n result += \"''\";\n i++;\n continue;\n }\n inString = !inString;\n result += ch;\n } else if (ch === \"?\" && !inString) {\n paramIndex++;\n result += `$${paramIndex}`;\n } else {\n result += ch;\n }\n }\n\n return result;\n}\n","import type { DatabaseAdapter, ExecuteResult } from \"./adapter.js\";\nimport { sqliteToPostgres } from \"./sql-compat.js\";\nimport { logger } from \"../../index.js\";\n\n// pg is lazy-imported so SQLite-only users don't need it installed\nlet pgModule: typeof import(\"pg\") | null = null;\n\nasync function getPg() {\n if (!pgModule) {\n pgModule = await import(\"pg\");\n }\n return pgModule;\n}\n\nexport class PostgresAdapter implements DatabaseAdapter {\n private pool: InstanceType<typeof import(\"pg\").Pool> | null = null;\n private connectionUrl: string;\n private connected = false;\n readonly dialect = \"postgres\" as const;\n\n constructor(connectionUrl: string) {\n this.connectionUrl = connectionUrl;\n }\n\n private async getPool() {\n if (!this.pool) {\n const pg = await getPg();\n this.pool = new pg.default.Pool({\n connectionString: this.connectionUrl,\n max: 10,\n ssl: this.connectionUrl.includes(\"sslmode=require\") || this.connectionUrl.includes(\"amazonaws.com\") || this.connectionUrl.includes(\"heroku\")\n ? { rejectUnauthorized: false }\n : undefined,\n });\n // Verify connectivity\n const client = await this.pool.connect();\n client.release();\n this.connected = true;\n logger.info(\"PostgreSQL database connected\");\n }\n return this.pool;\n }\n\n async select<T>(sql: string, params: any[] = []): Promise<T[]> {\n const pool = await this.getPool();\n const pgSql = sqliteToPostgres(sql);\n const result = await pool.query(pgSql, params);\n return result.rows as T[];\n }\n\n async selectOne<T>(sql: string, params: any[] = []): Promise<T | undefined> {\n const pool = await this.getPool();\n const pgSql = sqliteToPostgres(sql);\n const result = await pool.query(pgSql, params);\n return (result.rows[0] as T) ?? undefined;\n }\n\n async execute(sql: string, params: any[] = []): Promise<ExecuteResult> {\n const pool = await this.getPool();\n const pgSql = sqliteToPostgres(sql);\n const result = await pool.query(pgSql, params);\n return {\n changes: result.rowCount ?? 0,\n lastInsertRowid: 0,\n };\n }\n\n async transaction<T>(callback: () => Promise<T>): Promise<T> {\n const pool = await this.getPool();\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n const result = await callback();\n await client.query(\"COMMIT\");\n return result;\n } catch (error) {\n await client.query(\"ROLLBACK\");\n throw error;\n } finally {\n client.release();\n }\n }\n\n async exec(sql: string): Promise<void> {\n const pool = await this.getPool();\n await pool.query(sql);\n }\n\n async close(): Promise<void> {\n if (this.pool) {\n await this.pool.end();\n this.pool = null;\n this.connected = false;\n logger.info(\"Database connection closed\");\n }\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n}\n","import Database from \"better-sqlite3\";\nimport type { DatabaseAdapter, ExecuteResult } from \"./adapter.js\";\nimport { logger } from \"../../index.js\";\n\nexport class SqliteAdapter implements DatabaseAdapter {\n private db: Database.Database | null = null;\n readonly dialect = \"sqlite\" as const;\n\n constructor(private dbPath: string) {}\n\n private getDb(): Database.Database {\n if (!this.db) {\n this.db = new Database(this.dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n this.db.pragma(\"foreign_keys = ON\");\n this.db.prepare(\"SELECT 1\").get();\n logger.info(`SQLite database connected: ${this.dbPath}`);\n }\n return this.db;\n }\n\n async select<T>(sql: string, params: any[] = []): Promise<T[]> {\n const stmt = this.getDb().prepare(sql);\n return stmt.all(params) as T[];\n }\n\n async selectOne<T>(sql: string, params: any[] = []): Promise<T | undefined> {\n const stmt = this.getDb().prepare(sql);\n return stmt.get(params) as T | undefined;\n }\n\n async execute(sql: string, params: any[] = []): Promise<ExecuteResult> {\n const stmt = this.getDb().prepare(sql);\n const result = stmt.run(params);\n return {\n changes: result.changes,\n lastInsertRowid: result.lastInsertRowid,\n };\n }\n\n async transaction<T>(callback: () => Promise<T>): Promise<T> {\n const db = this.getDb();\n const savepointName = `sp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n db.exec(`SAVEPOINT ${savepointName}`);\n try {\n const result = await callback();\n db.exec(`RELEASE ${savepointName}`);\n return result;\n } catch (error) {\n db.exec(`ROLLBACK TO ${savepointName}`);\n db.exec(`RELEASE ${savepointName}`);\n throw error;\n }\n }\n\n async exec(sql: string): Promise<void> {\n this.getDb().exec(sql);\n }\n\n async close(): Promise<void> {\n if (this.db) {\n this.db.close();\n this.db = null;\n logger.info(\"Database connection closed\");\n }\n }\n\n isConnected(): boolean {\n return this.db !== null && this.db.open;\n }\n}\n","import type { DatabaseAdapter } from \"./adapters/index.js\";\nimport { logger } from \"../index.js\";\nimport path from \"path\";\nimport fs from \"fs\";\n\nlet adapter: DatabaseAdapter | null = null;\n\n/**\n * Auto-detect and initialize the database adapter:\n * - DATABASE_URL env → PostgreSQL\n * - DB_PATH env → SQLite\n */\nexport async function initializeAdapter(): Promise<DatabaseAdapter> {\n if (adapter) return adapter;\n\n const databaseUrl = process.env.DATABASE_URL;\n const dbPath = process.env.DB_PATH;\n\n if (databaseUrl) {\n const { PostgresAdapter } = await import(\"./adapters/postgres-adapter.js\");\n adapter = new PostgresAdapter(databaseUrl);\n logger.info(\"Using PostgreSQL adapter\");\n } else if (dbPath) {\n // Ensure DB directory exists\n const dbDir = path.dirname(dbPath.trim());\n if (!fs.existsSync(dbDir)) {\n fs.mkdirSync(dbDir, { recursive: true });\n }\n\n const { SqliteAdapter } = await import(\"./adapters/sqlite-adapter.js\");\n adapter = new SqliteAdapter(dbPath);\n logger.info(\"Using SQLite adapter\");\n } else {\n throw new Error(\n \"No database configured. Set DATABASE_URL (PostgreSQL) or DB_PATH (SQLite).\",\n );\n }\n\n return adapter;\n}\n\n/**\n * Returns the active adapter, lazy-initializing if needed.\n */\nexport async function getAdapter(): Promise<DatabaseAdapter> {\n if (!adapter) {\n return initializeAdapter();\n }\n return adapter;\n}\n\n/**\n * Graceful shutdown — close the active adapter.\n */\nexport async function closeAdapter(): Promise<void> {\n if (adapter) {\n await adapter.close();\n adapter = null;\n }\n}\n","#!/usr/bin/env node\nimport { Command } from \"commander\";\n\n// Import config early to validate DB_PATH\nimport \"../config\";\n\nimport { logger } from \"../infra/logger\";\nlogger.level = \"error\";\n\nimport { runMigrations } from \"../infra/database/migrations\";\nawait runMigrations();\n\nimport { listCommand } from \"./listCommand\";\nimport { getCommand } from \"./getCommand\";\nimport { createCommand } from \"./createCommand\";\nimport { updateCommand } from \"./updateCommand\";\nimport { deleteCommand } from \"./deleteCommand\";\nimport { downloadCommand } from \"./downloadCommand\";\nimport { viewCommand } from \"./viewCommand\";\nimport { eventsCommand } from \"./eventsCommand\";\nimport { closeWorker, closeDatabase } from \"../infra\";\n\nexport const program = new Command()\n .name(\"ddctl\")\n .description(\"CLI tool to manage your ddocs\")\n .version(\"0.0.3\")\n .addHelpText(\"beforeAll\", \"\\n\")\n .addHelpText(\"afterAll\", \"\\n\");\n\nprogram.addCommand(listCommand);\nprogram.addCommand(getCommand);\nprogram.addCommand(createCommand);\nprogram.addCommand(updateCommand);\nprogram.addCommand(deleteCommand);\nprogram.addCommand(downloadCommand);\nprogram.addCommand(viewCommand);\nprogram.addCommand(eventsCommand);\n\n// Close connections and exit after command completes\nprogram\n .parseAsync()\n .then(async () => {\n try {\n await closeWorker();\n await closeDatabase();\n } catch (error) {\n // Ignore errors during cleanup\n }\n process.exit(0);\n })\n .catch((error) => {\n console.error(\"Error:\", error);\n process.exit(1);\n });\n","import { getAdapter } from \"../connection.js\";\nimport { logger } from \"../../index.js\";\n\nconst STABLE_SCHEMA = `\nCREATE TABLE IF NOT EXISTS files (\n _id TEXT PRIMARY KEY,\n ddocId TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n localVersion INTEGER NOT NULL DEFAULT 1,\n onchainVersion INTEGER NOT NULL DEFAULT 0,\n syncStatus TEXT NOT NULL DEFAULT 'pending',\n createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n isDeleted INTEGER NOT NULL DEFAULT 0,\n portalAddress TEXT NOT NULL,\n metadata TEXT DEFAULT '{}',\n onChainFileId INTEGER,\n commentKey TEXT,\n linkKey TEXT,\n linkKeyNonce TEXT,\n link TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_files_createdAt ON files(createdAt);\nCREATE INDEX IF NOT EXISTS idx_files_syncStatus ON files(syncStatus);\nCREATE INDEX IF NOT EXISTS idx_files_title ON files(title);\nCREATE INDEX IF NOT EXISTS idx_files_portalAddress ON files(portalAddress);\n\nCREATE TABLE IF NOT EXISTS portals (\n _id TEXT PRIMARY KEY,\n portalAddress TEXT NOT NULL UNIQUE,\n portalSeed TEXT NOT NULL UNIQUE,\n ownerAddress TEXT NOT NULL,\n createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE IF NOT EXISTS api_keys (\n _id TEXT PRIMARY KEY,\n apiKeySeed TEXT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n collaboratorAddress TEXT NOT NULL UNIQUE,\n portalAddress TEXT NOT NULL,\n createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n isDeleted INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS events (\n _id TEXT PRIMARY KEY,\n type TEXT NOT NULL CHECK (type IN ('create', 'update', 'delete')),\n timestamp INTEGER NOT NULL,\n fileId TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'processed', 'failed')),\n retryCount INTEGER NOT NULL DEFAULT 0,\n lastError TEXT,\n lockedAt INTEGER,\n nextRetryAt INTEGER,\n userOpHash TEXT,\n pendingPayload TEXT,\n portalAddress TEXT\n);\nCREATE INDEX IF NOT EXISTS idx_events_pending_eligible ON events (status, nextRetryAt, timestamp) WHERE status = 'pending';\nCREATE INDEX IF NOT EXISTS idx_events_file_pending_ts ON events (fileId, status, timestamp) WHERE status = 'pending';\nCREATE INDEX IF NOT EXISTS idx_events_processing_locked ON events (status, lockedAt) WHERE status = 'processing';\nCREATE INDEX IF NOT EXISTS idx_events_failed_portal ON events (portalAddress, status) WHERE status = 'failed';\n\nCREATE TABLE IF NOT EXISTS folders (\n _id TEXT PRIMARY KEY,\n onchainFileId INTEGER NOT NULL,\n folderId TEXT NOT NULL,\n folderRef TEXT NOT NULL,\n folderName TEXT NOT NULL,\n portalAddress TEXT NOT NULL,\n metadataIPFSHash TEXT NOT NULL,\n contentIPFSHash TEXT NOT NULL,\n isDeleted INTEGER NOT NULL DEFAULT 0,\n lastTransactionHash TEXT,\n lastTransactionBlockNumber INTEGER NOT NULL,\n lastTransactionBlockTimestamp INTEGER NOT NULL,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\nCREATE INDEX IF NOT EXISTS idx_folders_folderRef_folderId ON folders(folderRef, folderId);\nCREATE INDEX IF NOT EXISTS idx_folders_folderRef ON folders(folderRef);\nCREATE INDEX IF NOT EXISTS idx_folders_created_at ON folders(created_at);\n`;\n\nexport async function runMigrations(): Promise<void> {\n const adapter = await getAdapter();\n await adapter.exec(STABLE_SCHEMA);\n logger.debug(\"Database schema ready\");\n}\n","import { Command } from \"commander\";\nimport Table from \"cli-table3\";\nimport { listFiles } from \"../domain/file\";\nimport type { GetFileResult } from \"../types\";\nimport { formatDate, getElapsedTime, columnNames, columnWidth, validateApiKey } from \"./utils/util\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\n\nexport const listCommand = new Command()\n .name(\"list\")\n .description(\"List all ddocs\")\n .option(\"-l, --limit <number>\", \"Limit the number of results\", parseInt)\n .option(\"-s, --skip <number>\", \"Skip the first N results\", parseInt)\n .action(async (options) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const apiKeyInfo = await ApiKeysModel.findByApiKey(apiKey);\n const portalAddress = apiKeyInfo?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n const params = {\n limit: options.limit,\n skip: options.skip,\n portalAddress,\n };\n\n const result = await listFiles(params);\n if (result.ddocs.length === 0) {\n console.log(\"No ddocs found.\");\n return;\n }\n\n const table = new Table({\n head: [\n columnNames.index,\n columnNames.ddocId,\n columnNames.title,\n columnNames.status,\n columnNames.local,\n columnNames.onchain,\n columnNames.created,\n columnNames.lastModified,\n ],\n colWidths: [\n columnWidth[columnNames.index],\n columnWidth[columnNames.ddocId],\n columnWidth[columnNames.title],\n columnWidth[columnNames.status],\n columnWidth[columnNames.local],\n columnWidth[columnNames.onchain],\n columnWidth[columnNames.created],\n columnWidth[columnNames.lastModified],\n ],\n style: { head: [] },\n });\n\n result.ddocs.forEach((ddoc: GetFileResult, index: number) => {\n const ddocId = (ddoc as any).ddocId || \"N/A\";\n table.push([\n index + 1,\n ddocId,\n ddoc.title.length > 23 ? ddoc.title.substring(0, 20) + \"...\" : ddoc.title,\n ddoc.syncStatus,\n ddoc.localVersion,\n ddoc.onchainVersion,\n formatDate(ddoc.createdAt),\n getElapsedTime(ddoc.updatedAt),\n ]);\n });\n\n console.log(`\\nFound ${result.total} ddoc(s):\\n`);\n console.log(table.toString());\n if (result.hasNext) {\n console.log(\"\\n(More results available. Use --skip and --limit for pagination)\");\n }\n } catch (error: any) {\n console.error(\"Error listing ddocs:\", error.message);\n throw error;\n }\n });\n","import { generate } from \"short-uuid\";\n\nimport { EventsModel, FilesModel } from \"../../infra/database/models\";\nimport type {\n File,\n ListFilesParams,\n ListFilesResult,\n CreateFileInput,\n UpdateFileInput,\n UpdateFilePayload,\n GetFileResult,\n} from \"../../types\";\nimport { DEFAULT_LIST_LIMIT } from \"./constants\";\n\nasync function listFiles(params: ListFilesParams): Promise<ListFilesResult> {\n const { limit, skip, portalAddress } = params;\n const effectiveLimit = limit || DEFAULT_LIST_LIMIT;\n\n const result = await FilesModel.findAll(portalAddress, effectiveLimit, skip);\n\n const processedFiles = result.files.map((file) => ({\n ddocId: file.ddocId,\n link: file.link,\n title: file.title,\n content: file.content,\n localVersion: file.localVersion,\n onchainVersion: file.onchainVersion,\n syncStatus: file.syncStatus,\n isDeleted: file.isDeleted,\n onChainFileId: file.onChainFileId,\n portalAddress: file.portalAddress,\n createdAt: file.createdAt,\n updatedAt: file.updatedAt,\n }));\n\n return {\n ddocs: processedFiles,\n total: result.total,\n hasNext: result.hasNext,\n };\n}\n\nasync function getFile(ddocId: string, portalAddress: string): Promise<GetFileResult | null> {\n if (!ddocId) {\n throw new Error(\"ddocId is required\");\n }\n\n const file = await FilesModel.findByDDocId(ddocId, portalAddress);\n\n if (!file) {\n return null;\n }\n\n return {\n ddocId: file.ddocId,\n link: file.link || \"\",\n title: file.title,\n content: file.content,\n localVersion: file.localVersion,\n onchainVersion: file.onchainVersion,\n syncStatus: file.syncStatus,\n isDeleted: file.isDeleted,\n onChainFileId: file.onChainFileId,\n portalAddress: file.portalAddress,\n createdAt: file.createdAt,\n updatedAt: file.updatedAt,\n };\n}\n\nconst createFile = async (input: CreateFileInput): Promise<File> => {\n if (!input.title || !input.content || !input.portalAddress) {\n throw new Error(\"title, content, and portalAddress are required\");\n }\n\n const ddocId = generate();\n const file = await FilesModel.create({\n title: input.title,\n content: input.content,\n ddocId: ddocId,\n portalAddress: input.portalAddress,\n });\n\n await EventsModel.create({ type: \"create\", fileId: file._id, portalAddress: file.portalAddress });\n return file;\n};\n\nconst updateFile = async (ddocId: string, payload: UpdateFileInput, portalAddress: string): Promise<Partial<File>> => {\n if (!ddocId) {\n throw new Error(\"ddocId is required\");\n }\n\n if (!payload.title && !payload.content) {\n throw new Error(\"At least one field is required: Either provide title, content, or both\");\n }\n\n const existingFile = await FilesModel.findByDDocId(ddocId, portalAddress);\n if (!existingFile) {\n throw new Error(`File with ddocId ${ddocId} not found`);\n }\n\n const updatePayload: UpdateFilePayload = {\n ...payload,\n localVersion: existingFile.localVersion + 1,\n syncStatus: \"pending\", // since the update is done in local db, it's not on the chain yet. hence pending\n };\n const updatedFile = await FilesModel.update(existingFile._id, updatePayload, portalAddress);\n\n await EventsModel.create({ type: \"update\", fileId: updatedFile._id, portalAddress: updatedFile.portalAddress });\n return {\n ddocId: updatedFile.ddocId,\n link: updatedFile.link,\n title: updatedFile.title,\n content: updatedFile.content,\n localVersion: updatedFile.localVersion,\n onchainVersion: updatedFile.onchainVersion,\n syncStatus: updatedFile.syncStatus,\n isDeleted: updatedFile.isDeleted,\n onChainFileId: updatedFile.onChainFileId,\n portalAddress: updatedFile.portalAddress,\n createdAt: updatedFile.createdAt,\n updatedAt: updatedFile.updatedAt,\n };\n};\n\nconst deleteFile = async (ddocId: string, portalAddress: string): Promise<File> => {\n if (!ddocId) {\n throw new Error(\"ddocId is required\");\n }\n\n const existingFile = await FilesModel.findByDDocId(ddocId, portalAddress);\n if (!existingFile) {\n throw new Error(`File with ddocId ${ddocId} not found`);\n }\n\n const deletedFile = await FilesModel.softDelete(existingFile._id);\n\n await EventsModel.create({ type: \"delete\", fileId: deletedFile._id, portalAddress: deletedFile.portalAddress });\n return deletedFile;\n};\n\nexport { listFiles, getFile, createFile, updateFile, deleteFile };\nexport type { CreateFileInput, UpdateFileInput, ListFilesParams, ListFilesResult, GetFileResult } from \"../../types\";\n","export const columnNames = {\n index: \"#\",\n ddocId: \"DDoc ID\",\n title: \"Title\",\n status: \"Status\",\n local: \"Local version\",\n onchain: \"On-chain version\",\n deleted: \"Deleted\",\n created: \"Created\",\n lastModified: \"Last modified\",\n} as const;\n\nexport const columnWidth: Record<string, number> = {\n [columnNames.index]: 3,\n [columnNames.ddocId]: 25,\n [columnNames.title]: 25,\n [columnNames.status]: 10,\n [columnNames.local]: 16,\n [columnNames.onchain]: 18,\n [columnNames.deleted]: 10,\n [columnNames.created]: 12,\n [columnNames.lastModified]: 20,\n};\n\nexport function formatDate(date: Date | string): string {\n const d = typeof date === \"string\" ? new Date(date) : date;\n const day = String(d.getDate()).padStart(2, \"0\");\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const year = d.getFullYear();\n return `${day}-${month}-${year}`;\n}\n\nexport function getElapsedTime(date: Date | string): string {\n const now = new Date();\n const past = typeof date === \"string\" ? new Date(date) : date;\n const diffMs = now.getTime() - past.getTime();\n\n if (diffMs < 0) {\n return \"just now\";\n }\n\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffWeeks = Math.floor(diffDays / 7);\n const diffMonths = Math.floor(diffDays / 30);\n const diffYears = Math.floor(diffDays / 365);\n\n const units = [\n { value: diffSeconds, max: 60, name: \"second\" },\n { value: diffMinutes, max: 60, name: \"minute\" },\n { value: diffHours, max: 24, name: \"hour\" },\n { value: diffDays, max: 7, name: \"day\" },\n { value: diffWeeks, max: 4, name: \"week\" },\n { value: diffMonths, max: 12, name: \"month\" },\n { value: diffYears, max: Infinity, name: \"year\" },\n ];\n\n const unit = units.find((u) => u.value < u.max);\n if (unit) {\n const label = unit.value === 1 ? unit.name : `${unit.name}s`;\n return `${unit.value} ${label} ago`;\n }\n\n return \"just now\";\n}\n\nexport function validateApiKey(apiKey: string | undefined): asserts apiKey is string {\n if (!apiKey) {\n const API_KEY_SETUP_MESSAGE = `\nAPI key is not configured.\n\nTo set up your API key, run:\n fileverse-api --apiKey <your-api-key> --rpcUrl <rpc-url>\n\nThis will configure your Fileverse API instance and save your credentials.\nAfter setup, you can use ddctl commands.\n`;\n console.error(API_KEY_SETUP_MESSAGE);\n process.exit(1);\n }\n}\n","import { Command } from \"commander\";\nimport Table from \"cli-table3\";\nimport { getFile } from \"../domain/file\";\nimport { formatDate, getElapsedTime, columnNames, columnWidth, validateApiKey } from \"./utils/util\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\n\nexport const getCommand = new Command()\n .name(\"get\")\n .description(\"Get a ddoc by its ID\")\n .argument(\"<ddocId>\", \"The ddoc ID to retrieve\")\n .action(async (ddocId: string) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const apiKeyInfo = await ApiKeysModel.findByApiKey(apiKey);\n const portalAddress = apiKeyInfo?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n const file = await getFile(ddocId, portalAddress);\n if (!file) {\n console.error(`Ddoc with ID \"${ddocId}\" not found.`);\n return;\n }\n\n const table = new Table({\n head: [\n columnNames.ddocId,\n columnNames.title,\n columnNames.status,\n columnNames.local,\n columnNames.onchain,\n columnNames.deleted,\n columnNames.created,\n columnNames.lastModified,\n ],\n colWidths: [\n columnWidth[columnNames.ddocId],\n columnWidth[columnNames.title],\n columnWidth[columnNames.status],\n columnWidth[columnNames.local],\n columnWidth[columnNames.onchain],\n columnWidth[columnNames.deleted],\n columnWidth[columnNames.created],\n columnWidth[columnNames.lastModified],\n ],\n style: { head: [] },\n });\n\n const fileDdocId = (file as any).ddocId || \"N/A\";\n table.push([\n fileDdocId,\n file.title.length > 23 ? file.title.substring(0, 20) + \"...\" : file.title,\n file.syncStatus,\n file.localVersion,\n file.onchainVersion,\n file.isDeleted ? \"True\" : \"False\",\n formatDate(file.createdAt),\n getElapsedTime(file.updatedAt),\n ]);\n\n console.log(\"\\nDdoc details:\\n\");\n console.log(table.toString());\n if (file.link) {\n console.log(`\\nLink: ${file.link}\\n`);\n }\n } catch (error: any) {\n console.error(\"Error getting ddoc:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport { createFile } from \"../domain/file\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport Table from \"cli-table3\";\nimport { formatDate, getElapsedTime, columnNames, columnWidth, validateApiKey } from \"./utils/util\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\n\nexport const createCommand = new Command()\n .name(\"create\")\n .description(\"Create a new ddoc from a file\")\n .argument(\"<filepath>\", \"Path to the file to create ddoc from\")\n .action(async (filepath: string) => {\n try {\n if (!fs.existsSync(filepath)) {\n throw new Error(`File not found: ${filepath}`);\n }\n\n const runtimeConfig = getRuntimeConfig();\n\n const apiKey = runtimeConfig.API_KEY;\n\n validateApiKey(apiKey);\n\n const apiKeyInfo = await ApiKeysModel.findByApiKey(apiKey);\n const portalAddress = apiKeyInfo?.portalAddress as string;\n\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n const content = fs.readFileSync(filepath, \"utf-8\");\n if (!content || content.trim().length === 0) {\n console.error(\"Error creating ddoc: File content cannot be empty. Add some content to the file and try again.\");\n process.exit(1);\n }\n\n const basename = path.basename(filepath);\n const lastDotIndex = basename.lastIndexOf(\".\");\n const title = lastDotIndex > 0 ? basename.substring(0, lastDotIndex) : basename;\n const file = await createFile({ title, content, portalAddress });\n\n console.log(\"\\nDdoc created successfully!\\n\");\n const table = new Table({\n head: [\n columnNames.ddocId,\n columnNames.title,\n columnNames.status,\n columnNames.local,\n columnNames.onchain,\n columnNames.created,\n columnNames.lastModified,\n ],\n colWidths: [\n columnWidth[columnNames.ddocId],\n columnWidth[columnNames.title],\n columnWidth[columnNames.status],\n columnWidth[columnNames.local],\n columnWidth[columnNames.onchain],\n columnWidth[columnNames.created],\n columnWidth[columnNames.lastModified],\n ],\n style: { head: [] },\n });\n\n const ddocId = (file as any).ddocId || \"N/A\";\n table.push([\n ddocId,\n file.title.length > 23 ? file.title.substring(0, 20) + \"...\" : file.title,\n file.syncStatus,\n file.localVersion,\n file.onchainVersion,\n formatDate(file.createdAt),\n getElapsedTime(file.updatedAt),\n ]);\n\n console.log(table.toString());\n } catch (error: any) {\n console.error(\"Error creating ddoc:\", error.message);\n process.exit(1);\n }\n });\n","import * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\n\nimport { Command } from \"commander\";\nimport { updateFile, getFile, type UpdateFileInput } from \"../domain/file\";\nimport { spawnSync } from \"child_process\";\nimport Table from \"cli-table3\";\nimport { formatDate, getElapsedTime, columnNames, columnWidth, validateApiKey } from \"./utils/util\";\nimport { ApiKeysModel } from \"../infra/database/models\";\nimport { getRuntimeConfig } from \"../config\";\n\nfunction showTable(updatedFile: any) {\n const table = new Table({\n head: [\n columnNames.ddocId,\n columnNames.title,\n columnNames.status,\n columnNames.local,\n columnNames.onchain,\n columnNames.created,\n columnNames.lastModified,\n ],\n colWidths: [\n columnWidth[columnNames.ddocId],\n columnWidth[columnNames.title],\n columnWidth[columnNames.status],\n columnWidth[columnNames.local],\n columnWidth[columnNames.onchain],\n columnWidth[columnNames.created],\n columnWidth[columnNames.lastModified],\n ],\n style: { head: [] },\n });\n\n const fileDdocId = (updatedFile as any).ddocId || \"N/A\";\n table.push([\n fileDdocId,\n updatedFile.title.length > 23 ? updatedFile.title.substring(0, 20) + \"...\" : updatedFile.title,\n updatedFile.syncStatus,\n updatedFile.localVersion,\n updatedFile.onchainVersion,\n formatDate(updatedFile.createdAt),\n getElapsedTime(updatedFile.updatedAt),\n ]);\n\n console.log(table.toString());\n}\n\nexport const updateCommand = new Command()\n .name(\"update\")\n .description(\"Update an existing ddoc. Use -f to update from a file, or omit -f to edit in vi editor\")\n .argument(\"<ddocId>\", \"The ddoc ID to update\")\n .option(\"-f, --file <file_path>\", \"path to file to update ddoc from (if omitted, opens vi editor)\")\n .action(async (ddocId: string, options: { file?: string }) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const apiKeyInfo = await ApiKeysModel.findByApiKey(apiKey);\n const portalAddress = apiKeyInfo?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n const file = await getFile(ddocId, portalAddress);\n if (!file) {\n throw new Error(`ddoc with ${ddocId} not found.`);\n }\n\n const filePath = options?.file ?? \"\";\n if (filePath) {\n const content = fs.readFileSync(filePath, \"utf-8\");\n if (!content || content.trim().length === 0) {\n throw new Error(`file content cannot be empty`);\n }\n\n const basename = path.basename(filePath);\n const lastDotIndex = basename.lastIndexOf(\".\");\n const title = lastDotIndex > 0 ? basename.substring(0, lastDotIndex) : basename;\n const payload: UpdateFileInput = {\n title,\n content,\n };\n const updatedFile = await updateFile(ddocId, payload, portalAddress);\n console.log(\"\\n✓ Ddoc updated successfully!\\n\");\n showTable(updatedFile);\n return;\n }\n\n // vi-editor flow\n const tmpFilePath = path.join(os.tmpdir(), `tmp-${ddocId}-${Date.now()}.txt`);\n fs.writeFileSync(tmpFilePath, file.content);\n\n const editor = process.env.EDITOR || \"vi\";\n const result = spawnSync(editor, [tmpFilePath], { stdio: \"inherit\" });\n if (result.status === 0) {\n const newContent = fs.readFileSync(tmpFilePath, \"utf-8\");\n if (newContent === file.content) {\n console.log(`No changes made. Update cancelled.`);\n fs.unlinkSync(tmpFilePath);\n return;\n }\n\n const payload: UpdateFileInput = {\n title: file.title, // keeping same title as current\n content: newContent,\n };\n const updatedFile = await updateFile(ddocId, payload, portalAddress);\n console.log(\"\\n✓ Ddoc updated successfully!\\n\");\n showTable(updatedFile);\n }\n\n fs.unlinkSync(tmpFilePath);\n } catch (error: any) {\n console.error(\"Error updating ddoc:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport { deleteFile } from \"../domain/file\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\nimport { validateApiKey } from \"./utils/util\";\n\nexport const deleteCommand = new Command()\n .name(\"delete\")\n .description(\"Delete one or more ddocs by their IDs\")\n .argument(\"<ddocIds...>\", \"One or more ddoc IDs to delete (space-separated)\")\n .action(async (ddocIds: string[]) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n\n const apiKey = runtimeConfig.API_KEY;\n\n validateApiKey(apiKey);\n\n const apiKeyInfo = await ApiKeysModel.findByApiKey(apiKey);\n const portalAddress = apiKeyInfo?.portalAddress as string;\n\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n for (const ddocId of ddocIds) {\n try {\n await deleteFile(ddocId, portalAddress);\n console.log(`ddoc ${ddocId} deleted successfully`);\n } catch (error: any) {\n console.error(`Error deleting ddoc ${ddocId}:`, error.message);\n // Continue with next ddoc instead of stopping\n }\n }\n } catch (error: any) {\n console.error(\"Error:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport * as fs from \"fs\";\nimport { getFile } from \"../domain/file\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\nimport { validateApiKey } from \"./utils/util\";\n\nexport const downloadCommand = new Command()\n .name(\"download\")\n .description(\"Download a ddoc to a local file\")\n .argument(\"<ddocId>\", \"The ddoc ID to download\")\n .option(\"-o, --output <filename>\", \"Output filename (only supports markdown)\")\n .action(async (ddocId: string, options: { output?: string }) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const apiKeyInfo = await ApiKeysModel.findByApiKey(apiKey);\n const portalAddress = apiKeyInfo?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n const file = await getFile(ddocId, portalAddress);\n if (!file) {\n console.error(`Ddoc with ID \"${ddocId}\" not found.`);\n return;\n }\n\n let outputFilename: string = file.title;\n if (options.output) {\n outputFilename = options.output.endsWith(\".md\") ? options.output : `${options.output}.md`;\n }\n\n fs.writeFileSync(outputFilename, file.content, \"utf-8\");\n\n console.log(`\\n✓ Ddoc downloaded successfully to: ${outputFilename}\\n`);\n } catch (error: any) {\n console.error(\"Error downloading ddoc:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport { getFile } from \"../domain/file\";\nimport { getRuntimeConfig } from \"../config\";\nimport { ApiKeysModel } from \"../infra/database/models\";\nimport { validateApiKey } from \"./utils/util\";\n\nexport const viewCommand = new Command()\n .name(\"view\")\n .description(\"View content preview of a ddoc\")\n .argument(\"<ddocId>\", \"The ddoc ID to view\")\n .option(\"-n, --lines <number>\", \"Number of lines to preview (default: 10)\", \"10\")\n .action(async (ddocId: string, options: { lines?: string }) => {\n try {\n const runtimeConfig = getRuntimeConfig();\n const apiKey = runtimeConfig.API_KEY;\n validateApiKey(apiKey);\n const apiKeyInfo = await ApiKeysModel.findByApiKey(apiKey);\n const portalAddress = apiKeyInfo?.portalAddress as string;\n if (!portalAddress) throw new Error(\"Portal address is required\");\n\n const file = await getFile(ddocId, portalAddress);\n if (!file) {\n console.error(`Ddoc with ID \"${ddocId}\" not found.`);\n return;\n }\n\n const content = file.content || \"\";\n const contentLines = content.split(\"\\n\");\n const totalLines = contentLines.length;\n const previewLines = Math.max(1, parseInt(options.lines || \"10\", 10));\n const linesToShow = Math.min(previewLines, totalLines);\n\n if (content.trim().length === 0) {\n console.log(\"\\nContent preview:\\n\");\n console.log(\"=\".repeat(80));\n console.log(\"(empty)\");\n console.log(\"=\".repeat(80));\n } else {\n const preview = contentLines.slice(0, linesToShow).join(\"\\n\");\n\n console.log(\"\\nContent preview:\\n\");\n console.log(\"=\".repeat(80));\n console.log(preview);\n if (totalLines > linesToShow) {\n console.log(`\\n... (${totalLines - linesToShow} more line${totalLines - linesToShow === 1 ? \"\" : \"s\"})`);\n }\n console.log(\"=\".repeat(80));\n console.log(`\\nShowing ${linesToShow} of ${totalLines} line${totalLines === 1 ? \"\" : \"s\"}\\n`);\n }\n } catch (error: any) {\n console.error(\"Error viewing ddoc:\", error.message);\n throw error;\n }\n });\n","import { Command } from \"commander\";\nimport Table from \"cli-table3\";\nimport { EventsModel } from \"../infra/database/models\";\nimport { formatDate } from \"./utils/util\";\n\nconst MAX_ERROR_LEN = 60;\n\nexport const eventsCommand = new Command().name(\"events\").description(\"Worker event operations (list failed, retry)\");\n\neventsCommand\n .command(\"list-failed\")\n .description(\"List all failed events\")\n .action(async () => {\n try {\n const events = await EventsModel.listFailed();\n if (events.length === 0) {\n console.log(\"No failed events.\");\n return;\n }\n const table = new Table({\n head: [\"ID\", \"File ID\", \"Portal\", \"Type\", \"Timestamp\", \"Last Error\"],\n colWidths: [28, 12, 14, 10, 12, MAX_ERROR_LEN],\n style: { head: [] },\n });\n events.forEach((e) => {\n const err = e.lastError ?? \"\";\n table.push([\n e._id,\n e.fileId,\n e.portalAddress || \"\\u2014\",\n e.type,\n formatDate(new Date(e.timestamp)),\n err.length > MAX_ERROR_LEN ? err.slice(0, MAX_ERROR_LEN - 3) + \"...\" : err,\n ]);\n });\n console.log(`\\nFailed events (${events.length}):\\n`);\n console.log(table.toString());\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\"Error listing failed events:\", msg);\n throw error;\n }\n });\n\neventsCommand\n .command(\"retry <eventId>\")\n .description(\"Retry a single failed event by ID\")\n .action(async (eventId: string) => {\n try {\n const updated = await EventsModel.resetFailedToPending(eventId);\n if (updated) {\n console.log(`Event ${eventId} reset to pending. Worker will pick it up.`);\n } else {\n console.error(`Event not found or not in failed state: ${eventId}`);\n process.exitCode = 1;\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\"Error retrying event:\", msg);\n throw error;\n }\n });\n\neventsCommand\n .command(\"retry-all\")\n .description(\"Retry all failed events\")\n .action(async () => {\n try {\n const count = await EventsModel.resetAllFailedToPending();\n console.log(`Reset ${count} failed event(s) to pending. Worker will pick them up.`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\"Error retrying failed events:\", msg);\n throw error;\n }\n });\n"],"mappings":"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","names":["path","init_constants","init_constants","adapter","uuidv7","uuidv7","uuidv7","init_constants","init_constants","toHex","generateRandomBytes","init_constants","fromUint8Array","toUint8Array","init_constants","fromUint8Array","toUint8Array","ucans","init_constants","init_worker","init_worker","path","fs","PostgresAdapter","SqliteAdapter","Command","adapter","init_constants","Command","Table","Command","Table","Command","fs","path","Table","Command","basename","Table","fs","path","os","Command","Table","Table","Command","basename","Command","Command","Command","fs","Command","Command","Command","Command","Table","Command","Table","Command"]}
|