@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.
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cloudflare.js +261 -4
- package/dist/cloudflare.js.map +1 -1
- package/dist/commands/index.js +25 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/index.js +75 -1
- package/dist/index.js.map +1 -1
- package/dist/worker.js +75 -1
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cloudflare.js
CHANGED
|
@@ -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:
|