@fileverse/api 0.0.9 → 0.0.10

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.
@@ -735,6 +735,29 @@ var init_events_model = __esm({
735
735
  `;
736
736
  await QueryBuilder.execute(sql, [Date.now(), _id]);
737
737
  }
738
+ static async markSubmitted(_id) {
739
+ const sql = `
740
+ UPDATE ${this.TABLE}
741
+ SET status = 'submitted',
742
+ lockedAt = NULL
743
+ WHERE _id = ?
744
+ `;
745
+ await QueryBuilder.execute(sql, [_id]);
746
+ }
747
+ static async findNextSubmitted(lockedFileIds) {
748
+ const exclusionClause = lockedFileIds.length > 0 ? `AND fileId NOT IN (${lockedFileIds.map(() => "?").join(", ")})` : "";
749
+ const sql = `
750
+ SELECT * FROM ${this.TABLE}
751
+ WHERE status = 'submitted'
752
+ AND userOpHash IS NOT NULL
753
+ ${exclusionClause}
754
+ ORDER BY timestamp ASC
755
+ LIMIT 1
756
+ `;
757
+ const params = [...lockedFileIds];
758
+ const row = await QueryBuilder.selectOne(sql, params);
759
+ return row ? this.parseEvent(row) : void 0;
760
+ }
738
761
  static async markProcessed(_id) {
739
762
  const sql = `
740
763
  UPDATE ${this.TABLE}
@@ -1275,7 +1298,7 @@ import { createSmartAccountClient } from "permissionless";
1275
1298
  import { toSafeSmartAccount } from "permissionless/accounts";
1276
1299
  import { entryPoint07Address } from "viem/account-abstraction";
1277
1300
  import { generatePrivateKey } from "viem/accounts";
1278
- var getPublicClient, getPimlicoClient, signerToSmartAccount, getSmartAccountClient, getNonce, waitForUserOpReceipt;
1301
+ var getPublicClient, getPimlicoClient, signerToSmartAccount, getSmartAccountClient, getNonce, getUserOpReceipt, waitForUserOpReceipt;
1279
1302
  var init_pimlico_utils = __esm({
1280
1303
  "src/sdk/pimlico-utils.ts"() {
1281
1304
  "use strict";
@@ -1339,6 +1362,14 @@ var init_pimlico_utils = __esm({
1339
1362
  size: 32
1340
1363
  })
1341
1364
  );
1365
+ getUserOpReceipt = async (hash2, authToken, portalAddress, invokerAddress) => {
1366
+ const pimlicoClient = getPimlicoClient(authToken, portalAddress, invokerAddress);
1367
+ try {
1368
+ return await pimlicoClient.getUserOperationReceipt({ hash: hash2 });
1369
+ } catch {
1370
+ return null;
1371
+ }
1372
+ };
1342
1373
  waitForUserOpReceipt = async (hash2, authToken, portalAddress, invokerAddress, timeout = 12e4) => {
1343
1374
  const pimlicoClient = getPimlicoClient(authToken, portalAddress, invokerAddress);
1344
1375
  return pimlicoClient.waitForUserOperationReceipt({
@@ -2492,6 +2523,56 @@ var init_file_manager = __esm({
2492
2523
  metadata
2493
2524
  };
2494
2525
  }
2526
+ async submitUpdateFile(file2) {
2527
+ logger.debug(`Submitting update for file ${file2.ddocId} with onChainFileId ${file2.onChainFileId}`);
2528
+ const { encryptedSecretKey, nonce, secretKey } = await generateLinkKeyMaterial({
2529
+ ddocId: file2.ddocId,
2530
+ linkKey: file2.linkKey,
2531
+ linkKeyNonce: file2.linkKeyNonce
2532
+ });
2533
+ const yjsContent = markdownToYjs(file2.content);
2534
+ const { encryptedFile, key } = await createEncryptedContentFile(yjsContent);
2535
+ const commentKey = toUint8Array2(file2.commentKey);
2536
+ const { appLock, ownerLock } = this.createLocks(key, encryptedSecretKey, commentKey);
2537
+ const linkLock = buildLinklock(secretKey, toUint8Array2(key), commentKey);
2538
+ const encryptedTitle = await encryptTitleWithFileKey({
2539
+ title: file2.title || "Untitled",
2540
+ key
2541
+ });
2542
+ const metadata = buildFileMetadata({
2543
+ encryptedTitle,
2544
+ encryptedFileSize: encryptedFile.size,
2545
+ appLock,
2546
+ ownerLock,
2547
+ ddocId: file2.ddocId,
2548
+ nonce: fromUint8Array2(nonce),
2549
+ owner: this.agentClient.getAgentAddress()
2550
+ });
2551
+ const authParams = await this.getAuthParams();
2552
+ const { metadataHash, contentHash, gateHash } = await uploadAllFilesToIPFS(
2553
+ { metadata, encryptedFile, linkLock, ddocId: file2.ddocId },
2554
+ authParams
2555
+ );
2556
+ const callData = prepareCallData({
2557
+ metadataHash,
2558
+ contentHash,
2559
+ gateHash,
2560
+ appFileId: file2.ddocId,
2561
+ fileId: file2.onChainFileId
2562
+ });
2563
+ const userOpHash = await this.sendFileOperation(callData);
2564
+ logger.debug(`Submitted update user op for file ${file2.ddocId}`);
2565
+ return { userOpHash, metadata };
2566
+ }
2567
+ async submitDeleteFile(file2) {
2568
+ logger.debug(`Submitting delete for file ${file2.ddocId} with onChainFileId ${file2.onChainFileId}`);
2569
+ const callData = prepareDeleteFileCallData({
2570
+ onChainFileId: file2.onChainFileId
2571
+ });
2572
+ const userOpHash = await this.sendFileOperation(callData);
2573
+ logger.debug(`Submitted delete user op for file ${file2.ddocId}`);
2574
+ return { userOpHash };
2575
+ }
2495
2576
  async updateFile(file2) {
2496
2577
  logger.debug(`Updating file ${file2.ddocId} with onChainFileId ${file2.onChainFileId}`);
2497
2578
  const { encryptedSecretKey, nonce, secretKey } = await generateLinkKeyMaterial({
@@ -2583,7 +2664,7 @@ function deriveCollaboratorKeys(apiKeySeed) {
2583
2664
  const { secretKey: ucanSecret } = generateKeyPairFromSeed(ucanDerivedSecret);
2584
2665
  return { privateAccountKey, ucanSecret };
2585
2666
  }
2586
- var createFileManager, executeOperation, handleExistingFileOp, handleNewFileOp, getProxyAuthParams;
2667
+ var createFileManager, executeOperation, handleExistingFileOp, handleNewFileOp, getProxyAuthParams, submitUpdateFileOp, submitDeleteFileOp, resolveFileOp;
2587
2668
  var init_publish = __esm({
2588
2669
  "src/domain/portal/publish.ts"() {
2589
2670
  "use strict";
@@ -2595,6 +2676,7 @@ var init_publish = __esm({
2595
2676
  init_smart_agent();
2596
2677
  init_file_manager();
2597
2678
  init_config();
2679
+ init_pimlico_utils();
2598
2680
  createFileManager = async (portalSeed, portalAddress, ucanSecret, privateAccountKey) => {
2599
2681
  const keyPair = ucans2.EdKeypair.fromSecretKey(fromUint8Array3(ucanSecret), {
2600
2682
  exportable: true
@@ -2657,6 +2739,50 @@ var init_publish = __esm({
2657
2739
  );
2658
2740
  return fileManager.getProxyAuthParams();
2659
2741
  };
2742
+ submitUpdateFileOp = async (fileId) => {
2743
+ const { file: file2, portalDetails, apiKey } = await getPortalData(fileId);
2744
+ const apiKeySeed = toUint8Array3(apiKey);
2745
+ const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
2746
+ const fileManager = await createFileManager(
2747
+ portalDetails.portalSeed,
2748
+ portalDetails.portalAddress,
2749
+ ucanSecret,
2750
+ privateAccountKey
2751
+ );
2752
+ return fileManager.submitUpdateFile(file2);
2753
+ };
2754
+ submitDeleteFileOp = async (fileId) => {
2755
+ const { file: file2, portalDetails, apiKey } = await getPortalData(fileId);
2756
+ const apiKeySeed = toUint8Array3(apiKey);
2757
+ const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
2758
+ const fileManager = await createFileManager(
2759
+ portalDetails.portalSeed,
2760
+ portalDetails.portalAddress,
2761
+ ucanSecret,
2762
+ privateAccountKey
2763
+ );
2764
+ return fileManager.submitDeleteFile(file2);
2765
+ };
2766
+ resolveFileOp = async (fileId, userOpHash, eventType) => {
2767
+ const { portalDetails, apiKey } = await getPortalData(fileId);
2768
+ const apiKeySeed = toUint8Array3(apiKey);
2769
+ const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
2770
+ const fileManager = await createFileManager(
2771
+ portalDetails.portalSeed,
2772
+ portalDetails.portalAddress,
2773
+ ucanSecret,
2774
+ privateAccountKey
2775
+ );
2776
+ const { authToken, portalAddress, invokerAddress } = await fileManager.getProxyAuthParams();
2777
+ const receipt = await getUserOpReceipt(
2778
+ userOpHash,
2779
+ authToken,
2780
+ portalAddress,
2781
+ invokerAddress
2782
+ );
2783
+ if (!receipt) return null;
2784
+ return { receipt };
2785
+ };
2660
2786
  }
2661
2787
  });
2662
2788
 
@@ -2754,7 +2880,7 @@ var init_rate_limit = __esm({
2754
2880
  });
2755
2881
 
2756
2882
  // src/infra/worker/eventProcessor.ts
2757
- var processEvent, onTransactionSuccess, processCreateEvent, processUpdateEvent, processDeleteEvent;
2883
+ var processEvent, submitEvent, resolveEvent, onTransactionSuccess, processCreateEvent, processUpdateEvent, processDeleteEvent;
2758
2884
  var init_eventProcessor = __esm({
2759
2885
  "src/infra/worker/eventProcessor.ts"() {
2760
2886
  "use strict";
@@ -2792,6 +2918,132 @@ var init_eventProcessor = __esm({
2792
2918
  return { success: false, error: errorMsg };
2793
2919
  }
2794
2920
  };
2921
+ submitEvent = async (event) => {
2922
+ const { fileId, type } = event;
2923
+ switch (type) {
2924
+ case "create": {
2925
+ const file2 = await FilesModel.findByIdIncludingDeleted(fileId);
2926
+ if (!file2) throw new Error(`File ${fileId} not found`);
2927
+ if (file2.isDeleted === 1) {
2928
+ logger.info(`File ${fileId} is deleted, skipping create submit`);
2929
+ return;
2930
+ }
2931
+ const result = await handleNewFileOp(fileId);
2932
+ await EventsModel.setEventPendingOp(event._id, result.userOpHash, {
2933
+ linkKey: result.linkKey,
2934
+ linkKeyNonce: result.linkKeyNonce,
2935
+ commentKey: result.commentKey,
2936
+ metadata: result.metadata
2937
+ });
2938
+ logger.info(`File ${file2.ddocId} create op submitted (hash: ${result.userOpHash})`);
2939
+ break;
2940
+ }
2941
+ case "update": {
2942
+ const file2 = await FilesModel.findByIdExcludingDeleted(fileId);
2943
+ if (!file2) return;
2944
+ if (file2.localVersion <= file2.onchainVersion) return;
2945
+ const result = await submitUpdateFileOp(fileId);
2946
+ await EventsModel.setEventPendingOp(event._id, result.userOpHash, {
2947
+ metadata: result.metadata,
2948
+ localVersion: file2.localVersion
2949
+ });
2950
+ logger.info(`File ${file2.ddocId} update op submitted (hash: ${result.userOpHash})`);
2951
+ break;
2952
+ }
2953
+ case "delete": {
2954
+ const file2 = await FilesModel.findByIdIncludingDeleted(fileId);
2955
+ if (!file2) return;
2956
+ if (file2.isDeleted === 1 && file2.syncStatus === "synced") {
2957
+ logger.info(`File ${fileId} deletion already synced, skipping`);
2958
+ return;
2959
+ }
2960
+ if (file2.onChainFileId === null || file2.onChainFileId === void 0) {
2961
+ await FilesModel.update(fileId, { syncStatus: "synced", isDeleted: 1 }, file2.portalAddress);
2962
+ return;
2963
+ }
2964
+ const result = await submitDeleteFileOp(fileId);
2965
+ await EventsModel.setEventPendingOp(event._id, result.userOpHash, {});
2966
+ logger.info(`File ${file2.ddocId} delete op submitted (hash: ${result.userOpHash})`);
2967
+ break;
2968
+ }
2969
+ default:
2970
+ throw new Error(`Unknown event type: ${type}`);
2971
+ }
2972
+ };
2973
+ resolveEvent = async (event) => {
2974
+ const { fileId, type, userOpHash } = event;
2975
+ if (!userOpHash) {
2976
+ logger.warn(`Event ${event._id} has no userOpHash, cannot resolve`);
2977
+ return { resolved: false };
2978
+ }
2979
+ const result = await resolveFileOp(fileId, userOpHash, type);
2980
+ if (!result) {
2981
+ return { resolved: false };
2982
+ }
2983
+ const { receipt } = result;
2984
+ if (!receipt.success) {
2985
+ await EventsModel.clearEventPendingOp(event._id);
2986
+ throw new Error(`User operation failed: ${receipt.reason}`);
2987
+ }
2988
+ switch (type) {
2989
+ case "create": {
2990
+ const file2 = await FilesModel.findByIdIncludingDeleted(fileId);
2991
+ if (!file2) throw new Error(`File ${fileId} not found during resolve`);
2992
+ const onChainFileId = parseFileEventLog(receipt.logs, "AddedFile", ADDED_FILE_EVENT);
2993
+ const pending = JSON.parse(event.pendingPayload);
2994
+ const frontendUrl = getRuntimeConfig().FRONTEND_URL;
2995
+ const payload = {
2996
+ onchainVersion: file2.localVersion,
2997
+ onChainFileId,
2998
+ linkKey: pending.linkKey,
2999
+ linkKeyNonce: pending.linkKeyNonce,
3000
+ commentKey: pending.commentKey,
3001
+ metadata: pending.metadata,
3002
+ link: `${frontendUrl}/${file2.portalAddress}/${onChainFileId}#key=${pending.linkKey}`
3003
+ };
3004
+ const updatedFile = await FilesModel.update(fileId, payload, file2.portalAddress);
3005
+ if (updatedFile.localVersion === updatedFile.onchainVersion) {
3006
+ await FilesModel.update(fileId, { syncStatus: "synced" }, file2.portalAddress);
3007
+ }
3008
+ await EventsModel.clearEventPendingOp(event._id);
3009
+ logger.info(`File ${file2.ddocId} create resolved successfully`);
3010
+ break;
3011
+ }
3012
+ case "update": {
3013
+ const file2 = await FilesModel.findByIdExcludingDeleted(fileId);
3014
+ if (!file2) throw new Error(`File ${fileId} not found during resolve`);
3015
+ parseFileEventLog(receipt.logs, "EditedFile", EDITED_FILE_EVENT);
3016
+ const pending = JSON.parse(event.pendingPayload);
3017
+ const payload = {
3018
+ onchainVersion: pending.localVersion,
3019
+ metadata: pending.metadata
3020
+ };
3021
+ const updatedFile = await FilesModel.update(fileId, payload, file2.portalAddress);
3022
+ if (updatedFile.localVersion === updatedFile.onchainVersion) {
3023
+ await FilesModel.update(fileId, { syncStatus: "synced" }, file2.portalAddress);
3024
+ }
3025
+ await EventsModel.clearEventPendingOp(event._id);
3026
+ logger.info(`File ${file2.ddocId} update resolved successfully`);
3027
+ break;
3028
+ }
3029
+ case "delete": {
3030
+ const file2 = await FilesModel.findByIdIncludingDeleted(fileId);
3031
+ if (!file2) throw new Error(`File ${fileId} not found during resolve`);
3032
+ parseFileEventLog(receipt.logs, "DeletedFile", DELETED_FILE_EVENT);
3033
+ await FilesModel.update(
3034
+ fileId,
3035
+ { syncStatus: "synced", isDeleted: 1, onchainVersion: file2.localVersion },
3036
+ file2.portalAddress
3037
+ );
3038
+ await EventsModel.clearEventPendingOp(event._id);
3039
+ logger.info(`File ${fileId} delete resolved successfully`);
3040
+ break;
3041
+ }
3042
+ default:
3043
+ throw new Error(`Unknown event type: ${type}`);
3044
+ }
3045
+ return { resolved: true };
3046
+ };
2795
3047
  onTransactionSuccess = async (fileId, file2, onChainFileId, pending) => {
2796
3048
  const frontendUrl = getRuntimeConfig().FRONTEND_URL;
2797
3049
  const payload = {
@@ -3409,7 +3661,7 @@ CREATE TABLE IF NOT EXISTS events (
3409
3661
  type TEXT NOT NULL CHECK (type IN ('create', 'update', 'delete')),
3410
3662
  timestamp BIGINT NOT NULL,
3411
3663
  fileId TEXT NOT NULL,
3412
- status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'processed', 'failed')),
3664
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'submitted', 'processed', 'failed')),
3413
3665
  retryCount INTEGER NOT NULL DEFAULT 0,
3414
3666
  lastError TEXT,
3415
3667
  lockedAt BIGINT,
@@ -17848,10 +18100,15 @@ export {
17848
18100
  listFiles,
17849
18101
  listFolders,
17850
18102
  processEvent,
18103
+ resolveEvent,
18104
+ resolveFileOp,
17851
18105
  runMigrations,
17852
18106
  savePortal,
17853
18107
  searchNodes,
17854
18108
  setAdapter,
18109
+ submitDeleteFileOp,
18110
+ submitEvent,
18111
+ submitUpdateFileOp,
17855
18112
  updateFile
17856
18113
  };
17857
18114
  /*! Bundled license information: