@seedprotocol/sdk 0.7.3-next.613.g79a8dfd → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{db-CQt-gcAl.js → BasePathResolver-Cm4apKRb.js} +2041 -127
- package/dist/BasePathResolver-Cm4apKRb.js.map +1 -0
- package/dist/{ClientManager-DDA9956Y.js → ClientManager-i3nJASkg.js} +664 -277
- package/dist/ClientManager-i3nJASkg.js.map +1 -0
- package/dist/{Item-D4FigtYS.js → Item-DBFchXpC.js} +495 -219
- package/dist/Item-DBFchXpC.js.map +1 -0
- package/dist/{ModelProperty-BDt6AU2h.js → ModelProperty-BS49g01Y.js} +167 -47
- package/dist/ModelProperty-BS49g01Y.js.map +1 -0
- package/dist/{Schema-B8xqLWkL.js → Schema-CpMNEnUO.js} +53 -42
- package/dist/Schema-CpMNEnUO.js.map +1 -0
- package/dist/{SchemaValidationService-BPxkfDLU.js → SchemaValidationService-DieV4KL-.js} +13 -14
- package/dist/{SchemaValidationService-BPxkfDLU.js.map → SchemaValidationService-DieV4KL-.js.map} +1 -1
- package/dist/{backfillMetadataPropertyIds-DuTPcv8t.js → backfillMetadataPropertyIds-DSoOoTnX.js} +14 -7
- package/dist/{backfillMetadataPropertyIds-DuTPcv8t.js.map → backfillMetadataPropertyIds-DSoOoTnX.js.map} +1 -1
- package/dist/{configurePlatform-YcOiDQBZ.js → configurePlatform-D0lQGAMQ.js} +8 -7
- package/dist/{configurePlatform-YcOiDQBZ.js.map → configurePlatform-D0lQGAMQ.js.map} +1 -1
- package/dist/detachRowsBeforeDelete-S0q-DJPT.js +50 -0
- package/dist/detachRowsBeforeDelete-S0q-DJPT.js.map +1 -0
- package/dist/{eas-CpOD74Ke.js → eas-6pjU-OuX.js} +9 -8
- package/dist/eas-6pjU-OuX.js.map +1 -0
- package/dist/{getItem-B_OoFkd7.js → getItem-CxBNZ-WL.js} +13 -11
- package/dist/{getItem-B_OoFkd7.js.map → getItem-CxBNZ-WL.js.map} +1 -1
- package/dist/{getPublishPayload-Xl3sRkR-.js → getPublishPayload-CTCvRUoU.js} +50 -18
- package/dist/getPublishPayload-CTCvRUoU.js.map +1 -0
- package/dist/getPublishUploads-SFHCbf_y.js +648 -0
- package/dist/getPublishUploads-SFHCbf_y.js.map +1 -0
- package/dist/{index-Xl1_4Lmb.js → index-8O3Uow-Y.js} +125 -57
- package/dist/index-8O3Uow-Y.js.map +1 -0
- package/dist/{index-CSOQFAOn.js → index-AVFTVp1m.js} +2 -2
- package/dist/index-AVFTVp1m.js.map +1 -0
- package/dist/index-Cvt5UBz4.js +30 -0
- package/dist/index-Cvt5UBz4.js.map +1 -0
- package/dist/{index.browser-CjTe0vt1.js → index.browser-DBhxRoe8.js} +554 -165
- package/dist/index.browser-DBhxRoe8.js.map +1 -0
- package/dist/{index.node-Cfa7npS6.js → index.node-CJhlcNHM.js} +25 -13
- package/dist/index.node-CJhlcNHM.js.map +1 -0
- package/dist/main.js +30 -47
- package/dist/main.js.map +1 -1
- package/dist/mimeTypes-DHAHC4eZ.js +204 -0
- package/dist/mimeTypes-DHAHC4eZ.js.map +1 -0
- package/dist/node.js +19 -21
- package/dist/node.js.map +1 -1
- package/dist/platform.browser.js +13 -11
- package/dist/platform.browser.js.map +1 -1
- package/dist/platform.node.js +18 -5
- package/dist/platform.node.js.map +1 -1
- package/dist/{property-BrXkY1Rg.js → property-DwPD283A.js} +14 -12
- package/dist/property-DwPD283A.js.map +1 -0
- package/dist/{publishDraftGraph-X0Wr9txE.js → publishDraftGraph-BOn3_Occ.js} +6 -9
- package/dist/{publishDraftGraph-X0Wr9txE.js.map → publishDraftGraph-BOn3_Occ.js.map} +1 -1
- package/dist/{resetAmbiguousLegacyItemData-Ci6PM_vC.js → resetAmbiguousLegacyItemData-BkYmBeJc.js} +14 -3
- package/dist/{resetAmbiguousLegacyItemData-Ci6PM_vC.js.map → resetAmbiguousLegacyItemData-BkYmBeJc.js.map} +1 -1
- package/dist/{resolveModelForSyncedSeed-DFVdnYq8.js → resolveModelForSyncedSeed-BL24nLa7.js} +2 -2
- package/dist/{resolveModelForSyncedSeed-DFVdnYq8.js.map → resolveModelForSyncedSeed-BL24nLa7.js.map} +1 -1
- package/dist/src/Item/Item.d.ts +21 -3
- package/dist/src/Item/Item.d.ts.map +1 -1
- package/dist/src/Item/service/actors/runPublish.d.ts +11 -2
- package/dist/src/Item/service/actors/runPublish.d.ts.map +1 -1
- package/dist/src/Item/service/itemMachineSingle.d.ts +12 -2
- package/dist/src/Item/service/itemMachineSingle.d.ts.map +1 -1
- package/dist/src/ItemProperty/ItemProperty.d.ts +5 -3
- package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts.map +1 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts.map +1 -1
- package/dist/src/ItemProperty/service/propertyMachine.d.ts.map +1 -1
- package/dist/src/Model/Model.d.ts +10 -4
- package/dist/src/Model/Model.d.ts.map +1 -1
- package/dist/src/Model/index.d.ts +2 -2
- package/dist/src/ModelProperty/ModelProperty.d.ts +10 -2
- package/dist/src/ModelProperty/ModelProperty.d.ts.map +1 -1
- package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +3 -0
- package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts.map +1 -1
- package/dist/src/Schema/Schema.d.ts +2 -2
- package/dist/src/Schema/Schema.d.ts.map +1 -1
- package/dist/src/Schema/index.d.ts +0 -1
- package/dist/src/Schema/index.d.ts.map +1 -1
- package/dist/src/browser/helpers/FileManager.d.ts +4 -1
- package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
- package/dist/src/browser/helpers/tolerantWebAccess.d.ts +79 -4
- package/dist/src/browser/helpers/tolerantWebAccess.d.ts.map +1 -1
- package/dist/src/browser/workers/FileDownloader.d.ts +19 -4
- package/dist/src/browser/workers/FileDownloader.d.ts.map +1 -1
- package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
- package/dist/src/client/ClientManager.d.ts +30 -11
- package/dist/src/client/ClientManager.d.ts.map +1 -1
- package/dist/src/client/actors/platformClassesInit.d.ts +6 -0
- package/dist/src/client/actors/platformClassesInit.d.ts.map +1 -1
- package/dist/src/client/actors/processSchemaFiles.d.ts.map +1 -1
- package/dist/src/client/actors/saveConfig.d.ts +13 -6
- package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
- package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
- package/dist/src/db/migrateLegacyItemStorageRows.d.ts +57 -0
- package/dist/src/db/migrateLegacyItemStorageRows.d.ts.map +1 -0
- package/dist/src/db/read/getCompositeChildPaths.d.ts +13 -0
- package/dist/src/db/read/getCompositeChildPaths.d.ts.map +1 -0
- package/dist/src/db/read/getItems.d.ts.map +1 -1
- package/dist/src/db/read/getPublishPayload.d.ts +1 -1
- package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
- package/dist/src/db/read/getPublishUploads.d.ts +32 -3
- package/dist/src/db/read/getPublishUploads.d.ts.map +1 -1
- package/dist/src/db/read/summarizePublishWork.d.ts.map +1 -1
- package/dist/src/db/write/claimUnpublishedDrafts.d.ts +2 -0
- package/dist/src/db/write/claimUnpublishedDrafts.d.ts.map +1 -1
- package/dist/src/db/write/convertVerbatimItemStorageRow.d.ts +44 -0
- package/dist/src/db/write/convertVerbatimItemStorageRow.d.ts.map +1 -0
- package/dist/src/db/write/createNewItem.d.ts.map +1 -1
- package/dist/src/eventBus.d.ts +5 -0
- package/dist/src/eventBus.d.ts.map +1 -1
- package/dist/src/events/files/download.d.ts.map +1 -1
- package/dist/src/helpers/FileManager/BaseFileManager.d.ts +3 -3
- package/dist/src/helpers/FileManager/BaseFileManager.d.ts.map +1 -1
- package/dist/src/helpers/FileManager/IFileManager.d.ts +25 -1
- package/dist/src/helpers/FileManager/IFileManager.d.ts.map +1 -1
- package/dist/src/helpers/arweaveContentTags.d.ts +33 -0
- package/dist/src/helpers/arweaveContentTags.d.ts.map +1 -0
- package/dist/src/helpers/cachedPropertyRefresh.d.ts +12 -0
- package/dist/src/helpers/cachedPropertyRefresh.d.ts.map +1 -0
- package/dist/src/helpers/compositeUpload.d.ts +28 -0
- package/dist/src/helpers/compositeUpload.d.ts.map +1 -0
- package/dist/src/helpers/db.d.ts +3 -0
- package/dist/src/helpers/db.d.ts.map +1 -1
- package/dist/src/helpers/easSdk.d.ts +16 -0
- package/dist/src/helpers/easSdk.d.ts.map +1 -0
- package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts +16 -0
- package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts.map +1 -0
- package/dist/src/helpers/entity/entityDestroy.d.ts +2 -0
- package/dist/src/helpers/entity/entityDestroy.d.ts.map +1 -1
- package/dist/src/helpers/itemStorageFile.d.ts +57 -0
- package/dist/src/helpers/itemStorageFile.d.ts.map +1 -0
- package/dist/src/helpers/mapWithConcurrency.d.ts +7 -0
- package/dist/src/helpers/mapWithConcurrency.d.ts.map +1 -0
- package/dist/src/helpers/mimeTypes.d.ts +43 -0
- package/dist/src/helpers/mimeTypes.d.ts.map +1 -0
- package/dist/src/helpers/ownership.d.ts +28 -4
- package/dist/src/helpers/ownership.d.ts.map +1 -1
- package/dist/src/helpers/property/index.d.ts +37 -51
- package/dist/src/helpers/property/index.d.ts.map +1 -1
- package/dist/src/helpers/publishConfig.d.ts +27 -8
- package/dist/src/helpers/publishConfig.d.ts.map +1 -1
- package/dist/src/helpers/storageDir.d.ts +16 -0
- package/dist/src/helpers/storageDir.d.ts.map +1 -0
- package/dist/src/helpers/tabCoordinator.d.ts +5 -1
- package/dist/src/helpers/tabCoordinator.d.ts.map +1 -1
- package/dist/src/helpers/tabEvents.d.ts +23 -2
- package/dist/src/helpers/tabEvents.d.ts.map +1 -1
- package/dist/src/helpers/updateSchema.d.ts +5 -0
- package/dist/src/helpers/updateSchema.d.ts.map +1 -1
- package/dist/src/helpers/waitForEntityIdle.d.ts +5 -0
- package/dist/src/helpers/waitForEntityIdle.d.ts.map +1 -1
- package/dist/src/imports/json.d.ts +9 -0
- package/dist/src/imports/json.d.ts.map +1 -1
- package/dist/src/index.d.ts +11 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/interfaces/IItem.d.ts +7 -1
- package/dist/src/interfaces/IItem.d.ts.map +1 -1
- package/dist/src/node/db/Db.d.ts.map +1 -1
- package/dist/src/node/helpers/PathResolver.d.ts.map +1 -1
- package/dist/src/node/index.d.ts +8 -1
- package/dist/src/node/index.d.ts.map +1 -1
- package/dist/src/platform/index.browser.d.ts +2 -0
- package/dist/src/platform/index.browser.d.ts.map +1 -1
- package/dist/src/platform/index.node.d.ts +2 -0
- package/dist/src/platform/index.node.d.ts.map +1 -1
- package/dist/src/types/fileManager.d.ts +3 -14
- package/dist/src/types/fileManager.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +12 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/machines.d.ts +2 -0
- package/dist/src/types/machines.d.ts.map +1 -1
- package/dist/src/types/property.d.ts +8 -0
- package/dist/src/types/property.d.ts.map +1 -1
- package/dist/src/types/publish.d.ts +14 -0
- package/dist/src/types/publish.d.ts.map +1 -1
- package/dist/{syncDbWithEas-BU-jZ5bJ.js → syncDbWithEas-Bv2Fe38J.js} +17 -17
- package/dist/{syncDbWithEas-BU-jZ5bJ.js.map → syncDbWithEas-Bv2Fe38J.js.map} +1 -1
- package/dist/tabEvents-9s2WCYw-.js +335 -0
- package/dist/tabEvents-9s2WCYw-.js.map +1 -0
- package/dist/tolerantWebAccess-iUu80pAP.js +551 -0
- package/dist/tolerantWebAccess-iUu80pAP.js.map +1 -0
- package/dist/{updateSeedRevokedAt-iLpzae1i.js → updateSeedRevokedAt-zBfi931j.js} +3 -3
- package/dist/{updateSeedRevokedAt-iLpzae1i.js.map → updateSeedRevokedAt-zBfi931j.js.map} +1 -1
- package/package.json +11 -12
- package/src/node/easLodashRegister.js +4 -0
- package/dist/BasePathResolver-BdxCSIzn.js +0 -679
- package/dist/BasePathResolver-BdxCSIzn.js.map +0 -1
- package/dist/ClientManager-DDA9956Y.js.map +0 -1
- package/dist/Item-D4FigtYS.js.map +0 -1
- package/dist/ModelProperty-BDt6AU2h.js.map +0 -1
- package/dist/Schema-B8xqLWkL.js.map +0 -1
- package/dist/cjs/ModelProperty-B-cZm1yU.js +0 -1977
- package/dist/cjs/ModelProperty-B-cZm1yU.js.map +0 -1
- package/dist/cjs/Schema-STP972Xw.js +0 -5256
- package/dist/cjs/Schema-STP972Xw.js.map +0 -1
- package/dist/cjs/SchemaValidationService-CKNW1bKg.js +0 -778
- package/dist/cjs/SchemaValidationService-CKNW1bKg.js.map +0 -1
- package/dist/cjs/backfillMetadataPropertyIds-CP3y1skt.js +0 -89
- package/dist/cjs/backfillMetadataPropertyIds-CP3y1skt.js.map +0 -1
- package/dist/cjs/eas-CAnUC4wd.js +0 -138
- package/dist/cjs/eas-CAnUC4wd.js.map +0 -1
- package/dist/cjs/getItem-CWgb3e9G.js +0 -58
- package/dist/cjs/getItem-CWgb3e9G.js.map +0 -1
- package/dist/cjs/getPublishPayload-BsZWiJ03.js +0 -1603
- package/dist/cjs/getPublishPayload-BsZWiJ03.js.map +0 -1
- package/dist/cjs/getPublishUploads-BQr8aMVP.js +0 -495
- package/dist/cjs/getPublishUploads-BQr8aMVP.js.map +0 -1
- package/dist/cjs/index-nA5PxBSs.js +0 -70
- package/dist/cjs/index-nA5PxBSs.js.map +0 -1
- package/dist/cjs/index-usKaANAq.js +0 -22171
- package/dist/cjs/index-usKaANAq.js.map +0 -1
- package/dist/cjs/index.browser-BSf8HpnU.js +0 -22
- package/dist/cjs/index.browser-BSf8HpnU.js.map +0 -1
- package/dist/cjs/property-xMwPhQ5N.js +0 -327
- package/dist/cjs/property-xMwPhQ5N.js.map +0 -1
- package/dist/cjs/publishDraftGraph-BRWlR1KO.js +0 -403
- package/dist/cjs/publishDraftGraph-BRWlR1KO.js.map +0 -1
- package/dist/cjs/resetAmbiguousLegacyItemData-8nfkj9rz.js +0 -116
- package/dist/cjs/resetAmbiguousLegacyItemData-8nfkj9rz.js.map +0 -1
- package/dist/cjs/resolveModelForSyncedSeed-kYRuDWYw.js +0 -49
- package/dist/cjs/resolveModelForSyncedSeed-kYRuDWYw.js.map +0 -1
- package/dist/cjs/syncDbWithEas-DqRhV8o2.js +0 -886
- package/dist/cjs/syncDbWithEas-DqRhV8o2.js.map +0 -1
- package/dist/db-CQt-gcAl.js.map +0 -1
- package/dist/eas-CpOD74Ke.js.map +0 -1
- package/dist/getPublishPayload-Xl3sRkR-.js.map +0 -1
- package/dist/getPublishUploads-DHP01KgU.js +0 -484
- package/dist/getPublishUploads-DHP01KgU.js.map +0 -1
- package/dist/index-BGt4_oPO.js +0 -31
- package/dist/index-BGt4_oPO.js.map +0 -1
- package/dist/index-CSOQFAOn.js.map +0 -1
- package/dist/index-Xl1_4Lmb.js.map +0 -1
- package/dist/index.browser-CjTe0vt1.js.map +0 -1
- package/dist/index.node-Cfa7npS6.js.map +0 -1
- package/dist/main.cjs +0 -84
- package/dist/main.cjs.map +0 -1
- package/dist/property-BrXkY1Rg.js.map +0 -1
- package/dist/tabEvents-BZJMY5uK.js +0 -1344
- package/dist/tabEvents-BZJMY5uK.js.map +0 -1
- package/dist/tolerantWebAccess-BZ2RA_mb.js +0 -160
- package/dist/tolerantWebAccess-BZ2RA_mb.js.map +0 -1
|
@@ -1,10 +1,272 @@
|
|
|
1
|
-
import { j as models, g as modelSchemas, s as schemas, a as BaseDb, r as seeds, p as properties, b as appState, m as metadata } from './BasePathResolver-BdxCSIzn.js';
|
|
2
|
-
import { toSnakeCase } from 'drizzle-orm/casing';
|
|
3
|
-
import { eq, and, inArray, desc, isNull } from 'drizzle-orm';
|
|
4
1
|
import debug from 'debug';
|
|
5
|
-
import {
|
|
2
|
+
import { isEasAttestationExplorerUrl, BaseArweaveClient, ensureReadGatewaySelected, getArweaveReadBaseUrls, getTransactionTagsByIds } from '@seedprotocol/arweave';
|
|
3
|
+
import { BaseQueryClient, BaseEasClient, GET_FILES_METADATA } from '@seedprotocol/eas';
|
|
4
|
+
import { waitFor } from 'xstate';
|
|
6
5
|
import 'pluralize';
|
|
7
6
|
import { Type } from '@sinclair/typebox';
|
|
7
|
+
import { sqliteTable, integer, text, unique, int, blob, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
8
|
+
import { relations, and, eq, inArray, desc, isNull, isNotNull, or } from 'drizzle-orm';
|
|
9
|
+
import { toSnakeCase as toSnakeCase$1 } from 'drizzle-orm/casing';
|
|
10
|
+
import { upperFirst, camelCase, throttle } from 'lodash-es';
|
|
11
|
+
import { customAlphabet } from 'nanoid';
|
|
12
|
+
import { alphanumeric } from 'nanoid-dictionary';
|
|
13
|
+
import '@seedprotocol/query';
|
|
14
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
15
|
+
import { sha3_256 } from '@noble/hashes/sha3';
|
|
16
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
17
|
+
|
|
18
|
+
class BaseDb {
|
|
19
|
+
static configure(impl) {
|
|
20
|
+
if (!impl) {
|
|
21
|
+
throw new Error('Cannot configure Db with undefined or null. Ensure the platform-specific Db is properly created.');
|
|
22
|
+
}
|
|
23
|
+
BaseDb._impl = impl;
|
|
24
|
+
}
|
|
25
|
+
static requireImpl() {
|
|
26
|
+
if (!BaseDb._impl) {
|
|
27
|
+
throw new Error('Db not configured. Call BaseDb.configure() during platform init.');
|
|
28
|
+
}
|
|
29
|
+
return BaseDb._impl;
|
|
30
|
+
}
|
|
31
|
+
static getAppDb() {
|
|
32
|
+
return BaseDb.requireImpl().getAppDb();
|
|
33
|
+
}
|
|
34
|
+
static prepareDb(filesDir, config) {
|
|
35
|
+
return BaseDb.requireImpl().prepareDb(filesDir, config);
|
|
36
|
+
}
|
|
37
|
+
static isAppDbReady() {
|
|
38
|
+
if (!BaseDb._impl) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return BaseDb._impl.isAppDbReady();
|
|
42
|
+
}
|
|
43
|
+
static connectToDb(pathToDir) {
|
|
44
|
+
return BaseDb.requireImpl().connectToDb(pathToDir);
|
|
45
|
+
}
|
|
46
|
+
static async migrate(pathToDbDir, dbName, dbId) {
|
|
47
|
+
return BaseDb.requireImpl().migrate(pathToDbDir, dbName, dbId);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Execute a reactive query that emits new results whenever the underlying data changes.
|
|
51
|
+
*
|
|
52
|
+
* Supports two usage patterns:
|
|
53
|
+
* 1. SQL tag function: liveQuery((sql) => sql`SELECT * FROM models`)
|
|
54
|
+
* 2. Drizzle query builder (browser only): liveQuery(db.select().from(models))
|
|
55
|
+
*/
|
|
56
|
+
static liveQuery(query) {
|
|
57
|
+
return BaseDb.requireImpl().liveQuery(query);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
BaseDb._impl = null;
|
|
61
|
+
|
|
62
|
+
const models = sqliteTable('models', {
|
|
63
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
64
|
+
name: text('name').notNull(),
|
|
65
|
+
schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking - must be unique
|
|
66
|
+
isEdited: integer('is_edited', { mode: 'boolean' }), // true if model has been edited locally, false if matches schema file
|
|
67
|
+
}, (table) => {
|
|
68
|
+
return {
|
|
69
|
+
uniqueSchemaFileId: unique('unique_schema_file_id').on(table.schemaFileId),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
const modelsRelations = relations(models, ({ many }) => ({
|
|
73
|
+
properties: many(properties),
|
|
74
|
+
}));
|
|
75
|
+
const properties = sqliteTable('properties', {
|
|
76
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
77
|
+
name: text('name').notNull(),
|
|
78
|
+
dataType: text('data_type').notNull(),
|
|
79
|
+
modelId: integer('model_id')
|
|
80
|
+
.notNull()
|
|
81
|
+
.references(() => models.id),
|
|
82
|
+
refModelId: integer('ref_model_id').references(() => models.id),
|
|
83
|
+
refValueType: text('ref_value_type'),
|
|
84
|
+
schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking
|
|
85
|
+
isEdited: integer('is_edited', { mode: 'boolean' }), // true if property has been edited locally, false if matches schema file
|
|
86
|
+
required: integer('required', { mode: 'boolean' }), // true if relation/image property is required (publish fails when related item not found)
|
|
87
|
+
// Storage settings from the schema file's `storage: { type, path, extension }`, in the internal
|
|
88
|
+
// shape ModelProperty uses. Null when the property has no storage config.
|
|
89
|
+
storageType: text('storage_type'), // 'ItemStorage' | 'PropertyStorage'
|
|
90
|
+
localStorageDir: text('local_storage_dir'), // storage.path, e.g. '/html'
|
|
91
|
+
filenameSuffix: text('filename_suffix'), // storage.extension, e.g. '.html'
|
|
92
|
+
}, (table) => {
|
|
93
|
+
return {
|
|
94
|
+
uniqueNameModelId: unique('unique_name_model_id').on(table.name, table.modelId),
|
|
95
|
+
uniqueSchemaFileId: unique('unique_property_schema_file_id').on(table.schemaFileId),
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
const propertiesRelations = relations(properties, ({ one }) => ({
|
|
99
|
+
model: one(models),
|
|
100
|
+
refModel: one(models),
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
var ModelSchema = /*#__PURE__*/Object.freeze({
|
|
104
|
+
__proto__: null,
|
|
105
|
+
models: models,
|
|
106
|
+
modelsRelations: modelsRelations,
|
|
107
|
+
properties: properties,
|
|
108
|
+
propertiesRelations: propertiesRelations
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
class BaseFileManager {
|
|
112
|
+
static configure(impl) {
|
|
113
|
+
if (!impl) {
|
|
114
|
+
throw new Error('Cannot configure FileManager with undefined or null. Ensure the platform-specific FileManager is properly created.');
|
|
115
|
+
}
|
|
116
|
+
BaseFileManager._impl = impl;
|
|
117
|
+
}
|
|
118
|
+
static requireImpl() {
|
|
119
|
+
if (!BaseFileManager._impl) {
|
|
120
|
+
throw new Error('FileManager not configured. Call BaseFileManager.configure() during platform init.');
|
|
121
|
+
}
|
|
122
|
+
return BaseFileManager._impl;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Clears FS init flags so a later call can retry. Used after a failed init
|
|
126
|
+
* and by tests. Does not unmount or delete any files.
|
|
127
|
+
*/
|
|
128
|
+
static resetInitializationState() {
|
|
129
|
+
this.fileSystemInitialized = false;
|
|
130
|
+
this.initializing = false;
|
|
131
|
+
this.initializePromise = null;
|
|
132
|
+
this.workingDir = undefined;
|
|
133
|
+
}
|
|
134
|
+
static initializeFileSystem(workingDir) {
|
|
135
|
+
if (this.fileSystemInitialized) {
|
|
136
|
+
return Promise.resolve();
|
|
137
|
+
}
|
|
138
|
+
if (this.initializePromise) {
|
|
139
|
+
return this.initializePromise;
|
|
140
|
+
}
|
|
141
|
+
this.initializing = true;
|
|
142
|
+
const pending = this.runInitializeFileSystem(workingDir).finally(() => {
|
|
143
|
+
this.initializing = false;
|
|
144
|
+
if (this.initializePromise === pending) {
|
|
145
|
+
this.initializePromise = null;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
this.initializePromise = pending;
|
|
149
|
+
return pending;
|
|
150
|
+
}
|
|
151
|
+
static async runInitializeFileSystem(workingDir) {
|
|
152
|
+
await BaseFileManager.requireImpl().initializeFileSystem(workingDir);
|
|
153
|
+
this.fileSystemInitialized = true;
|
|
154
|
+
this.workingDir = workingDir;
|
|
155
|
+
}
|
|
156
|
+
static getWorkingDir() {
|
|
157
|
+
if (!this.workingDir) {
|
|
158
|
+
throw new Error('Working directory is not set');
|
|
159
|
+
}
|
|
160
|
+
return this.workingDir;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Build a path under the configured files root (e.g. /app-files).
|
|
164
|
+
* Use this instead of hardcoding /files/ for images, html, json, etc.
|
|
165
|
+
* @param subpaths - path segments to join (e.g. 'images', fileName)
|
|
166
|
+
* @returns full path like /app-files/images/egg.jpg
|
|
167
|
+
*/
|
|
168
|
+
static getFilesPath(...subpaths) {
|
|
169
|
+
const root = this.getWorkingDir().replace(/\/$/, '');
|
|
170
|
+
const parts = [root, ...subpaths].filter(Boolean);
|
|
171
|
+
return parts.join('/').replace(/\/+/g, '/');
|
|
172
|
+
}
|
|
173
|
+
static getContentUrlFromPath(path) {
|
|
174
|
+
return BaseFileManager.requireImpl().getContentUrlFromPath(path);
|
|
175
|
+
}
|
|
176
|
+
static downloadAllFiles(params) {
|
|
177
|
+
return BaseFileManager.requireImpl().downloadAllFiles(params);
|
|
178
|
+
}
|
|
179
|
+
static downloadFileByTransactionId({ transactionId, arweaveHost, arweaveBaseUrls, excludedTransactions, expectedContentHash, compositeChildPaths, }) {
|
|
180
|
+
return BaseFileManager.requireImpl().downloadAllFiles({
|
|
181
|
+
transactionIds: [transactionId],
|
|
182
|
+
arweaveHost,
|
|
183
|
+
arweaveBaseUrls,
|
|
184
|
+
excludedTransactions,
|
|
185
|
+
expectedContentHashes: expectedContentHash ? { [transactionId]: expectedContentHash } : undefined,
|
|
186
|
+
compositeChildPaths: compositeChildPaths ? { [transactionId]: compositeChildPaths } : undefined,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
static resizeImage({ filePath, width, height }) {
|
|
190
|
+
return BaseFileManager.requireImpl().resizeImage({ filePath, width, height });
|
|
191
|
+
}
|
|
192
|
+
static resizeAllImages({ width, height }) {
|
|
193
|
+
return BaseFileManager.requireImpl().resizeAllImages({ width, height });
|
|
194
|
+
}
|
|
195
|
+
static pathExists(filePath) {
|
|
196
|
+
return BaseFileManager.requireImpl().pathExists(filePath);
|
|
197
|
+
}
|
|
198
|
+
static invalidateCachedPaths(filePaths) {
|
|
199
|
+
return BaseFileManager.requireImpl().invalidateCachedPaths(filePaths);
|
|
200
|
+
}
|
|
201
|
+
/** File size in bytes, or null if the path does not exist / cannot be stated. */
|
|
202
|
+
static getFileSize(filePath) {
|
|
203
|
+
return BaseFileManager.requireImpl().getFileSize(filePath);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Returns a list of filenames in the given directory (e.g. 'images', 'files').
|
|
207
|
+
*/
|
|
208
|
+
static listFiles(dir) {
|
|
209
|
+
return BaseFileManager.requireImpl().listFiles(dir);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Returns a list of image filenames in the images folder (originals only, excludes size subdirs).
|
|
213
|
+
* Use this to get all stored images without traversing 480/760/1024/1440/1920 subdirectories.
|
|
214
|
+
*/
|
|
215
|
+
static listImageFiles() {
|
|
216
|
+
return this.listFiles('images');
|
|
217
|
+
}
|
|
218
|
+
static createDirIfNotExists(filePath) {
|
|
219
|
+
return BaseFileManager.requireImpl().createDirIfNotExists(filePath);
|
|
220
|
+
}
|
|
221
|
+
static async waitForFile(filePath) {
|
|
222
|
+
return BaseFileManager.requireImpl().waitForFile(filePath);
|
|
223
|
+
}
|
|
224
|
+
static async waitForFileWithContent(filePath, interval, timeout) {
|
|
225
|
+
return BaseFileManager.requireImpl().waitForFileWithContent(filePath, interval, timeout);
|
|
226
|
+
}
|
|
227
|
+
static async saveFile(filePath, content) {
|
|
228
|
+
return BaseFileManager.requireImpl().saveFile(filePath, content);
|
|
229
|
+
}
|
|
230
|
+
static saveFileSync(filePath, content) {
|
|
231
|
+
return BaseFileManager.requireImpl().saveFileSync(filePath, content);
|
|
232
|
+
}
|
|
233
|
+
static async readFile(filePath) {
|
|
234
|
+
return BaseFileManager.requireImpl().readFile(filePath);
|
|
235
|
+
}
|
|
236
|
+
static readFileSync(filePath) {
|
|
237
|
+
return BaseFileManager.requireImpl().readFileSync(filePath);
|
|
238
|
+
}
|
|
239
|
+
static async readFileAsBuffer(filePath) {
|
|
240
|
+
return BaseFileManager.requireImpl().readFileAsBuffer(filePath);
|
|
241
|
+
}
|
|
242
|
+
static async readFileAsString(filePath) {
|
|
243
|
+
return BaseFileManager.requireImpl().readFileAsString(filePath);
|
|
244
|
+
}
|
|
245
|
+
static async getFs() {
|
|
246
|
+
return BaseFileManager.requireImpl().getFs();
|
|
247
|
+
}
|
|
248
|
+
static getFsSync() {
|
|
249
|
+
return BaseFileManager.requireImpl().getFsSync();
|
|
250
|
+
}
|
|
251
|
+
static getPathModule() {
|
|
252
|
+
return BaseFileManager.requireImpl().getPathModule();
|
|
253
|
+
}
|
|
254
|
+
static getParentDirPath(filePath) {
|
|
255
|
+
return BaseFileManager.requireImpl().getParentDirPath(filePath);
|
|
256
|
+
}
|
|
257
|
+
static getFilenameFromPath(filePath) {
|
|
258
|
+
return BaseFileManager.requireImpl().getFilenameFromPath(filePath);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
BaseFileManager.fileSystemInitialized = false;
|
|
262
|
+
BaseFileManager.initializing = false;
|
|
263
|
+
BaseFileManager._impl = null;
|
|
264
|
+
BaseFileManager.initializePromise = null;
|
|
265
|
+
|
|
266
|
+
var BaseFileManager$1 = /*#__PURE__*/Object.freeze({
|
|
267
|
+
__proto__: null,
|
|
268
|
+
BaseFileManager: BaseFileManager
|
|
269
|
+
});
|
|
8
270
|
|
|
9
271
|
/** Default maximum length for Text properties when not explicitly set. Html holds arbitrary-length content and does not use this limit. */
|
|
10
272
|
const DEFAULT_TEXT_MAX_LENGTH = 255;
|
|
@@ -105,17 +367,19 @@ function normalizePropertyRecordSchema(def) {
|
|
|
105
367
|
}
|
|
106
368
|
return out;
|
|
107
369
|
}
|
|
370
|
+
// String literals, not enum members: PropertyDataType must accept plain strings
|
|
371
|
+
// such as `{ dataType: 'Text' }`. ModelPropertyDataTypes members still fit.
|
|
108
372
|
const TPropertyDataType = Type.Union([
|
|
109
|
-
Type.Literal(
|
|
110
|
-
Type.Literal(
|
|
111
|
-
Type.Literal(
|
|
112
|
-
Type.Literal(
|
|
113
|
-
Type.Literal(
|
|
114
|
-
Type.Literal(
|
|
115
|
-
Type.Literal(
|
|
116
|
-
Type.Literal(
|
|
117
|
-
Type.Literal(
|
|
118
|
-
Type.Literal(
|
|
373
|
+
Type.Literal('Text'),
|
|
374
|
+
Type.Literal('Number'),
|
|
375
|
+
Type.Literal('List'),
|
|
376
|
+
Type.Literal('Relation'),
|
|
377
|
+
Type.Literal('Image'),
|
|
378
|
+
Type.Literal('Json'),
|
|
379
|
+
Type.Literal('File'),
|
|
380
|
+
Type.Literal('Boolean'),
|
|
381
|
+
Type.Literal('Date'),
|
|
382
|
+
Type.Literal('Html'),
|
|
119
383
|
]);
|
|
120
384
|
const TStorageType = Type.Union([
|
|
121
385
|
Type.Literal('ItemStorage'), // Looks for a storageTransactionId property on the item
|
|
@@ -195,24 +459,547 @@ const Property = {
|
|
|
195
459
|
Html: htmlPropertyFactory,
|
|
196
460
|
/** `Html` opts are not representable in `TPropertyConstructor`; keep runtime shape via double assert. */
|
|
197
461
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
462
|
+
|
|
463
|
+
/** Same rules as getCorrectId in helpers/index (avoid barrel circular import). */
|
|
464
|
+
function parseLocalIdOrUid(localIdOrUid) {
|
|
465
|
+
const id = { localId: undefined, uid: undefined };
|
|
466
|
+
if (!localIdOrUid)
|
|
467
|
+
return id;
|
|
468
|
+
if (localIdOrUid.length === 10) {
|
|
469
|
+
id.localId = localIdOrUid;
|
|
470
|
+
}
|
|
471
|
+
if (localIdOrUid.startsWith('0x') && localIdOrUid.length === 66) {
|
|
472
|
+
id.uid = localIdOrUid;
|
|
473
|
+
}
|
|
474
|
+
return id;
|
|
475
|
+
}
|
|
476
|
+
/** Relation/image refs may be string ids or `{ seedLocalId?, seedUid? }`. */
|
|
477
|
+
function normalizeRelationPropertyValue(value) {
|
|
478
|
+
if (value == null)
|
|
479
|
+
return undefined;
|
|
480
|
+
if (typeof value === 'string') {
|
|
481
|
+
const t = value.trim();
|
|
482
|
+
return t !== '' ? t : undefined;
|
|
483
|
+
}
|
|
484
|
+
if (typeof value === 'object' && value !== null) {
|
|
485
|
+
const o = value;
|
|
486
|
+
if (typeof o.seedLocalId === 'string' && o.seedLocalId.trim() !== '') {
|
|
487
|
+
return o.seedLocalId.trim();
|
|
488
|
+
}
|
|
489
|
+
if (typeof o.seedUid === 'string' && o.seedUid.trim() !== '') {
|
|
490
|
+
return o.seedUid.trim();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
/** getCorrectId only accepts 10-char local ids and 0x66 uids; local seed refs can be up to 21 chars. */
|
|
496
|
+
const LOCAL_SEED_REF = /^[a-zA-Z0-9_-]{10,21}$/;
|
|
497
|
+
/** Resolve seed ids from a single string (context propertyValue or DB row). */
|
|
498
|
+
function resolveSeedIdsFromRefString(s) {
|
|
499
|
+
const fromCorrect = parseLocalIdOrUid(s);
|
|
500
|
+
if (fromCorrect.localId || fromCorrect.uid) {
|
|
501
|
+
return { seedLocalId: fromCorrect.localId, seedUid: fromCorrect.uid };
|
|
502
|
+
}
|
|
503
|
+
if (LOCAL_SEED_REF.test(s) && !s.startsWith('0x')) {
|
|
504
|
+
return { seedLocalId: s };
|
|
505
|
+
}
|
|
506
|
+
return {};
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* True when a relation ref is an attested seed uid (0x…) rather than a local id. A uid only exists once
|
|
510
|
+
* the seed is on EAS, so publish can attest the uid as-is when no local copy exists (e.g. a server
|
|
511
|
+
* publishing on someone's behalf that only holds the uids it was given). Callers must not walk into it.
|
|
512
|
+
*/
|
|
513
|
+
function isPublishedSeedRef(value) {
|
|
514
|
+
const ref = normalizeRelationPropertyValue(value);
|
|
515
|
+
if (!ref)
|
|
516
|
+
return false;
|
|
517
|
+
const { seedLocalId, seedUid } = resolveSeedIdsFromRefString(ref);
|
|
518
|
+
return !!seedUid && !seedLocalId;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Storage seed local id (nanoid) or 0x uid. Stricter than resolveSeedIdsFromRefString, which accepts
|
|
522
|
+
* any 10-char string (so a 10-char html snippet like "<p>raw</p>" would pass as a local id).
|
|
523
|
+
*/
|
|
524
|
+
const STORAGE_SEED_REF = /^(?:[A-Za-z0-9_-]{10,21}|0x[0-9a-fA-F]{64})$/;
|
|
525
|
+
/** True when a File/Image/Html value is already a storage seed reference rather than raw content. */
|
|
526
|
+
function isStorageSeedRef(value) {
|
|
527
|
+
return typeof value === 'string' && STORAGE_SEED_REF.test(value.trim());
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const seeds = sqliteTable('seeds', {
|
|
531
|
+
localId: text('local_id').unique(),
|
|
532
|
+
uid: text('uid'),
|
|
533
|
+
schemaUid: text('schema_uid'),
|
|
534
|
+
type: text('type'),
|
|
535
|
+
// schemaFileId of the models row this item belongs to (= Model.id). Disambiguates items whose
|
|
536
|
+
// model name exists in more than one schema; null for legacy rows that couldn't be backfilled.
|
|
537
|
+
modelFileId: text('model_file_id'),
|
|
538
|
+
publisher: text('publisher'),
|
|
539
|
+
attestationRaw: text('attestation_raw'),
|
|
540
|
+
attestationCreatedAt: int('attestation_created_at'),
|
|
541
|
+
createdAt: int('created_at'),
|
|
542
|
+
updatedAt: int('updated_at'),
|
|
543
|
+
_markedForDeletion: int('_marked_for_deletion'),
|
|
544
|
+
revokedAt: int('revoked_at'),
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const versions = sqliteTable('versions', {
|
|
548
|
+
localId: text('local_id').unique(),
|
|
549
|
+
uid: text('uid'),
|
|
550
|
+
seedLocalId: text('seed_local_id'),
|
|
551
|
+
seedUid: text('seed_uid'),
|
|
552
|
+
seedType: text('seed_type'),
|
|
553
|
+
note: text('note'),
|
|
554
|
+
createdAt: int('created_at'),
|
|
555
|
+
updatedAt: int('updated_at'),
|
|
556
|
+
attestationCreatedAt: int('attestation_created_at'),
|
|
557
|
+
attestationRaw: text('attestation_raw'),
|
|
558
|
+
publisher: text('publisher'),
|
|
559
|
+
/**
|
|
560
|
+
* When the version attestation was revoked, in Unix seconds (EAS `revocationTime`, or the local
|
|
561
|
+
* unpublish time until sync confirms it). Null while live or unpublished.
|
|
562
|
+
*/
|
|
563
|
+
revokedAt: int('revoked_at'),
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const metadata = sqliteTable('metadata', {
|
|
567
|
+
localId: text('local_id').unique(),
|
|
568
|
+
uid: text('uid'),
|
|
569
|
+
propertyId: integer('property_id').references(() => properties.id),
|
|
570
|
+
propertyName: text('property_name'),
|
|
571
|
+
propertyValue: text('property_value'),
|
|
572
|
+
schemaUid: text('schema_uid'),
|
|
573
|
+
modelType: text('model_type'),
|
|
574
|
+
seedLocalId: text('seed_local_id'),
|
|
575
|
+
seedUid: text('seed_uid'),
|
|
576
|
+
versionLocalId: text('version_local_id'),
|
|
577
|
+
versionUid: text('version_uid'),
|
|
578
|
+
easDataType: text('eas_data_type'),
|
|
579
|
+
refValueType: text('ref_value_type'),
|
|
580
|
+
refModelUid: text('ref_schema_uid'),
|
|
581
|
+
refSeedType: text('ref_seed_type'),
|
|
582
|
+
refResolvedValue: text('ref_resolved_value'),
|
|
583
|
+
refResolvedDisplayValue: text('ref_resolved_display_value'),
|
|
584
|
+
localStorageDir: text('local_storage_dir'),
|
|
585
|
+
attestationRaw: text('attestation_raw'),
|
|
586
|
+
attestationCreatedAt: int('attestation_created_at'),
|
|
587
|
+
contentHash: text('content_hash'),
|
|
588
|
+
createdAt: int('created_at'),
|
|
589
|
+
updatedAt: int('updated_at'),
|
|
590
|
+
publisher: text('publisher'),
|
|
591
|
+
// Unix seconds when this row's attestation was revoked on EAS; null while it's live (or for a
|
|
592
|
+
// local, unpublished row). EAS sync keeps only the canonical attestation per (version, property
|
|
593
|
+
// schema): the newest live one, or the newest revoked one when all are revoked. So a non-null
|
|
594
|
+
// value marks a property kept only for its last value, not one that is present on the version.
|
|
595
|
+
revokedAt: int('revoked_at'),
|
|
596
|
+
// For a row EAS sync derived for an ItemStorage property from a `storage_transaction_id`
|
|
597
|
+
// attestation (uid null, ref_value_type 'file', value = the transaction id): that attestation's
|
|
598
|
+
// uid. Null for every other row. Marks the row as published content, not a local draft.
|
|
599
|
+
derivedFromUid: text('derived_from_uid'),
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const appState = sqliteTable('appState', {
|
|
603
|
+
key: text('key').unique(),
|
|
604
|
+
value: text('value'),
|
|
605
|
+
createdAt: int('created_at'),
|
|
606
|
+
updatedAt: int('updated_at'),
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const modelUids = sqliteTable('model_uids', {
|
|
610
|
+
id: int('id').primaryKey({ autoIncrement: true }),
|
|
611
|
+
uid: text('uid').notNull(),
|
|
612
|
+
modelId: int('model_id')
|
|
613
|
+
.notNull()
|
|
614
|
+
.unique()
|
|
615
|
+
.references(() => models.id),
|
|
616
|
+
});
|
|
617
|
+
const modelRelations = relations(modelUids, ({ one }) => ({
|
|
618
|
+
model: one(models),
|
|
619
|
+
}));
|
|
620
|
+
|
|
621
|
+
const propertyUids = sqliteTable('property_uids', {
|
|
622
|
+
id: int('id').primaryKey({ autoIncrement: true }),
|
|
623
|
+
uid: text('uid').notNull(),
|
|
624
|
+
propertyId: int('property_id')
|
|
625
|
+
.notNull()
|
|
626
|
+
.unique()
|
|
627
|
+
.references(() => properties.id),
|
|
628
|
+
});
|
|
629
|
+
const propertyUidRelations = relations(propertyUids, ({ one }) => ({
|
|
630
|
+
property: one(properties),
|
|
631
|
+
}));
|
|
632
|
+
|
|
633
|
+
const config = sqliteTable('config', {
|
|
634
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
635
|
+
key: text('key').notNull(),
|
|
636
|
+
text: text('text'),
|
|
637
|
+
json: text('json'),
|
|
638
|
+
blob: blob('blob'),
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
const schemas = sqliteTable('schemas', {
|
|
642
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
643
|
+
name: text('name').notNull(),
|
|
644
|
+
version: integer('version').notNull(),
|
|
645
|
+
schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking - must be unique
|
|
646
|
+
schemaData: text('schema_data'), // Full JSON schema content (SchemaFileFormat as JSON string)
|
|
647
|
+
isDraft: integer('is_draft', { mode: 'boolean' }), // true if schema is in draft/editing state, false if published to file
|
|
648
|
+
isEdited: integer('is_edited', { mode: 'boolean' }), // true if schema has been edited locally, false if matches schema file
|
|
649
|
+
createdAt: int('created_at'),
|
|
650
|
+
updatedAt: int('updated_at'),
|
|
651
|
+
}, (table) => {
|
|
652
|
+
return {
|
|
653
|
+
uniqueSchemaFileId: unique('unique_schema_schema_file_id').on(table.schemaFileId),
|
|
204
654
|
};
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
var SchemaSchema = /*#__PURE__*/Object.freeze({
|
|
658
|
+
__proto__: null,
|
|
659
|
+
schemas: schemas
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
const modelSchemas = sqliteTable('model_schemas', {
|
|
663
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
664
|
+
modelId: integer('model_id').references(() => models.id),
|
|
665
|
+
schemaId: integer('schema_id').references(() => schemas.id),
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
var ModelSchemaSchema = /*#__PURE__*/Object.freeze({
|
|
669
|
+
__proto__: null,
|
|
670
|
+
modelSchemas: modelSchemas
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
const publishProcesses = sqliteTable('publish_processes', {
|
|
674
|
+
id: int('id').primaryKey({ autoIncrement: true }),
|
|
675
|
+
seedLocalId: text('seed_local_id').notNull(),
|
|
676
|
+
modelName: text('model_name').notNull(),
|
|
677
|
+
schemaId: text('schema_id'),
|
|
678
|
+
status: text('status').notNull(), // 'in_progress' | 'completed' | 'failed' | 'interrupted'
|
|
679
|
+
startedAt: int('started_at').notNull(),
|
|
680
|
+
completedAt: int('completed_at'),
|
|
681
|
+
errorMessage: text('error_message'),
|
|
682
|
+
errorStep: text('error_step'),
|
|
683
|
+
errorDetails: text('error_details'),
|
|
684
|
+
errorCode: text('error_code'), // e.g. ManagedAccountPublishError.code
|
|
685
|
+
persistedSnapshot: text('persisted_snapshot').notNull(),
|
|
686
|
+
seedId: text('seed_id'),
|
|
687
|
+
existingSeedUid: text('existing_seed_uid'),
|
|
688
|
+
createdAt: int('created_at'),
|
|
689
|
+
updatedAt: int('updated_at'),
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const uploadProcesses = sqliteTable('upload_processes', {
|
|
693
|
+
id: int('id').primaryKey({ autoIncrement: true }),
|
|
694
|
+
reimbursementConfirmed: int('reimbursement_confirmed').notNull(), // 0 or 1 for boolean
|
|
695
|
+
reimbursementTransactionId: text('reimbursement_transaction_id'),
|
|
696
|
+
transactionKeys: text('transaction_keys'),
|
|
697
|
+
persistedSnapshot: text('persisted_snapshot').notNull(), // JSON string
|
|
698
|
+
createdAt: int('created_at'),
|
|
699
|
+
updatedAt: int('updated_at'),
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
/** Background job: resolve L1 tx id and wait for L1 confirmation after bundler publish. */
|
|
703
|
+
const arweaveL1FinalizeJobs = sqliteTable('arweave_l1_finalize_jobs', {
|
|
704
|
+
id: int('id').primaryKey({ autoIncrement: true }),
|
|
705
|
+
seedLocalId: text('seed_local_id').notNull(),
|
|
706
|
+
dataItemId: text('data_item_id').notNull(),
|
|
707
|
+
/** L1 bundle / anchor transaction id from gateway GraphQL `bundledIn.id`. */
|
|
708
|
+
l1TransactionId: text('l1_transaction_id'),
|
|
709
|
+
bundleId: text('bundle_id'),
|
|
710
|
+
versionLocalId: text('version_local_id'),
|
|
711
|
+
itemPropertyName: text('item_property_name'),
|
|
712
|
+
/** `pending_l1` | `confirmed` | `failed` */
|
|
713
|
+
phase: text('phase').notNull(),
|
|
714
|
+
statusJson: text('status_json'),
|
|
715
|
+
errorMessage: text('error_message'),
|
|
716
|
+
createdAt: int('created_at').notNull(),
|
|
717
|
+
updatedAt: int('updated_at').notNull(),
|
|
718
|
+
}, (table) => ({
|
|
719
|
+
dataItemIdUnique: uniqueIndex('arweave_l1_finalize_jobs_data_item_id_unique').on(table.dataItemId),
|
|
720
|
+
}));
|
|
721
|
+
|
|
722
|
+
const easSyncProcesses = sqliteTable('eas_sync_processes', {
|
|
723
|
+
id: int('id').primaryKey({ autoIncrement: true }),
|
|
724
|
+
status: text('status').notNull(), // 'in_progress' | 'completed' | 'failed'
|
|
725
|
+
startedAt: int('started_at').notNull(),
|
|
726
|
+
completedAt: int('completed_at'),
|
|
727
|
+
requestPayload: text('request_payload').notNull(),
|
|
728
|
+
errorMessage: text('error_message'),
|
|
729
|
+
errorDetails: text('error_details'),
|
|
730
|
+
persistedSnapshot: text('persisted_snapshot').notNull(),
|
|
731
|
+
createdAt: int('created_at'),
|
|
732
|
+
updatedAt: int('updated_at'),
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Links Image seeds materialized from Html `data:image/*` URIs to the parent Item seed for co-publish.
|
|
737
|
+
* Rows are removed after a successful publish (see clearHtmlEmbeddedImageCoPublishRows).
|
|
738
|
+
*/
|
|
739
|
+
const htmlEmbeddedImageCoPublish = sqliteTable('html_embedded_image_co_publish', {
|
|
740
|
+
id: int('id').primaryKey({ autoIncrement: true }),
|
|
741
|
+
parentSeedLocalId: text('parent_seed_local_id').notNull(),
|
|
742
|
+
htmlSeedLocalId: text('html_seed_local_id').notNull(),
|
|
743
|
+
imageSeedLocalId: text('image_seed_local_id').notNull(),
|
|
744
|
+
/** Stable dedup key (e.g. sha256 of data URI or normalized URI string). */
|
|
745
|
+
stableKey: text('stable_key').notNull(),
|
|
746
|
+
createdAt: int('created_at').notNull(),
|
|
747
|
+
}, (t) => [
|
|
748
|
+
uniqueIndex('html_embed_co_pub_parent_html_stable').on(t.parentSeedLocalId, t.htmlSeedLocalId, t.stableKey),
|
|
749
|
+
]);
|
|
750
|
+
|
|
751
|
+
const seedUidToStorageTransactionId = new Map();
|
|
752
|
+
const getStorageTransactionIdForSeedUid = async (seedUid) => {
|
|
753
|
+
if (seedUidToStorageTransactionId.has(seedUid)) {
|
|
754
|
+
return seedUidToStorageTransactionId.get(seedUid);
|
|
755
|
+
}
|
|
756
|
+
const appDb = BaseDb.getAppDb();
|
|
757
|
+
const results = (await appDb
|
|
758
|
+
.select({
|
|
759
|
+
storageTransactionId: metadata.propertyValue,
|
|
760
|
+
})
|
|
761
|
+
.from(metadata)
|
|
762
|
+
.where(and(eq(metadata.seedUid, seedUid), eq(metadata.propertyName, 'storageTransactionId'))));
|
|
763
|
+
if (!results || results.length === 0) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
seedUidToStorageTransactionId.set(seedUid, results[0].storageTransactionId);
|
|
767
|
+
return results[0].storageTransactionId;
|
|
205
768
|
};
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
769
|
+
|
|
770
|
+
const logger$4 = debug('seedSdk:helpers:mediaRef');
|
|
771
|
+
/** Typical Arweave transaction id: 43 URL-safe base64 characters. */
|
|
772
|
+
const ARWEAVE_TX_ID = /^[a-zA-Z0-9_-]{43}$/;
|
|
773
|
+
function trimOrEmpty(raw) {
|
|
774
|
+
if (raw == null)
|
|
775
|
+
return '';
|
|
776
|
+
return typeof raw === 'string' ? raw.trim() : String(raw).trim();
|
|
777
|
+
}
|
|
778
|
+
function coalesceStringField(item, key) {
|
|
779
|
+
const v = item[key];
|
|
780
|
+
if (v == null)
|
|
781
|
+
return '';
|
|
782
|
+
if (typeof v === 'string')
|
|
783
|
+
return v.trim();
|
|
784
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
785
|
+
return String(v);
|
|
786
|
+
return '';
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Try JSON relation payloads and plain relation id strings.
|
|
790
|
+
*/
|
|
791
|
+
function unwrapRelationString(raw) {
|
|
792
|
+
const t = raw.trim();
|
|
793
|
+
if (!t)
|
|
794
|
+
return t;
|
|
795
|
+
try {
|
|
796
|
+
const parsed = JSON.parse(t);
|
|
797
|
+
const fromObj = normalizeRelationPropertyValue(parsed);
|
|
798
|
+
if (fromObj)
|
|
799
|
+
return fromObj;
|
|
800
|
+
}
|
|
801
|
+
catch {
|
|
802
|
+
// not JSON
|
|
803
|
+
}
|
|
804
|
+
return t;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Pure, sync classification of a media-related string from feeds or XML.
|
|
808
|
+
*/
|
|
809
|
+
function classifyMediaRef(raw, options) {
|
|
810
|
+
const s = trimOrEmpty(raw);
|
|
811
|
+
if (!s) {
|
|
812
|
+
return { kind: 'empty' };
|
|
813
|
+
}
|
|
814
|
+
const treatAs = options?.treatAs;
|
|
815
|
+
if (treatAs === 'url') {
|
|
816
|
+
return { kind: 'url', href: s };
|
|
817
|
+
}
|
|
818
|
+
if (treatAs === 'arweaveTx') {
|
|
819
|
+
return { kind: 'arweaveTxId', txId: s };
|
|
820
|
+
}
|
|
821
|
+
if (treatAs === 'seedUid') {
|
|
822
|
+
if (s.startsWith('0x') && s.length === 66) {
|
|
823
|
+
return { kind: 'seedUid', uid: s };
|
|
824
|
+
}
|
|
825
|
+
return { kind: 'unknown', raw: s };
|
|
826
|
+
}
|
|
827
|
+
if (s.startsWith('http://') || s.startsWith('https://') || s.startsWith('blob:') || s.startsWith('data:')) {
|
|
828
|
+
if (isEasAttestationExplorerUrl(s)) {
|
|
829
|
+
return { kind: 'nonMediaUrl', href: s };
|
|
830
|
+
}
|
|
831
|
+
return { kind: 'url', href: s };
|
|
832
|
+
}
|
|
833
|
+
const unwrapped = unwrapRelationString(s);
|
|
834
|
+
const ids = resolveSeedIdsFromRefString(unwrapped);
|
|
835
|
+
if (ids.seedUid) {
|
|
836
|
+
return { kind: 'seedUid', uid: ids.seedUid };
|
|
837
|
+
}
|
|
838
|
+
if (ids.seedLocalId) {
|
|
839
|
+
return { kind: 'seedLocalId', localId: ids.seedLocalId };
|
|
840
|
+
}
|
|
841
|
+
if (unwrapped.startsWith('0x') && unwrapped.length === 66) {
|
|
842
|
+
return { kind: 'seedUid', uid: unwrapped };
|
|
843
|
+
}
|
|
844
|
+
if (ARWEAVE_TX_ID.test(unwrapped) && !unwrapped.startsWith('0x')) {
|
|
845
|
+
return { kind: 'arweaveTxId', txId: unwrapped };
|
|
846
|
+
}
|
|
847
|
+
return { kind: 'unknown', raw: s };
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Same as convertTxIdToImage but local to this module to avoid circular imports with helpers/index.
|
|
851
|
+
*/
|
|
852
|
+
async function tryLocalBlobUrlFromTxId(txId) {
|
|
853
|
+
const imageFilePath = BaseFileManager.getFilesPath('images', txId);
|
|
854
|
+
const fileExists = await BaseFileManager.pathExists(imageFilePath);
|
|
855
|
+
if (!fileExists) {
|
|
856
|
+
logger$4(`[tryLocalBlobUrlFromTxId] ${imageFilePath} does not exist`);
|
|
857
|
+
return undefined;
|
|
858
|
+
}
|
|
859
|
+
const buffer = await BaseFileManager.readFileAsBuffer(imageFilePath);
|
|
860
|
+
let arrayBuffer;
|
|
861
|
+
if (buffer instanceof Blob) {
|
|
862
|
+
arrayBuffer = await buffer.arrayBuffer();
|
|
863
|
+
}
|
|
864
|
+
else if (buffer instanceof ArrayBuffer) {
|
|
865
|
+
arrayBuffer = buffer;
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
869
|
+
if (sliced instanceof SharedArrayBuffer) {
|
|
870
|
+
const uint8Array = new Uint8Array(sliced);
|
|
871
|
+
arrayBuffer = uint8Array.buffer.slice(0);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
arrayBuffer = sliced;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const uint = new Uint8Array(arrayBuffer);
|
|
878
|
+
const imageBlob = new Blob([uint]);
|
|
879
|
+
return URL.createObjectURL(imageBlob);
|
|
880
|
+
}
|
|
881
|
+
async function resolveTxId(txId) {
|
|
882
|
+
const local = await tryLocalBlobUrlFromTxId(txId);
|
|
883
|
+
if (local) {
|
|
884
|
+
return { status: 'ready', href: local, source: 'localBlob' };
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
status: 'ready',
|
|
888
|
+
href: BaseArweaveClient.getRawUrl(txId),
|
|
889
|
+
source: 'gateway',
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Resolve a classified media reference to a display URL (https/blob/data), using local files when present.
|
|
894
|
+
*/
|
|
895
|
+
async function resolveMediaRef(raw, options) {
|
|
896
|
+
const classification = classifyMediaRef(raw, options);
|
|
897
|
+
switch (classification.kind) {
|
|
898
|
+
case 'empty':
|
|
899
|
+
return { status: 'empty' };
|
|
900
|
+
case 'url':
|
|
901
|
+
return {
|
|
902
|
+
status: 'ready',
|
|
903
|
+
href: classification.href,
|
|
904
|
+
source: 'direct',
|
|
905
|
+
};
|
|
906
|
+
case 'nonMediaUrl':
|
|
907
|
+
return {
|
|
908
|
+
status: 'unresolved',
|
|
909
|
+
reason: 'non_media_url',
|
|
910
|
+
classification,
|
|
911
|
+
};
|
|
912
|
+
case 'seedLocalId':
|
|
913
|
+
return {
|
|
914
|
+
status: 'unresolved',
|
|
915
|
+
reason: 'seed_local_id_not_portable',
|
|
916
|
+
classification,
|
|
917
|
+
};
|
|
918
|
+
case 'unknown':
|
|
919
|
+
return {
|
|
920
|
+
status: 'unresolved',
|
|
921
|
+
reason: 'unknown_ref',
|
|
922
|
+
classification,
|
|
923
|
+
};
|
|
924
|
+
case 'arweaveTxId':
|
|
925
|
+
return resolveTxId(classification.txId);
|
|
926
|
+
case 'seedUid': {
|
|
927
|
+
const appDb = BaseDb.getAppDb();
|
|
928
|
+
if (!appDb) {
|
|
929
|
+
return {
|
|
930
|
+
status: 'unresolved',
|
|
931
|
+
reason: 'seed_uid_requires_app_db',
|
|
932
|
+
classification,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
const storageTransactionId = await getStorageTransactionIdForSeedUid(classification.uid);
|
|
936
|
+
if (!storageTransactionId) {
|
|
937
|
+
return {
|
|
938
|
+
status: 'unresolved',
|
|
939
|
+
reason: 'seed_uid_no_storage_transaction',
|
|
940
|
+
classification,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
return resolveTxId(storageTransactionId);
|
|
944
|
+
}
|
|
945
|
+
default:
|
|
946
|
+
return {
|
|
947
|
+
status: 'unresolved',
|
|
948
|
+
reason: 'unknown_ref',
|
|
949
|
+
classification,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Read a string field from a parsed item, trying camelCase and snake_case keys.
|
|
955
|
+
*/
|
|
956
|
+
function getFeedItemStringField(item, key) {
|
|
957
|
+
const direct = coalesceStringField(item, key);
|
|
958
|
+
if (direct)
|
|
959
|
+
return direct;
|
|
960
|
+
const snake = key.replace(/[A-Z]/g, (l) => `_${l.toLowerCase()}`);
|
|
961
|
+
if (snake !== key) {
|
|
962
|
+
const alt = coalesceStringField(item, snake);
|
|
963
|
+
if (alt)
|
|
964
|
+
return alt;
|
|
965
|
+
}
|
|
966
|
+
const parts = key.split('_');
|
|
967
|
+
if (parts.length > 1) {
|
|
968
|
+
const camel = parts[0] + parts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
|
|
969
|
+
const alt2 = coalesceStringField(item, camel);
|
|
970
|
+
if (alt2)
|
|
971
|
+
return alt2;
|
|
972
|
+
}
|
|
973
|
+
return '';
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Apply a field manifest to a plain feed item: classify media/file fields; pass through html/text.
|
|
977
|
+
*/
|
|
978
|
+
function normalizeFeedItemFields(item, manifest) {
|
|
979
|
+
const out = {};
|
|
980
|
+
for (const [fieldKey, descriptor] of Object.entries(manifest)) {
|
|
981
|
+
const raw = getFeedItemStringField(item, fieldKey);
|
|
982
|
+
if (!raw) {
|
|
983
|
+
out[fieldKey] = undefined;
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (descriptor.role === 'html') {
|
|
987
|
+
out[fieldKey] = { role: 'html', raw };
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
if (descriptor.role === 'text') {
|
|
991
|
+
out[fieldKey] = { role: 'text', raw };
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
const classification = classifyMediaRef(raw, { treatAs: descriptor.treatAs });
|
|
995
|
+
out[fieldKey] = {
|
|
996
|
+
role: descriptor.role,
|
|
997
|
+
raw,
|
|
998
|
+
classification,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
return out;
|
|
1002
|
+
}
|
|
216
1003
|
|
|
217
1004
|
class SeedEventEmitter {
|
|
218
1005
|
constructor() {
|
|
@@ -254,18 +1041,39 @@ class SeedEventEmitter {
|
|
|
254
1041
|
this.listeners.delete(event);
|
|
255
1042
|
return this;
|
|
256
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Calls every listener. A listener that throws is logged and skipped: the others still
|
|
1046
|
+
* run, and the error never reaches the SDK code that emitted the event (which may be
|
|
1047
|
+
* midway through a save).
|
|
1048
|
+
*/
|
|
257
1049
|
emit(event, ...args) {
|
|
258
1050
|
const set = this.listeners.get(event);
|
|
259
1051
|
if (!set || set.size === 0)
|
|
260
1052
|
return false;
|
|
261
1053
|
for (const listener of [...set]) {
|
|
262
|
-
|
|
1054
|
+
try {
|
|
1055
|
+
listener(...args);
|
|
1056
|
+
}
|
|
1057
|
+
catch (error) {
|
|
1058
|
+
console.error(`[seed] listener for "${String(event)}" threw:`, error);
|
|
1059
|
+
}
|
|
263
1060
|
}
|
|
264
1061
|
return true;
|
|
265
1062
|
}
|
|
266
1063
|
}
|
|
267
1064
|
const eventEmitter = new SeedEventEmitter();
|
|
268
1065
|
|
|
1066
|
+
let refresher;
|
|
1067
|
+
function setCachedPropertyRefresher(fn) {
|
|
1068
|
+
refresher = fn;
|
|
1069
|
+
}
|
|
1070
|
+
/** Give cached ModelProperty instances of these properties their rows' current values. */
|
|
1071
|
+
async function refreshCachedProperties(propertyFileIds) {
|
|
1072
|
+
if (!refresher || propertyFileIds.length === 0)
|
|
1073
|
+
return;
|
|
1074
|
+
await refresher(propertyFileIds);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
269
1077
|
/**
|
|
270
1078
|
* Detect SQLite UNIQUE constraint failures across better-sqlite3 and drizzle/wasm wrappers.
|
|
271
1079
|
* Drizzle often surfaces `Failed query: insert into...` without "UNIQUE constraint" on message,
|
|
@@ -406,7 +1214,7 @@ const modelNamesForNameOrType = async (nameOrType, db) => {
|
|
|
406
1214
|
const rows = (await db.selectDistinct({ name: models.name }).from(models));
|
|
407
1215
|
return rows
|
|
408
1216
|
.map((r) => r.name)
|
|
409
|
-
.filter((name) => name === nameOrType || name === pascal || toSnakeCase(name) === nameOrType);
|
|
1217
|
+
.filter((name) => name === nameOrType || name === pascal || toSnakeCase$1(name) === nameOrType);
|
|
410
1218
|
};
|
|
411
1219
|
/**
|
|
412
1220
|
* resolveModelRecord for a model name **or** an EAS model type (snake_case model name, what sync
|
|
@@ -619,7 +1427,7 @@ function linkModelToSchema(db, modelId, schemaId) {
|
|
|
619
1427
|
return current;
|
|
620
1428
|
}
|
|
621
1429
|
|
|
622
|
-
const logger = debug('seedSdk:helpers:db');
|
|
1430
|
+
const logger$3 = debug('seedSdk:helpers:db');
|
|
623
1431
|
/**
|
|
624
1432
|
* The `properties` storage columns for a property definition, in either the internal shape
|
|
625
1433
|
* (`storageType`, `localStorageDir`, `filenameSuffix`) or the schema-file shape
|
|
@@ -725,7 +1533,7 @@ async function getItemStoragePropertiesForModel(modelNameOrType, scope = {}) {
|
|
|
725
1533
|
async function migrateMetadataForPropertyRename(modelName, oldPropertyName, newPropertyName, propertySchemaFileId, scope = {}) {
|
|
726
1534
|
const db = BaseDb.getAppDb();
|
|
727
1535
|
if (!db) {
|
|
728
|
-
logger('[migrateMetadataForPropertyRename] Database not available, skipping');
|
|
1536
|
+
logger$3('[migrateMetadataForPropertyRename] Database not available, skipping');
|
|
729
1537
|
return 0;
|
|
730
1538
|
}
|
|
731
1539
|
// Find the property row by schemaFileId (preferred) or by name
|
|
@@ -737,7 +1545,7 @@ async function migrateMetadataForPropertyRename(modelName, oldPropertyName, newP
|
|
|
737
1545
|
propertyId = await getPropertyIdForModelAndName(modelName, oldPropertyName, scope);
|
|
738
1546
|
}
|
|
739
1547
|
if (propertyId == null) {
|
|
740
|
-
logger(`[migrateMetadataForPropertyRename] Property not found: ${modelName}.${oldPropertyName}, skipping`);
|
|
1548
|
+
logger$3(`[migrateMetadataForPropertyRename] Property not found: ${modelName}.${oldPropertyName}, skipping`);
|
|
741
1549
|
return 0;
|
|
742
1550
|
}
|
|
743
1551
|
// Primary path: update metadata by propertyId
|
|
@@ -753,7 +1561,7 @@ async function migrateMetadataForPropertyRename(modelName, oldPropertyName, newP
|
|
|
753
1561
|
.where(eq(metadata.propertyId, propertyId));
|
|
754
1562
|
}
|
|
755
1563
|
// Fallback: update metadata with null propertyId (legacy rows) by propertyName + modelType
|
|
756
|
-
const modelType = toSnakeCase(modelName);
|
|
1564
|
+
const modelType = toSnakeCase$1(modelName);
|
|
757
1565
|
const toUpdateByFallback = await db
|
|
758
1566
|
.select({ localId: metadata.localId })
|
|
759
1567
|
.from(metadata)
|
|
@@ -766,11 +1574,11 @@ async function migrateMetadataForPropertyRename(modelName, oldPropertyName, newP
|
|
|
766
1574
|
.where(and(eq(metadata.propertyName, oldPropertyName), eq(metadata.modelType, modelType), isNull(metadata.propertyId)));
|
|
767
1575
|
}
|
|
768
1576
|
if (updatedByFallback > 0) {
|
|
769
|
-
logger(`[migrateMetadataForPropertyRename] Fallback updated ${updatedByFallback} rows (propertyId was null)`);
|
|
1577
|
+
logger$3(`[migrateMetadataForPropertyRename] Fallback updated ${updatedByFallback} rows (propertyId was null)`);
|
|
770
1578
|
}
|
|
771
1579
|
const total = updatedByPropertyId + updatedByFallback;
|
|
772
1580
|
if (total > 0) {
|
|
773
|
-
logger(`[migrateMetadataForPropertyRename] Migrated ${total} metadata rows for ${modelName}.${oldPropertyName} -> ${newPropertyName}`);
|
|
1581
|
+
logger$3(`[migrateMetadataForPropertyRename] Migrated ${total} metadata rows for ${modelName}.${oldPropertyName} -> ${newPropertyName}`);
|
|
774
1582
|
}
|
|
775
1583
|
return total;
|
|
776
1584
|
}
|
|
@@ -919,7 +1727,7 @@ const findOrCreateModelRecord = async (db, modelName, modelFileId, schemaId) =>
|
|
|
919
1727
|
.set({ schemaFileId: modelFileId, isEdited: false })
|
|
920
1728
|
.where(eq(models.id, chosen.id));
|
|
921
1729
|
chosen = { ...chosen, schemaFileId: modelFileId, isEdited: false };
|
|
922
|
-
logger(`Adopted existing model "${modelName}" (id: ${chosen.id}) with schemaFileId "${modelFileId}"`);
|
|
1730
|
+
logger$3(`Adopted existing model "${modelName}" (id: ${chosen.id}) with schemaFileId "${modelFileId}"`);
|
|
923
1731
|
}
|
|
924
1732
|
if (chosen) {
|
|
925
1733
|
return chosen;
|
|
@@ -1190,7 +1998,7 @@ const addSchemaToDb = async (schema, schemaFileId, schemaData, isDraft, isEdited
|
|
|
1190
1998
|
.limit(1);
|
|
1191
1999
|
if (draftSchemas.length > 0) {
|
|
1192
2000
|
// Found a draft - we'll update it to non-draft below
|
|
1193
|
-
logger(`Found draft schema "${schema.name}" with schemaFileId "${draftSchemas[0].schemaFileId}", will update to non-draft with schemaFileId "${schemaFileId}"`);
|
|
2001
|
+
logger$3(`Found draft schema "${schema.name}" with schemaFileId "${draftSchemas[0].schemaFileId}", will update to non-draft with schemaFileId "${schemaFileId}"`);
|
|
1194
2002
|
existingSchemas = draftSchemas;
|
|
1195
2003
|
}
|
|
1196
2004
|
}
|
|
@@ -1210,11 +2018,11 @@ const addSchemaToDb = async (schema, schemaFileId, schemaData, isDraft, isEdited
|
|
|
1210
2018
|
!existingSchemas[0].schemaFileId ||
|
|
1211
2019
|
(isDraft === false && existingSchemas[0].isDraft === true);
|
|
1212
2020
|
if (shouldUpdate) {
|
|
1213
|
-
logger(`Updating schemaFileId from "${existingSchemas[0].schemaFileId}" to "${schemaFileId}" for schema "${schema.name}"`);
|
|
2021
|
+
logger$3(`Updating schemaFileId from "${existingSchemas[0].schemaFileId}" to "${schemaFileId}" for schema "${schema.name}"`);
|
|
1214
2022
|
updates.schemaFileId = schemaFileId;
|
|
1215
2023
|
}
|
|
1216
2024
|
else {
|
|
1217
|
-
logger(`Preserving existing schemaFileId "${existingSchemas[0].schemaFileId}" for non-draft schema "${schema.name}" (new schemaFileId "${schemaFileId}" would overwrite it)`);
|
|
2025
|
+
logger$3(`Preserving existing schemaFileId "${existingSchemas[0].schemaFileId}" for non-draft schema "${schema.name}" (new schemaFileId "${schemaFileId}" would overwrite it)`);
|
|
1218
2026
|
}
|
|
1219
2027
|
}
|
|
1220
2028
|
// Always update name if it's different (handles name changes)
|
|
@@ -1241,16 +2049,16 @@ const addSchemaToDb = async (schema, schemaFileId, schemaData, isDraft, isEdited
|
|
|
1241
2049
|
updates.updatedAt = schema.updatedAt;
|
|
1242
2050
|
}
|
|
1243
2051
|
if (Object.keys(updates).length > 0) {
|
|
1244
|
-
logger(`Applying updates to schema "${schema.name}" (id: ${existingSchemas[0].id}):`, Object.keys(updates));
|
|
2052
|
+
logger$3(`Applying updates to schema "${schema.name}" (id: ${existingSchemas[0].id}):`, Object.keys(updates));
|
|
1245
2053
|
await db
|
|
1246
2054
|
.update(schemas)
|
|
1247
2055
|
.set(updates)
|
|
1248
2056
|
.where(eq(schemas.id, existingSchemas[0].id));
|
|
1249
2057
|
const updated = { ...existingSchemas[0], ...updates };
|
|
1250
|
-
logger(`Schema "${schema.name}" updated. New schemaFileId: "${updated.schemaFileId}", isDraft: ${updated.isDraft}`);
|
|
2058
|
+
logger$3(`Schema "${schema.name}" updated. New schemaFileId: "${updated.schemaFileId}", isDraft: ${updated.isDraft}`);
|
|
1251
2059
|
return updated;
|
|
1252
2060
|
}
|
|
1253
|
-
logger(`No updates needed for schema "${schema.name}" (id: ${existingSchemas[0].id})`);
|
|
2061
|
+
logger$3(`No updates needed for schema "${schema.name}" (id: ${existingSchemas[0].id})`);
|
|
1254
2062
|
return existingSchemas[0];
|
|
1255
2063
|
}
|
|
1256
2064
|
const newSchema = await db.insert(schemas).values({
|
|
@@ -1286,9 +2094,7 @@ const renameModelInDb = async (oldName, newName, schemaNameOrId) => {
|
|
|
1286
2094
|
if (!db) {
|
|
1287
2095
|
throw new Error('Database not found');
|
|
1288
2096
|
}
|
|
1289
|
-
const
|
|
1290
|
-
const _ns_0 = _mod_0.Q;
|
|
1291
|
-
const { schemas: schemasTable } = _ns_0;
|
|
2097
|
+
const { schemas: schemasTable } = await Promise.resolve().then(function () { return SchemaSchema; });
|
|
1292
2098
|
// Find the model by old name, optionally scoped by schema
|
|
1293
2099
|
let existingModels;
|
|
1294
2100
|
if (schemaNameOrId !== undefined) {
|
|
@@ -1435,7 +2241,7 @@ modelId) {
|
|
|
1435
2241
|
// Compare database value with schema file value
|
|
1436
2242
|
// If they differ, the property has been edited
|
|
1437
2243
|
if (dbProperty.dataType !== schemaFileValue.dataType) {
|
|
1438
|
-
logger(`Property ${modelName}:${propertyName} has been edited (dataType differs: DB=${dbProperty.dataType}, Schema=${schemaFileValue.dataType})`);
|
|
2244
|
+
logger$3(`Property ${modelName}:${propertyName} has been edited (dataType differs: DB=${dbProperty.dataType}, Schema=${schemaFileValue.dataType})`);
|
|
1439
2245
|
return true;
|
|
1440
2246
|
}
|
|
1441
2247
|
// Check refModelId if it's a relation
|
|
@@ -1444,19 +2250,19 @@ modelId) {
|
|
|
1444
2250
|
if (refModelRecord) {
|
|
1445
2251
|
const expectedRefModelId = refModelRecord.id;
|
|
1446
2252
|
if (dbProperty.refModelId !== expectedRefModelId) {
|
|
1447
|
-
logger(`Property ${modelName}:${propertyName} has been edited (refModelId differs)`);
|
|
2253
|
+
logger$3(`Property ${modelName}:${propertyName} has been edited (refModelId differs)`);
|
|
1448
2254
|
return true;
|
|
1449
2255
|
}
|
|
1450
2256
|
}
|
|
1451
2257
|
}
|
|
1452
2258
|
else if (dbProperty.refModelId !== null) {
|
|
1453
2259
|
// Schema file has no ref, but DB has one
|
|
1454
|
-
logger(`Property ${modelName}:${propertyName} has been edited (refModelId differs)`);
|
|
2260
|
+
logger$3(`Property ${modelName}:${propertyName} has been edited (refModelId differs)`);
|
|
1455
2261
|
return true;
|
|
1456
2262
|
}
|
|
1457
2263
|
// Check refValueType
|
|
1458
2264
|
if (dbProperty.refValueType !== (schemaFileValue.refValueType || null)) {
|
|
1459
|
-
logger(`Property ${modelName}:${propertyName} has been edited (refValueType differs)`);
|
|
2265
|
+
logger$3(`Property ${modelName}:${propertyName} has been edited (refValueType differs)`);
|
|
1460
2266
|
return true;
|
|
1461
2267
|
}
|
|
1462
2268
|
// Check required (for Relation/Image properties)
|
|
@@ -1464,7 +2270,7 @@ modelId) {
|
|
|
1464
2270
|
const schemaRequired = !!schemaFileValue.required;
|
|
1465
2271
|
const dbRequired = dbProperty.required === true;
|
|
1466
2272
|
if (dbRequired !== schemaRequired) {
|
|
1467
|
-
logger(`Property ${modelName}:${propertyName} has been edited (required differs: DB=${dbRequired}, Schema=${schemaRequired})`);
|
|
2273
|
+
logger$3(`Property ${modelName}:${propertyName} has been edited (required differs: DB=${dbRequired}, Schema=${schemaRequired})`);
|
|
1468
2274
|
return true;
|
|
1469
2275
|
}
|
|
1470
2276
|
}
|
|
@@ -1480,7 +2286,7 @@ modelId) {
|
|
|
1480
2286
|
if (dbStorage.storageType !== fileStorage.storageType ||
|
|
1481
2287
|
dbStorage.localStorageDir !== fileStorage.localStorageDir ||
|
|
1482
2288
|
dbStorage.filenameSuffix !== fileStorage.filenameSuffix) {
|
|
1483
|
-
logger(`Property ${modelName}:${propertyName} has been edited (storage settings differ)`);
|
|
2289
|
+
logger$3(`Property ${modelName}:${propertyName} has been edited (storage settings differ)`);
|
|
1484
2290
|
return true;
|
|
1485
2291
|
}
|
|
1486
2292
|
}
|
|
@@ -1492,7 +2298,7 @@ modelId) {
|
|
|
1492
2298
|
}
|
|
1493
2299
|
catch (error) {
|
|
1494
2300
|
// If we can't check, assume it's not edited to be safe
|
|
1495
|
-
logger('Error checking if property is edited:', error);
|
|
2301
|
+
logger$3('Error checking if property is edited:', error);
|
|
1496
2302
|
return false;
|
|
1497
2303
|
}
|
|
1498
2304
|
}
|
|
@@ -1517,11 +2323,11 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1517
2323
|
// SchemaType includes id, so check if it's set
|
|
1518
2324
|
if (schema.id !== null && schema.id !== undefined) {
|
|
1519
2325
|
schemaRecord = schema;
|
|
1520
|
-
logger(`Using existing schema record with id: ${schemaRecord.id}`);
|
|
2326
|
+
logger$3(`Using existing schema record with id: ${schemaRecord.id}`);
|
|
1521
2327
|
}
|
|
1522
2328
|
else {
|
|
1523
2329
|
schemaRecord = await addSchemaToDb(schema, schemaFileData?.schemaFileId);
|
|
1524
|
-
logger(`Created/found schema record with id: ${schemaRecord.id}`);
|
|
2330
|
+
logger$3(`Created/found schema record with id: ${schemaRecord.id}`);
|
|
1525
2331
|
}
|
|
1526
2332
|
}
|
|
1527
2333
|
// Handle model renames first if provided
|
|
@@ -1534,6 +2340,8 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1534
2340
|
// Search for all models by schemaFileId (preferred) or name and create them if they don't exist
|
|
1535
2341
|
const modelRecords = new Map();
|
|
1536
2342
|
const modelProcessingErrors = [];
|
|
2343
|
+
// Property rows this call changed, so cached ModelProperty instances can be given the new values
|
|
2344
|
+
const updatedPropertyFileIds = [];
|
|
1537
2345
|
for (const [modelName, modelClass] of Object.entries(models$1)) {
|
|
1538
2346
|
try {
|
|
1539
2347
|
const modelFileId = schemaFileData?.modelFileIds?.get(modelName);
|
|
@@ -1547,7 +2355,7 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1547
2355
|
.where(eq(models.schemaFileId, modelFileId))
|
|
1548
2356
|
.limit(1);
|
|
1549
2357
|
if (existingWithFileId.length > 0 && existingWithFileId[0].id !== modelRecord.id) {
|
|
1550
|
-
logger(`WARNING: Model "${modelName}" (id: ${modelRecord.id}) conflicts with existing model "${existingWithFileId[0].name}" (id: ${existingWithFileId[0].id}) both trying to use schemaFileId "${modelFileId}"`);
|
|
2358
|
+
logger$3(`WARNING: Model "${modelName}" (id: ${modelRecord.id}) conflicts with existing model "${existingWithFileId[0].name}" (id: ${existingWithFileId[0].id}) both trying to use schemaFileId "${modelFileId}"`);
|
|
1551
2359
|
modelRecord = existingWithFileId[0];
|
|
1552
2360
|
}
|
|
1553
2361
|
else if (!modelRecord.schemaFileId) {
|
|
@@ -1575,8 +2383,14 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1575
2383
|
const pendingProperties = modelContext._pendingPropertyDefinitions || {};
|
|
1576
2384
|
// If context properties are empty, try modelClass.properties (for backward compatibility)
|
|
1577
2385
|
// but convert ModelProperty instances to object format
|
|
2386
|
+
// Property definitions read from the schema file win: a cached Model keeps the properties it
|
|
2387
|
+
// was created with, so after a rename its context still has the old name.
|
|
2388
|
+
const filePropertyDefinitions = schemaFileData?.propertyDefinitions?.get(modelName);
|
|
1578
2389
|
let schema = {};
|
|
1579
|
-
if (
|
|
2390
|
+
if (filePropertyDefinitions) {
|
|
2391
|
+
schema = filePropertyDefinitions;
|
|
2392
|
+
}
|
|
2393
|
+
else if (Object.keys(contextProperties).length > 0) {
|
|
1580
2394
|
schema = contextProperties;
|
|
1581
2395
|
}
|
|
1582
2396
|
else if (Object.keys(pendingProperties).length > 0) {
|
|
@@ -1600,14 +2414,13 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1600
2414
|
}
|
|
1601
2415
|
// Get property schemaFileId if available
|
|
1602
2416
|
let propertyFileId = schemaFileData?.propertyFileIds?.get(modelName)?.get(propertyName);
|
|
2417
|
+
const propertyFileIdGenerated = !propertyFileId;
|
|
1603
2418
|
// If no propertyFileId from map, generate a random ID
|
|
1604
2419
|
// IDs should be generated in the import process before calling addModelsToDb
|
|
1605
2420
|
if (!propertyFileId) {
|
|
1606
|
-
const
|
|
1607
|
-
const _ns_1 = _mod_1.P;
|
|
1608
|
-
const { generateId } = _ns_1;
|
|
2421
|
+
const { generateId } = await Promise.resolve().then(function () { return generateId$1; });
|
|
1609
2422
|
propertyFileId = generateId();
|
|
1610
|
-
logger(`Generated propertyFileId "${propertyFileId}" for property "${modelName}:${propertyName}" (not found in propertyFileIds map)`);
|
|
2423
|
+
logger$3(`Generated propertyFileId "${propertyFileId}" for property "${modelName}:${propertyName}" (not found in propertyFileIds map)`);
|
|
1611
2424
|
}
|
|
1612
2425
|
// First, try to find by schemaFileId (preferred for change tracking)
|
|
1613
2426
|
let existingProperty;
|
|
@@ -1674,7 +2487,7 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1674
2487
|
if (potentialMatches.length > 0) {
|
|
1675
2488
|
const matchedProperty = potentialMatches[0];
|
|
1676
2489
|
existingProperty = matchedProperty;
|
|
1677
|
-
logger(`Found renamed property: ${modelName}:${matchedProperty.name} -> ${propertyName} (matched by characteristics)`);
|
|
2490
|
+
logger$3(`Found renamed property: ${modelName}:${matchedProperty.name} -> ${propertyName} (matched by characteristics)`);
|
|
1678
2491
|
}
|
|
1679
2492
|
}
|
|
1680
2493
|
if (existingProperty) {
|
|
@@ -1695,7 +2508,7 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1695
2508
|
}, modelRecord.id);
|
|
1696
2509
|
if (isPropertyEdited) {
|
|
1697
2510
|
// Property has been locally edited - skip update to preserve local changes
|
|
1698
|
-
logger(`Skipping update for property ${modelName}:${existingProperty.name} -> ${propertyName} - it has been locally edited and will be preserved`);
|
|
2511
|
+
logger$3(`Skipping update for property ${modelName}:${existingProperty.name} -> ${propertyName} - it has been locally edited and will be preserved`);
|
|
1699
2512
|
continue;
|
|
1700
2513
|
}
|
|
1701
2514
|
// Property exists (possibly with different name) - update it with new values from schema file
|
|
@@ -1703,6 +2516,10 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1703
2516
|
// Ensure schemaFileId is updated if we have it
|
|
1704
2517
|
// Preserve existing isEdited value if it was true (local edits take precedence)
|
|
1705
2518
|
const updateData = { ...propertyData };
|
|
2519
|
+
// A generated id must not replace the one the row already has from its schema file
|
|
2520
|
+
if (propertyFileIdGenerated && existingProperty.schemaFileId) {
|
|
2521
|
+
updateData.schemaFileId = existingProperty.schemaFileId;
|
|
2522
|
+
}
|
|
1706
2523
|
if (existingProperty.isEdited === true) {
|
|
1707
2524
|
// Preserve isEdited = true if property was locally edited
|
|
1708
2525
|
delete updateData.isEdited;
|
|
@@ -1726,6 +2543,9 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1726
2543
|
.update(properties)
|
|
1727
2544
|
.set(updateData)
|
|
1728
2545
|
.where(eq(properties.id, existingProperty.id));
|
|
2546
|
+
const updatedFileId = updateData.schemaFileId ?? existingProperty.schemaFileId;
|
|
2547
|
+
if (updatedFileId)
|
|
2548
|
+
updatedPropertyFileIds.push(updatedFileId);
|
|
1729
2549
|
}
|
|
1730
2550
|
}
|
|
1731
2551
|
else {
|
|
@@ -1746,18 +2566,18 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1746
2566
|
if (existingByFileId.length > 0) {
|
|
1747
2567
|
const found = existingByFileId[0];
|
|
1748
2568
|
matchedDbPropertyIds.add(found.id);
|
|
1749
|
-
logger(`Property ${modelName}:${propertyName} already exists by schemaFileId "${propertyFileId}" (id: ${found.id}), skipping insert`);
|
|
2569
|
+
logger$3(`Property ${modelName}:${propertyName} already exists by schemaFileId "${propertyFileId}" (id: ${found.id}), skipping insert`);
|
|
1750
2570
|
continue;
|
|
1751
2571
|
}
|
|
1752
2572
|
}
|
|
1753
|
-
logger(`Creating new property ${modelName}:${propertyName} with schemaFileId: ${propertyData.schemaFileId}`);
|
|
2573
|
+
logger$3(`Creating new property ${modelName}:${propertyName} with schemaFileId: ${propertyData.schemaFileId}`);
|
|
1754
2574
|
try {
|
|
1755
2575
|
await db.insert(properties).values(propertyDataWithIsEdited);
|
|
1756
2576
|
}
|
|
1757
2577
|
catch (insertError) {
|
|
1758
2578
|
// Treat UNIQUE constraint as success - property already exists (e.g. from concurrent addModelsToDb call)
|
|
1759
2579
|
if (isSqliteUniqueConstraintError(insertError)) {
|
|
1760
|
-
logger(`Property ${modelName}:${propertyName} already exists (UNIQUE constraint), treating as success`);
|
|
2580
|
+
logger$3(`Property ${modelName}:${propertyName} already exists (UNIQUE constraint), treating as success`);
|
|
1761
2581
|
}
|
|
1762
2582
|
else {
|
|
1763
2583
|
throw insertError;
|
|
@@ -1768,14 +2588,14 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1768
2588
|
}
|
|
1769
2589
|
catch (error) {
|
|
1770
2590
|
const errorMessage = error?.message || String(error);
|
|
1771
|
-
logger(`Error processing model "${modelName}": ${errorMessage}`);
|
|
2591
|
+
logger$3(`Error processing model "${modelName}": ${errorMessage}`);
|
|
1772
2592
|
modelProcessingErrors.push({ modelName, error: errorMessage });
|
|
1773
2593
|
// Continue processing other models even if one fails
|
|
1774
2594
|
}
|
|
1775
2595
|
}
|
|
1776
2596
|
if (modelProcessingErrors.length > 0) {
|
|
1777
2597
|
console.warn(`[addModelsToDb] Errors occurred while processing ${modelProcessingErrors.length} model(s):`, modelProcessingErrors.map(e => `${e.modelName}: ${e.error}`).join('; '));
|
|
1778
|
-
logger(`Errors processing models: ${modelProcessingErrors.map(e => e.modelName).join(', ')}`);
|
|
2598
|
+
logger$3(`Errors processing models: ${modelProcessingErrors.map(e => e.modelName).join(', ')}`);
|
|
1779
2599
|
}
|
|
1780
2600
|
// If schema was provided, create modelSchema join records to connect models to the schema
|
|
1781
2601
|
if (schemaRecord && schemaRecord.id) {
|
|
@@ -1783,7 +2603,7 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1783
2603
|
.filter(([, r]) => r.id)
|
|
1784
2604
|
.map(([name, r]) => ({ modelName: name, modelId: r.id }));
|
|
1785
2605
|
if (modelIds.length === 0) {
|
|
1786
|
-
logger('No model IDs to link, skipping schema linking');
|
|
2606
|
+
logger$3('No model IDs to link, skipping schema linking');
|
|
1787
2607
|
}
|
|
1788
2608
|
else {
|
|
1789
2609
|
// Single batch query: get all existing joins for this schema
|
|
@@ -1797,14 +2617,16 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1797
2617
|
for (const { modelName, modelId } of toInsert) {
|
|
1798
2618
|
if (!(await linkModelToSchema(db, modelId, schemaRecord.id)))
|
|
1799
2619
|
continue;
|
|
1800
|
-
logger(`Created join table entry for model ${modelName} (id: ${modelId}) to schema (id: ${schemaRecord.id})`);
|
|
2620
|
+
logger$3(`Created join table entry for model ${modelName} (id: ${modelId}) to schema (id: ${schemaRecord.id})`);
|
|
1801
2621
|
}
|
|
1802
2622
|
}
|
|
1803
2623
|
}
|
|
1804
2624
|
}
|
|
1805
2625
|
else if (schemaRecord && !schemaRecord.id) {
|
|
1806
|
-
logger(`Warning: schemaRecord provided but has no id, cannot create join table entries`);
|
|
2626
|
+
logger$3(`Warning: schemaRecord provided but has no id, cannot create join table entries`);
|
|
1807
2627
|
}
|
|
2628
|
+
// A cached instance is otherwise never re-read from its row (createById returns it as is)
|
|
2629
|
+
await refreshCachedProperties(updatedPropertyFileIds);
|
|
1808
2630
|
};
|
|
1809
2631
|
/**
|
|
1810
2632
|
* Loads models from the database for a given schema by querying the model_schemas join table.
|
|
@@ -1816,7 +2638,7 @@ const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) =>
|
|
|
1816
2638
|
const loadModelsFromDbForSchema = async (schemaId) => {
|
|
1817
2639
|
const db = BaseDb.getAppDb();
|
|
1818
2640
|
if (!db) {
|
|
1819
|
-
logger('Database not found, cannot load models from DB');
|
|
2641
|
+
logger$3('Database not found, cannot load models from DB');
|
|
1820
2642
|
return {};
|
|
1821
2643
|
}
|
|
1822
2644
|
try {
|
|
@@ -1885,11 +2707,11 @@ const loadModelsFromDbForSchema = async (schemaId) => {
|
|
|
1885
2707
|
// or reconstructed from schemaData if available
|
|
1886
2708
|
};
|
|
1887
2709
|
}
|
|
1888
|
-
logger(`Loaded ${Object.keys(models$1).length} models from database for schema ${schemaId}`);
|
|
2710
|
+
logger$3(`Loaded ${Object.keys(models$1).length} models from database for schema ${schemaId}`);
|
|
1889
2711
|
return models$1;
|
|
1890
2712
|
}
|
|
1891
2713
|
catch (error) {
|
|
1892
|
-
logger(`Error loading models from database for schema ${schemaId}:`, error);
|
|
2714
|
+
logger$3(`Error loading models from database for schema ${schemaId}:`, error);
|
|
1893
2715
|
return {};
|
|
1894
2716
|
}
|
|
1895
2717
|
};
|
|
@@ -2006,34 +2828,34 @@ const savePropertyToDb = async (property) => {
|
|
|
2006
2828
|
// Find existing property - try multiple strategies to handle name changes
|
|
2007
2829
|
// 1. First try by schemaFileId (most reliable - doesn't change when name changes)
|
|
2008
2830
|
let existingProperties = [];
|
|
2009
|
-
logger(`[savePropertyToDb] Looking for property ${property.modelName}:${property.name} (schemaFileId: ${schemaFileId}, originalName: ${property._originalValues?.name})`);
|
|
2831
|
+
logger$3(`[savePropertyToDb] Looking for property ${property.modelName}:${property.name} (schemaFileId: ${schemaFileId}, originalName: ${property._originalValues?.name})`);
|
|
2010
2832
|
if (schemaFileId) {
|
|
2011
2833
|
existingProperties = await db
|
|
2012
2834
|
.select()
|
|
2013
2835
|
.from(properties)
|
|
2014
2836
|
.where(and(eq(properties.schemaFileId, schemaFileId), eq(properties.modelId, modelRecord.id)))
|
|
2015
2837
|
.limit(1);
|
|
2016
|
-
logger(`[savePropertyToDb] Found ${existingProperties.length} properties by schemaFileId`);
|
|
2838
|
+
logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by schemaFileId`);
|
|
2017
2839
|
}
|
|
2018
2840
|
// 2. If not found by schemaFileId, try by original name (if name was changed)
|
|
2019
2841
|
if (existingProperties.length === 0 && property._originalValues?.name && property._originalValues.name !== property.name) {
|
|
2020
|
-
logger(`[savePropertyToDb] Trying to find by original name: ${property._originalValues.name}`);
|
|
2842
|
+
logger$3(`[savePropertyToDb] Trying to find by original name: ${property._originalValues.name}`);
|
|
2021
2843
|
existingProperties = await db
|
|
2022
2844
|
.select()
|
|
2023
2845
|
.from(properties)
|
|
2024
2846
|
.where(and(eq(properties.name, property._originalValues.name), eq(properties.modelId, modelRecord.id)))
|
|
2025
2847
|
.limit(1);
|
|
2026
|
-
logger(`[savePropertyToDb] Found ${existingProperties.length} properties by original name`);
|
|
2848
|
+
logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by original name`);
|
|
2027
2849
|
}
|
|
2028
2850
|
// 3. Fallback: try by current name
|
|
2029
2851
|
if (existingProperties.length === 0) {
|
|
2030
|
-
logger(`[savePropertyToDb] Trying to find by current name: ${property.name}`);
|
|
2852
|
+
logger$3(`[savePropertyToDb] Trying to find by current name: ${property.name}`);
|
|
2031
2853
|
existingProperties = await db
|
|
2032
2854
|
.select()
|
|
2033
2855
|
.from(properties)
|
|
2034
2856
|
.where(and(eq(properties.name, property.name), eq(properties.modelId, modelRecord.id)))
|
|
2035
2857
|
.limit(1);
|
|
2036
|
-
logger(`[savePropertyToDb] Found ${existingProperties.length} properties by current name`);
|
|
2858
|
+
logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by current name`);
|
|
2037
2859
|
}
|
|
2038
2860
|
// Prepare property data
|
|
2039
2861
|
const propertyData = {
|
|
@@ -2082,7 +2904,7 @@ const savePropertyToDb = async (property) => {
|
|
|
2082
2904
|
isEdited: true, // Mark as edited when saving changes
|
|
2083
2905
|
})
|
|
2084
2906
|
.where(eq(properties.id, existingProperty.id));
|
|
2085
|
-
logger(`Updated property ${property.modelName}:${property._originalValues?.name || 'unknown'} -> ${property.name} in database`);
|
|
2907
|
+
logger$3(`Updated property ${property.modelName}:${property._originalValues?.name || 'unknown'} -> ${property.name} in database`);
|
|
2086
2908
|
}
|
|
2087
2909
|
else {
|
|
2088
2910
|
// Property doesn't exist, create it
|
|
@@ -2092,12 +2914,12 @@ const savePropertyToDb = async (property) => {
|
|
|
2092
2914
|
...propertyData,
|
|
2093
2915
|
isEdited: true, // Runtime-created properties are edited
|
|
2094
2916
|
});
|
|
2095
|
-
logger(`Created property ${property.modelName}:${property.name} in database`);
|
|
2917
|
+
logger$3(`Created property ${property.modelName}:${property.name} in database`);
|
|
2096
2918
|
}
|
|
2097
2919
|
catch (insertError) {
|
|
2098
2920
|
// Treat UNIQUE constraint as success - property already exists (e.g. from concurrent addModelsToDb or race)
|
|
2099
2921
|
if (isSqliteUniqueConstraintError(insertError)) {
|
|
2100
|
-
logger(`Property ${property.modelName}:${property.name} already exists (UNIQUE constraint), treating as success`);
|
|
2922
|
+
logger$3(`Property ${property.modelName}:${property.name} already exists (UNIQUE constraint), treating as success`);
|
|
2101
2923
|
}
|
|
2102
2924
|
else {
|
|
2103
2925
|
throw insertError;
|
|
@@ -2214,7 +3036,7 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2214
3036
|
if (doubleCheck.length > 0) {
|
|
2215
3037
|
// Another process created it, use existing
|
|
2216
3038
|
modelId = doubleCheck[0].id;
|
|
2217
|
-
logger(`Model with schemaFileId "${modelFileId}" was created by another process, using existing record (id: ${modelId})`);
|
|
3039
|
+
logger$3(`Model with schemaFileId "${modelFileId}" was created by another process, using existing record (id: ${modelId})`);
|
|
2218
3040
|
}
|
|
2219
3041
|
else {
|
|
2220
3042
|
try {
|
|
@@ -2228,7 +3050,7 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2228
3050
|
catch (error) {
|
|
2229
3051
|
// Handle unique constraint violation
|
|
2230
3052
|
if (isSqliteUniqueConstraintError(error)) {
|
|
2231
|
-
logger(`Unique constraint violation for schemaFileId "${modelFileId}", attempting to find existing model`);
|
|
3053
|
+
logger$3(`Unique constraint violation for schemaFileId "${modelFileId}", attempting to find existing model`);
|
|
2232
3054
|
const existing = await db
|
|
2233
3055
|
.select()
|
|
2234
3056
|
.from(models)
|
|
@@ -2236,7 +3058,7 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2236
3058
|
.limit(1);
|
|
2237
3059
|
if (existing.length > 0) {
|
|
2238
3060
|
modelId = existing[0].id;
|
|
2239
|
-
logger(`Found existing model with schemaFileId "${modelFileId}" (id: ${modelId}) after constraint violation`);
|
|
3061
|
+
logger$3(`Found existing model with schemaFileId "${modelFileId}" (id: ${modelId}) after constraint violation`);
|
|
2240
3062
|
}
|
|
2241
3063
|
else {
|
|
2242
3064
|
throw error;
|
|
@@ -2292,9 +3114,7 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2292
3114
|
throw new Error(`Model with id ${modelId} does not exist in database. Cannot create join record.`);
|
|
2293
3115
|
}
|
|
2294
3116
|
// Verify schemaId exists and get name/fileId for invalidation broadcast
|
|
2295
|
-
const
|
|
2296
|
-
const _ns_2 = _mod_2.Q;
|
|
2297
|
-
const schemaSchemaMod = _ns_2;
|
|
3117
|
+
const schemaSchemaMod = await Promise.resolve().then(function () { return SchemaSchema; });
|
|
2298
3118
|
const { schemas: schemasTable } = schemaSchemaMod;
|
|
2299
3119
|
const schemaCheck = await db
|
|
2300
3120
|
.select({
|
|
@@ -2308,7 +3128,7 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2308
3128
|
if (schemaCheck.length === 0) {
|
|
2309
3129
|
throw new Error(`Schema with id ${data.schemaId} does not exist in database. Cannot create join record.`);
|
|
2310
3130
|
}
|
|
2311
|
-
logger(`Creating join record: modelId=${modelId}, schemaId=${data.schemaId} (both verified to exist)`);
|
|
3131
|
+
logger$3(`Creating join record: modelId=${modelId}, schemaId=${data.schemaId} (both verified to exist)`);
|
|
2312
3132
|
await linkModelToSchema(db, modelId, data.schemaId);
|
|
2313
3133
|
// Notify React useModels so it can invalidate; live query over join often doesn't re-run when model_schemas is inserted.
|
|
2314
3134
|
// Yield so the insert is visible to the refetch that will run when the broadcast is received.
|
|
@@ -2323,13 +3143,13 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2323
3143
|
}
|
|
2324
3144
|
catch (_) { }
|
|
2325
3145
|
}
|
|
2326
|
-
logger(`Successfully created join record for model ${data.modelName} (id: ${modelId}) to schema (id: ${data.schemaId})`);
|
|
3146
|
+
logger$3(`Successfully created join record for model ${data.modelName} (id: ${modelId}) to schema (id: ${data.schemaId})`);
|
|
2327
3147
|
}
|
|
2328
3148
|
catch (error) {
|
|
2329
3149
|
if (error?.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
|
|
2330
|
-
logger(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}"`);
|
|
2331
|
-
logger(`modelId: ${modelId}, schemaId: ${data.schemaId}`);
|
|
2332
|
-
logger(`Error details:`, error);
|
|
3150
|
+
logger$3(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}"`);
|
|
3151
|
+
logger$3(`modelId: ${modelId}, schemaId: ${data.schemaId}`);
|
|
3152
|
+
logger$3(`Error details:`, error);
|
|
2333
3153
|
// Re-throw with more context
|
|
2334
3154
|
throw new Error(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}" (modelId: ${modelId}, schemaId: ${data.schemaId}). ${error.message}`);
|
|
2335
3155
|
}
|
|
@@ -2349,7 +3169,7 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2349
3169
|
if (existingProps.length > 0 && existingProps[0].schemaFileId) {
|
|
2350
3170
|
// Use existing property's schemaFileId
|
|
2351
3171
|
propertyFileId = existingProps[0].schemaFileId;
|
|
2352
|
-
logger(`Using existing propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
|
|
3172
|
+
logger$3(`Using existing propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
|
|
2353
3173
|
}
|
|
2354
3174
|
else if (propData.id) {
|
|
2355
3175
|
// Use provided ID
|
|
@@ -2358,11 +3178,9 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2358
3178
|
else {
|
|
2359
3179
|
// Generate random propertyFileId
|
|
2360
3180
|
// IDs should be generated in the import process before calling writeModelToDb
|
|
2361
|
-
const
|
|
2362
|
-
const _ns_3 = _mod_3.P;
|
|
2363
|
-
const { generateId } = _ns_3;
|
|
3181
|
+
const { generateId } = await Promise.resolve().then(function () { return generateId$1; });
|
|
2364
3182
|
propertyFileId = generateId();
|
|
2365
|
-
logger(`Generated propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
|
|
3183
|
+
logger$3(`Generated propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
|
|
2366
3184
|
}
|
|
2367
3185
|
await writePropertyToDb(propertyFileId, {
|
|
2368
3186
|
modelId,
|
|
@@ -2371,7 +3189,7 @@ async function writeModelToDb(modelFileId, data) {
|
|
|
2371
3189
|
});
|
|
2372
3190
|
}
|
|
2373
3191
|
}
|
|
2374
|
-
logger(`Wrote model ${data.modelName} (${modelFileId}) to database`);
|
|
3192
|
+
logger$3(`Wrote model ${data.modelName} (${modelFileId}) to database`);
|
|
2375
3193
|
}
|
|
2376
3194
|
/**
|
|
2377
3195
|
* Write property to database
|
|
@@ -2446,7 +3264,7 @@ async function writePropertyToDb(propertyFileId, data, isEdited = false) {
|
|
|
2446
3264
|
.update(properties)
|
|
2447
3265
|
.set(propertyData)
|
|
2448
3266
|
.where(eq(properties.id, existing.id));
|
|
2449
|
-
logger(`Updated property ${data.name} (${propertyFileId}) in database`);
|
|
3267
|
+
logger$3(`Updated property ${data.name} (${propertyFileId}) in database`);
|
|
2450
3268
|
}
|
|
2451
3269
|
else {
|
|
2452
3270
|
// Property doesn't exist, create it
|
|
@@ -2462,17 +3280,17 @@ async function writePropertyToDb(propertyFileId, data, isEdited = false) {
|
|
|
2462
3280
|
.update(properties)
|
|
2463
3281
|
.set(propertyData)
|
|
2464
3282
|
.where(eq(properties.id, doubleCheck[0].id));
|
|
2465
|
-
logger(`Property with schemaFileId "${propertyFileId}" was created by another process, updated existing record`);
|
|
3283
|
+
logger$3(`Property with schemaFileId "${propertyFileId}" was created by another process, updated existing record`);
|
|
2466
3284
|
}
|
|
2467
3285
|
else {
|
|
2468
3286
|
try {
|
|
2469
3287
|
await db.insert(properties).values({ required: false, ...propertyData });
|
|
2470
|
-
logger(`Created property ${data.name} (${propertyFileId}) in database`);
|
|
3288
|
+
logger$3(`Created property ${data.name} (${propertyFileId}) in database`);
|
|
2471
3289
|
}
|
|
2472
3290
|
catch (error) {
|
|
2473
3291
|
// Handle unique constraint violation
|
|
2474
3292
|
if (isSqliteUniqueConstraintError(error)) {
|
|
2475
|
-
logger(`Unique constraint violation for property schemaFileId "${propertyFileId}", attempting to find existing property`);
|
|
3293
|
+
logger$3(`Unique constraint violation for property schemaFileId "${propertyFileId}", attempting to find existing property`);
|
|
2476
3294
|
const existing = await db
|
|
2477
3295
|
.select()
|
|
2478
3296
|
.from(properties)
|
|
@@ -2483,7 +3301,7 @@ async function writePropertyToDb(propertyFileId, data, isEdited = false) {
|
|
|
2483
3301
|
.update(properties)
|
|
2484
3302
|
.set(propertyData)
|
|
2485
3303
|
.where(eq(properties.id, existing[0].id));
|
|
2486
|
-
logger(`Found existing property with schemaFileId "${propertyFileId}" (id: ${existing[0].id}) after constraint violation, updated it`);
|
|
3304
|
+
logger$3(`Found existing property with schemaFileId "${propertyFileId}" (id: ${existing[0].id}) after constraint violation, updated it`);
|
|
2487
3305
|
}
|
|
2488
3306
|
else {
|
|
2489
3307
|
throw new Error(`Failed to create or find property "${data.name}" with schemaFileId "${propertyFileId}": ${error.message}`);
|
|
@@ -2507,9 +3325,7 @@ async function getSchemaId(schemaNameOrFileId) {
|
|
|
2507
3325
|
if (!db) {
|
|
2508
3326
|
throw new Error('Database not available');
|
|
2509
3327
|
}
|
|
2510
|
-
const
|
|
2511
|
-
const _ns_4 = _mod_4.Q;
|
|
2512
|
-
const schemaSchemaMod = _ns_4;
|
|
3328
|
+
const schemaSchemaMod = await Promise.resolve().then(function () { return SchemaSchema; });
|
|
2513
3329
|
const { schemas: schemasTable } = schemaSchemaMod;
|
|
2514
3330
|
const drizzleMod = await import('drizzle-orm');
|
|
2515
3331
|
const { eq, desc } = drizzleMod;
|
|
@@ -2545,9 +3361,7 @@ async function getSchemaIdByFileId(schemaFileId) {
|
|
|
2545
3361
|
if (!db) {
|
|
2546
3362
|
throw new Error('Database not available');
|
|
2547
3363
|
}
|
|
2548
|
-
const
|
|
2549
|
-
const _ns_5 = _mod_5.Q;
|
|
2550
|
-
const schemaSchemaMod = _ns_5;
|
|
3364
|
+
const schemaSchemaMod = await Promise.resolve().then(function () { return SchemaSchema; });
|
|
2551
3365
|
const { schemas: schemasTable } = schemaSchemaMod;
|
|
2552
3366
|
const drizzleMod = await import('drizzle-orm');
|
|
2553
3367
|
const { eq, desc } = drizzleMod;
|
|
@@ -2574,9 +3388,7 @@ async function getModelId(modelNameOrFileId, schemaNameOrId) {
|
|
|
2574
3388
|
if (!db) {
|
|
2575
3389
|
throw new Error('Database not available');
|
|
2576
3390
|
}
|
|
2577
|
-
const
|
|
2578
|
-
const _ns_6 = _mod_6.N;
|
|
2579
|
-
const modelSchemaMod = _ns_6;
|
|
3391
|
+
const modelSchemaMod = await Promise.resolve().then(function () { return ModelSchema; });
|
|
2580
3392
|
const { models: modelsTable } = modelSchemaMod;
|
|
2581
3393
|
const drizzleMod = await import('drizzle-orm');
|
|
2582
3394
|
const { eq, and, or } = drizzleMod;
|
|
@@ -2591,13 +3403,9 @@ async function getModelId(modelNameOrFileId, schemaNameOrId) {
|
|
|
2591
3403
|
[eq(modelsTable.name, modelNameOrFileId)];
|
|
2592
3404
|
// If schema is provided, narrow the search
|
|
2593
3405
|
if (schemaNameOrId !== undefined) {
|
|
2594
|
-
const
|
|
2595
|
-
const _ns_7 = _mod_7.R;
|
|
2596
|
-
const modelSchemaSchemaMod = _ns_7;
|
|
3406
|
+
const modelSchemaSchemaMod = await Promise.resolve().then(function () { return ModelSchemaSchema; });
|
|
2597
3407
|
const { modelSchemas } = modelSchemaSchemaMod;
|
|
2598
|
-
const
|
|
2599
|
-
const _ns_8 = _mod_8.Q;
|
|
2600
|
-
const schemaSchemaMod = _ns_8;
|
|
3408
|
+
const schemaSchemaMod = await Promise.resolve().then(function () { return SchemaSchema; });
|
|
2601
3409
|
const { schemas: schemasTable } = schemaSchemaMod;
|
|
2602
3410
|
if (typeof schemaNameOrId === 'number') {
|
|
2603
3411
|
// schemaNameOrId is schemaId
|
|
@@ -2642,9 +3450,7 @@ async function getModelIdByFileId(modelFileId) {
|
|
|
2642
3450
|
if (!db) {
|
|
2643
3451
|
throw new Error('Database not available');
|
|
2644
3452
|
}
|
|
2645
|
-
const
|
|
2646
|
-
const _ns_9 = _mod_9.N;
|
|
2647
|
-
const modelSchemaMod = _ns_9;
|
|
3453
|
+
const modelSchemaMod = await Promise.resolve().then(function () { return ModelSchema; });
|
|
2648
3454
|
const { models: modelsTable } = modelSchemaMod;
|
|
2649
3455
|
const drizzleMod = await import('drizzle-orm');
|
|
2650
3456
|
const { eq } = drizzleMod;
|
|
@@ -2690,5 +3496,1113 @@ var db = /*#__PURE__*/Object.freeze({
|
|
|
2690
3496
|
writePropertyToDb: writePropertyToDb
|
|
2691
3497
|
});
|
|
2692
3498
|
|
|
2693
|
-
|
|
2694
|
-
|
|
3499
|
+
const getMetadata = async (props) => {
|
|
3500
|
+
const appDb = BaseDb.getAppDb();
|
|
3501
|
+
let storageTransactionId;
|
|
3502
|
+
if (props) {
|
|
3503
|
+
storageTransactionId = props.storageTransactionId;
|
|
3504
|
+
}
|
|
3505
|
+
const whereClauses = [];
|
|
3506
|
+
if (storageTransactionId) {
|
|
3507
|
+
whereClauses.push(eq(metadata.propertyName, 'storageTransactionId'));
|
|
3508
|
+
whereClauses.push(eq(metadata.propertyValue, storageTransactionId));
|
|
3509
|
+
}
|
|
3510
|
+
const metadataRows = await appDb
|
|
3511
|
+
.select()
|
|
3512
|
+
.from(metadata)
|
|
3513
|
+
.where(and(...whereClauses));
|
|
3514
|
+
if (!metadataRows || metadataRows.length === 0) {
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
return metadataRows[0];
|
|
3518
|
+
};
|
|
3519
|
+
|
|
3520
|
+
const saveMetadata = async (metadataRecord, metadataValues) => {
|
|
3521
|
+
if (!metadataRecord.localId) {
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3524
|
+
const appDb = BaseDb.getAppDb();
|
|
3525
|
+
await appDb
|
|
3526
|
+
.update(metadata)
|
|
3527
|
+
.set({
|
|
3528
|
+
...metadataValues,
|
|
3529
|
+
updatedAt: Date.now(),
|
|
3530
|
+
})
|
|
3531
|
+
.where(eq(metadata.localId, metadataRecord.localId));
|
|
3532
|
+
};
|
|
3533
|
+
|
|
3534
|
+
const isNode = () => {
|
|
3535
|
+
return (typeof process !== 'undefined' &&
|
|
3536
|
+
process.versions != null &&
|
|
3537
|
+
process.versions.node != null);
|
|
3538
|
+
};
|
|
3539
|
+
const isBrowser = () => {
|
|
3540
|
+
return !isElectron() && typeof document !== 'undefined' && typeof window !== 'undefined';
|
|
3541
|
+
};
|
|
3542
|
+
const isElectron = () => {
|
|
3543
|
+
return typeof process !== 'undefined' && process.versions != null && process.versions.electron != null;
|
|
3544
|
+
};
|
|
3545
|
+
/**
|
|
3546
|
+
* Detects if code is running in an Electron renderer process.
|
|
3547
|
+
*
|
|
3548
|
+
* Uses multiple detection methods for robustness:
|
|
3549
|
+
* 1. Checks process.type === 'renderer' (most reliable)
|
|
3550
|
+
* 2. Falls back to checking if Electron is present and window/document exist
|
|
3551
|
+
*
|
|
3552
|
+
* @returns true if running in Electron renderer process, false otherwise
|
|
3553
|
+
*/
|
|
3554
|
+
const isElectronRenderer = () => {
|
|
3555
|
+
// Method 1: Check process.type (most reliable - official Electron API)
|
|
3556
|
+
if (typeof process !== 'undefined' &&
|
|
3557
|
+
process.type === 'renderer') {
|
|
3558
|
+
return true;
|
|
3559
|
+
}
|
|
3560
|
+
// Method 2: Fallback - Electron + browser-like environment
|
|
3561
|
+
// Renderer processes have window/document, main process doesn't (in modern Electron)
|
|
3562
|
+
if (isElectron() &&
|
|
3563
|
+
typeof window !== 'undefined' &&
|
|
3564
|
+
typeof document !== 'undefined') {
|
|
3565
|
+
return true;
|
|
3566
|
+
}
|
|
3567
|
+
return false;
|
|
3568
|
+
};
|
|
3569
|
+
/**
|
|
3570
|
+
* True where OPFS-backed Arweave file downloads should run: normal browsers and
|
|
3571
|
+
* Electron renderer. {@link isBrowser} is false for every Electron build (including renderer).
|
|
3572
|
+
*/
|
|
3573
|
+
const supportsOpfsFileDownloads = () => {
|
|
3574
|
+
return isBrowser() || isElectronRenderer();
|
|
3575
|
+
};
|
|
3576
|
+
|
|
3577
|
+
/**
|
|
3578
|
+
* Tag every Seed upload carries with the hex digest of its data. Producers: the SDK's
|
|
3579
|
+
* `getPublishUploadData` / `getPublishUploads` (and through them both the L1 transaction path and
|
|
3580
|
+
* the bundler / DataItem path in `@seedprotocol/publish`). Consumer: the files download
|
|
3581
|
+
* (`events/files/download.ts` and the download worker), which verifies downloaded bytes against it
|
|
3582
|
+
* and copies a verified value into `metadata.content_hash`.
|
|
3583
|
+
*/
|
|
3584
|
+
const CONTENT_SHA256_TAG = 'Content-SHA-256';
|
|
3585
|
+
const CONTENT_TYPE_TAG = 'Content-Type';
|
|
3586
|
+
/**
|
|
3587
|
+
* Lowercase hex SHA-256 of `data`: the value of the {@link CONTENT_SHA256_TAG} tag.
|
|
3588
|
+
*
|
|
3589
|
+
* Uses @noble/hashes rather than WebCrypto so it also works outside secure contexts (WebCrypto's
|
|
3590
|
+
* `crypto.subtle` is missing on plain-http origins); the digest is the same.
|
|
3591
|
+
*
|
|
3592
|
+
* In SDK 0.7.2 and earlier the L1 transaction path (`getPublishUploads`) wrote SHA3-256 under this tag name
|
|
3593
|
+
* while the bundler path wrote SHA-256. Uploads from that path still carry SHA3-256 digests; use
|
|
3594
|
+
* {@link contentSha256TagMatches} to check a tag value against bytes.
|
|
3595
|
+
*/
|
|
3596
|
+
const getContentSha256 = (data) => bytesToHex(sha256(data));
|
|
3597
|
+
/**
|
|
3598
|
+
* True when `tagValue` (a {@link CONTENT_SHA256_TAG} value read from Arweave) is the digest of
|
|
3599
|
+
* `data`. Accepts SHA-256 and, for uploads made by SDK 0.7.2 and earlier through the L1 transaction path,
|
|
3600
|
+
* SHA3-256.
|
|
3601
|
+
*/
|
|
3602
|
+
const contentSha256TagMatches = (data, tagValue) => {
|
|
3603
|
+
const expected = tagValue.trim().toLowerCase();
|
|
3604
|
+
if (!expected)
|
|
3605
|
+
return false;
|
|
3606
|
+
return getContentSha256(data) === expected || bytesToHex(sha3_256(data)) === expected;
|
|
3607
|
+
};
|
|
3608
|
+
/**
|
|
3609
|
+
* Tags for one upload, in the order every upload uses: content hash, content type, then any
|
|
3610
|
+
* configured extra tags (e.g. App-Name from `arweaveUploadTags`).
|
|
3611
|
+
*/
|
|
3612
|
+
const buildArweaveContentTags = (contentHash, contentType, extra) => {
|
|
3613
|
+
const tags = [];
|
|
3614
|
+
if (contentHash)
|
|
3615
|
+
tags.push({ name: CONTENT_SHA256_TAG, value: contentHash });
|
|
3616
|
+
if (contentType)
|
|
3617
|
+
tags.push({ name: CONTENT_TYPE_TAG, value: contentType });
|
|
3618
|
+
if (extra?.length)
|
|
3619
|
+
tags.push(...extra);
|
|
3620
|
+
return tags;
|
|
3621
|
+
};
|
|
3622
|
+
|
|
3623
|
+
/** Subdirectory of the files root that holds a storage seed's file, by its data type. */
|
|
3624
|
+
const getStorageSubdirForDataType = (dataType) => {
|
|
3625
|
+
switch (normalizeDataType(dataType ?? undefined)) {
|
|
3626
|
+
case 'Image':
|
|
3627
|
+
return 'images';
|
|
3628
|
+
case 'Html':
|
|
3629
|
+
return 'html';
|
|
3630
|
+
case 'Json':
|
|
3631
|
+
return 'json';
|
|
3632
|
+
case 'File':
|
|
3633
|
+
default:
|
|
3634
|
+
// Unknown types are generic files; `images/` is only for what the SDK knows is an image.
|
|
3635
|
+
return 'files';
|
|
3636
|
+
}
|
|
3637
|
+
};
|
|
3638
|
+
/** Absolute directory for a storage seed's file (`images/`, `files/`, `html/` or `json/` under the files root). */
|
|
3639
|
+
const getStorageDirForDataType = (dataType) => BaseFileManager.getFilesPath(getStorageSubdirForDataType(dataType));
|
|
3640
|
+
/**
|
|
3641
|
+
* Subdirectory (relative to the files root, no leading slash) for an ItemStorage property's file:
|
|
3642
|
+
* its model's `storage.path` (`localStorageDir`), else `files`.
|
|
3643
|
+
*/
|
|
3644
|
+
const getItemStorageSubdir = (localStorageDir) => (localStorageDir ?? '').replace(/^\/+/, '').replace(/\/+$/, '') || 'files';
|
|
3645
|
+
/**
|
|
3646
|
+
* File name a local save gives an ItemStorage property: `<seedUid or seedLocalId><extension>`
|
|
3647
|
+
* (see saveItemStorage). Downloaded content is named after its transaction instead
|
|
3648
|
+
* (`<txId><extension>`, see syncDbWithEas), which is what the metadata row's refResolvedValue says.
|
|
3649
|
+
*/
|
|
3650
|
+
const getItemStorageLocalFileName = (seedId, filenameSuffix) => `${seedId}${filenameSuffix ?? ''}`;
|
|
3651
|
+
|
|
3652
|
+
/** Rows per query: well under SQLite's bound-parameter limit. */
|
|
3653
|
+
const CHUNK_SIZE = 400;
|
|
3654
|
+
/**
|
|
3655
|
+
* Where the download worker saves each child of a composite upload, by transaction id then child
|
|
3656
|
+
* (property) name: a path relative to the files root.
|
|
3657
|
+
*
|
|
3658
|
+
* The paths come from the ItemStorage rows sync derives from a `storage_transaction_id`
|
|
3659
|
+
* attestation (see `createMetadataRecordsForStorageTransactionId`): property value = the
|
|
3660
|
+
* transaction id, `refResolvedValue` = `<txId><extension>`, `localStorageDir` = the model's storage
|
|
3661
|
+
* directory. Those rows are what hydration reads, so a child saved here is found as that property's
|
|
3662
|
+
* file. Transactions with no such rows are left out; the worker then falls back to the legacy
|
|
3663
|
+
* html / json layout.
|
|
3664
|
+
*/
|
|
3665
|
+
const getCompositeChildPaths = async (transactionIds) => {
|
|
3666
|
+
const out = {};
|
|
3667
|
+
const appDb = BaseDb.getAppDb();
|
|
3668
|
+
if (!appDb || transactionIds.length === 0)
|
|
3669
|
+
return out;
|
|
3670
|
+
for (let i = 0; i < transactionIds.length; i += CHUNK_SIZE) {
|
|
3671
|
+
const chunk = transactionIds.slice(i, i + CHUNK_SIZE);
|
|
3672
|
+
const rows = await appDb
|
|
3673
|
+
.select({
|
|
3674
|
+
transactionId: metadata.propertyValue,
|
|
3675
|
+
propertyName: metadata.propertyName,
|
|
3676
|
+
refResolvedValue: metadata.refResolvedValue,
|
|
3677
|
+
localStorageDir: metadata.localStorageDir,
|
|
3678
|
+
})
|
|
3679
|
+
.from(metadata)
|
|
3680
|
+
.where(and(isNotNull(metadata.derivedFromUid), isNotNull(metadata.refResolvedValue), inArray(metadata.propertyValue, chunk)));
|
|
3681
|
+
for (const row of rows) {
|
|
3682
|
+
const { transactionId, propertyName, refResolvedValue } = row;
|
|
3683
|
+
if (!transactionId || !propertyName || !refResolvedValue)
|
|
3684
|
+
continue;
|
|
3685
|
+
// A file name, never a path: anything else is not where the SDK would look.
|
|
3686
|
+
if (refResolvedValue.includes('/') || refResolvedValue.includes('\\'))
|
|
3687
|
+
continue;
|
|
3688
|
+
const byName = (out[transactionId] ?? (out[transactionId] = {}));
|
|
3689
|
+
byName[propertyName] ?? (byName[propertyName] = `${getItemStorageSubdir(row.localStorageDir)}/${refResolvedValue}`);
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
return out;
|
|
3693
|
+
};
|
|
3694
|
+
|
|
3695
|
+
const logger$2 = debug('seedSdk:files:download');
|
|
3696
|
+
const AUTO_BULK_DOWNLOAD_THROTTLE_MS = 10000;
|
|
3697
|
+
/** Avoid refetch-on-every-call: transient empty indexer responses were overwriting good cache (see trailing throttle ~10s after success). */
|
|
3698
|
+
const GET_FILES_METADATA_STALE_MS = 120000;
|
|
3699
|
+
let bulkDownloadInFlight = null;
|
|
3700
|
+
const lazyDownloadInFlightByTransactionId = new Map();
|
|
3701
|
+
// syncDbFiles helper - internal service removed, functionality moved here
|
|
3702
|
+
const syncDbFiles = async (endpoints) => {
|
|
3703
|
+
// TODO: Implement syncDbFiles functionality if needed
|
|
3704
|
+
// This was previously in @/services/internal/helpers
|
|
3705
|
+
logger$2('[download] syncDbFiles called but not yet implemented');
|
|
3706
|
+
return Promise.resolve();
|
|
3707
|
+
};
|
|
3708
|
+
const getAppDbReady = async () => {
|
|
3709
|
+
if (!BaseDb.isAppDbReady()) {
|
|
3710
|
+
// Wait for ClientManager to be ready (DB_INIT state or later)
|
|
3711
|
+
const _mod_0 = await import('./ClientManager-i3nJASkg.js');
|
|
3712
|
+
const _ns_0 = _mod_0.L;
|
|
3713
|
+
const clientManagerMod = _ns_0;
|
|
3714
|
+
const { getClient } = clientManagerMod;
|
|
3715
|
+
const clientManager = getClient();
|
|
3716
|
+
const clientService = clientManager.getService();
|
|
3717
|
+
await waitFor(clientService, (snapshot) => {
|
|
3718
|
+
const state = snapshot.value;
|
|
3719
|
+
return state === 'dbInit' ||
|
|
3720
|
+
state === 'saveConfig' ||
|
|
3721
|
+
state === 'processSchemaFiles' ||
|
|
3722
|
+
state === 'addModelsToStore' ||
|
|
3723
|
+
state === 'addModelsToDb' ||
|
|
3724
|
+
state === 'idle';
|
|
3725
|
+
}, { timeout: 30000 });
|
|
3726
|
+
}
|
|
3727
|
+
return BaseDb.getAppDb();
|
|
3728
|
+
};
|
|
3729
|
+
const getExcludedTransactions = async () => {
|
|
3730
|
+
const appDb = await getAppDbReady();
|
|
3731
|
+
if (!appDb) {
|
|
3732
|
+
return new Set();
|
|
3733
|
+
}
|
|
3734
|
+
const excludedTransactionsQuery = await appDb
|
|
3735
|
+
.select()
|
|
3736
|
+
.from(appState)
|
|
3737
|
+
.where(eq(appState.key, 'excludedTransactions'));
|
|
3738
|
+
const excludedTransactions = new Set();
|
|
3739
|
+
if (excludedTransactionsQuery && excludedTransactionsQuery.length === 1) {
|
|
3740
|
+
const valueString = excludedTransactionsQuery[0].value;
|
|
3741
|
+
if (valueString) {
|
|
3742
|
+
const excludedTransactionsArray = JSON.parse(valueString);
|
|
3743
|
+
for (const txId of excludedTransactionsArray) {
|
|
3744
|
+
excludedTransactions.add(txId);
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
return excludedTransactions;
|
|
3749
|
+
};
|
|
3750
|
+
/** Arweave transaction ids are 43 chars in the URL-safe base64 alphabet. */
|
|
3751
|
+
const ARWEAVE_TX_ID_RE$1 = /^[a-z0-9_-]{43}$/i;
|
|
3752
|
+
const normalizeArweaveTxId = (raw) => {
|
|
3753
|
+
if (raw == null || raw === '')
|
|
3754
|
+
return undefined;
|
|
3755
|
+
const t = String(raw).trim();
|
|
3756
|
+
if (ARWEAVE_TX_ID_RE$1.test(t))
|
|
3757
|
+
return t;
|
|
3758
|
+
const m = t.match(/^([a-z0-9_-]{43})(?:\.[^/]+)?$/i);
|
|
3759
|
+
if (m)
|
|
3760
|
+
return m[1];
|
|
3761
|
+
// Values may be full gateway URLs or paths; scan for the embedded tx id.
|
|
3762
|
+
const embedded = t.match(/[a-z0-9_-]{43}/gi);
|
|
3763
|
+
if (embedded) {
|
|
3764
|
+
for (const candidate of embedded) {
|
|
3765
|
+
if (ARWEAVE_TX_ID_RE$1.test(candidate))
|
|
3766
|
+
return candidate;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
return undefined;
|
|
3770
|
+
};
|
|
3771
|
+
const mergeAddressLists = (a, b) => {
|
|
3772
|
+
const seen = new Set();
|
|
3773
|
+
const out = [];
|
|
3774
|
+
for (const addr of [...(a ?? []), ...(b ?? [])]) {
|
|
3775
|
+
if (!addr || !addr.trim())
|
|
3776
|
+
continue;
|
|
3777
|
+
const k = addr.toLowerCase();
|
|
3778
|
+
if (seen.has(k))
|
|
3779
|
+
continue;
|
|
3780
|
+
seen.add(k);
|
|
3781
|
+
out.push(addr);
|
|
3782
|
+
}
|
|
3783
|
+
return out;
|
|
3784
|
+
};
|
|
3785
|
+
const collectTransactionIdsFromLocalMetadata = async () => {
|
|
3786
|
+
const appDb = await getAppDbReady();
|
|
3787
|
+
if (!appDb) {
|
|
3788
|
+
return [];
|
|
3789
|
+
}
|
|
3790
|
+
const nameOr = or(eq(metadata.propertyName, 'storageTransactionId'), eq(metadata.propertyName, 'storage_transaction_id'), eq(metadata.propertyName, 'transactionId'));
|
|
3791
|
+
const propRows = await appDb
|
|
3792
|
+
.select({ propertyValue: metadata.propertyValue })
|
|
3793
|
+
.from(metadata)
|
|
3794
|
+
.where(and(nameOr, isNotNull(metadata.propertyValue)));
|
|
3795
|
+
const refRows = await appDb
|
|
3796
|
+
.select({ refResolvedValue: metadata.refResolvedValue })
|
|
3797
|
+
.from(metadata)
|
|
3798
|
+
.where(isNotNull(metadata.refResolvedValue));
|
|
3799
|
+
const ids = new Set();
|
|
3800
|
+
for (const row of propRows) {
|
|
3801
|
+
const id = normalizeArweaveTxId(row.propertyValue ?? undefined);
|
|
3802
|
+
if (id)
|
|
3803
|
+
ids.add(id);
|
|
3804
|
+
}
|
|
3805
|
+
for (const row of refRows) {
|
|
3806
|
+
const id = normalizeArweaveTxId(row.refResolvedValue ?? undefined);
|
|
3807
|
+
if (id)
|
|
3808
|
+
ids.add(id);
|
|
3809
|
+
}
|
|
3810
|
+
return Array.from(ids);
|
|
3811
|
+
};
|
|
3812
|
+
const parseTransactionIdsFromMetadata = (filesMetadata, excludedTransactions) => {
|
|
3813
|
+
const transactionIds = [];
|
|
3814
|
+
for (const fileMetadata of filesMetadata) {
|
|
3815
|
+
// Validate and parse decodedDataJson
|
|
3816
|
+
if (!fileMetadata.decodedDataJson || fileMetadata.decodedDataJson.trim() === '') {
|
|
3817
|
+
console.warn('[events/files] [download] empty decodedDataJson for fileMetadata: ', fileMetadata.id);
|
|
3818
|
+
continue;
|
|
3819
|
+
}
|
|
3820
|
+
let json;
|
|
3821
|
+
try {
|
|
3822
|
+
json = JSON.parse(fileMetadata.decodedDataJson);
|
|
3823
|
+
}
|
|
3824
|
+
catch (error) {
|
|
3825
|
+
console.warn('[events/files] [download] failed to parse decodedDataJson for fileMetadata: ', fileMetadata.id, error);
|
|
3826
|
+
continue;
|
|
3827
|
+
}
|
|
3828
|
+
if (!Array.isArray(json) || json.length === 0 || !json[0]?.value?.value) {
|
|
3829
|
+
console.warn('[events/files] [download] invalid decodedDataJson structure for fileMetadata: ', fileMetadata.id);
|
|
3830
|
+
continue;
|
|
3831
|
+
}
|
|
3832
|
+
const rawTx = json[0].value.value;
|
|
3833
|
+
if (typeof rawTx !== 'string' || rawTx.trim().length === 0) {
|
|
3834
|
+
continue;
|
|
3835
|
+
}
|
|
3836
|
+
const transactionId = normalizeArweaveTxId(rawTx.trim());
|
|
3837
|
+
if (!transactionId) {
|
|
3838
|
+
continue;
|
|
3839
|
+
}
|
|
3840
|
+
if (excludedTransactions.has(transactionId) || excludedTransactions.has(rawTx.trim())) {
|
|
3841
|
+
continue;
|
|
3842
|
+
}
|
|
3843
|
+
transactionIds.push(transactionId);
|
|
3844
|
+
}
|
|
3845
|
+
return transactionIds;
|
|
3846
|
+
};
|
|
3847
|
+
/** Content-SHA-256 tag values for the transactions that have one, from one batched tag lookup (cached). */
|
|
3848
|
+
const getExpectedContentHashes = async (transactionIds) => {
|
|
3849
|
+
const queryClient = BaseQueryClient.getQueryClient();
|
|
3850
|
+
const tagsById = new Map();
|
|
3851
|
+
const toFetch = [];
|
|
3852
|
+
for (const transactionId of transactionIds) {
|
|
3853
|
+
// Tags never change once a transaction is indexed, so a cached lookup stays good.
|
|
3854
|
+
const cached = queryClient.getQueryData?.(['getTransactionTags', transactionId]);
|
|
3855
|
+
if (cached)
|
|
3856
|
+
tagsById.set(transactionId, cached);
|
|
3857
|
+
else
|
|
3858
|
+
toFetch.push(transactionId);
|
|
3859
|
+
}
|
|
3860
|
+
if (toFetch.length > 0) {
|
|
3861
|
+
try {
|
|
3862
|
+
const fetched = await getTransactionTagsByIds(toFetch, {
|
|
3863
|
+
client: BaseArweaveClient.getArweaveClient(),
|
|
3864
|
+
onError: (error) => logger$2('[download] transaction tags lookup failed', error),
|
|
3865
|
+
});
|
|
3866
|
+
for (const [transactionId, tags] of fetched) {
|
|
3867
|
+
tagsById.set(transactionId, tags);
|
|
3868
|
+
// Seed the per-transaction cache entry (the client interface has no setQueryData).
|
|
3869
|
+
void queryClient
|
|
3870
|
+
.fetchQuery({ queryKey: ['getTransactionTags', transactionId], queryFn: async () => tags })
|
|
3871
|
+
.catch(() => { });
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
catch (error) {
|
|
3875
|
+
logger$2('[download] transaction tags lookup failed', error);
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
const out = {};
|
|
3879
|
+
for (const [transactionId, tags] of tagsById) {
|
|
3880
|
+
const value = tags.find((tag) => tag.name === CONTENT_SHA256_TAG)?.value?.trim();
|
|
3881
|
+
if (value)
|
|
3882
|
+
out[transactionId] = value;
|
|
3883
|
+
}
|
|
3884
|
+
return out;
|
|
3885
|
+
};
|
|
3886
|
+
/** Store the (now verified) Content-SHA-256 of each downloaded transaction on its metadata row. */
|
|
3887
|
+
const recordVerifiedContentHashes = async (transactionIds, expectedContentHashes) => {
|
|
3888
|
+
for (const transactionId of transactionIds) {
|
|
3889
|
+
const contentHash = expectedContentHashes[transactionId];
|
|
3890
|
+
if (!contentHash)
|
|
3891
|
+
continue;
|
|
3892
|
+
try {
|
|
3893
|
+
const metadataRecord = await getMetadata({ storageTransactionId: transactionId });
|
|
3894
|
+
if (metadataRecord) {
|
|
3895
|
+
await saveMetadata(metadataRecord, { contentHash });
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
catch (error) {
|
|
3899
|
+
logger$2(error);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
};
|
|
3903
|
+
const downloadTransactionIds = async (rawTransactionIds, options) => {
|
|
3904
|
+
if (!supportsOpfsFileDownloads()) {
|
|
3905
|
+
return false;
|
|
3906
|
+
}
|
|
3907
|
+
const transactionIds = Array.from(new Set(rawTransactionIds.filter((tx) => !!tx && tx.trim().length > 0)));
|
|
3908
|
+
if (transactionIds.length === 0) {
|
|
3909
|
+
return false;
|
|
3910
|
+
}
|
|
3911
|
+
const excludedTransactions = await getExcludedTransactions();
|
|
3912
|
+
await ensureReadGatewaySelected();
|
|
3913
|
+
// Browser: do not call getTransactionStatus() — many gateways (e.g. g8way.io) redirect or omit
|
|
3914
|
+
// CORS headers, so fetch() fails and we would wrongly exclude txs. The OPFS worker fetch decides success.
|
|
3915
|
+
const transactionIdsToDownload = transactionIds.filter((id) => !excludedTransactions.has(id));
|
|
3916
|
+
if (transactionIdsToDownload.length === 0) {
|
|
3917
|
+
return false;
|
|
3918
|
+
}
|
|
3919
|
+
// One batched lookup for every upload's Content-SHA-256 tag; downloads are verified against it.
|
|
3920
|
+
// Missing tags (old uploads, or a just-published one not indexed yet) just mean no verification.
|
|
3921
|
+
const expectedContentHashes = await getExpectedContentHashes(transactionIdsToDownload);
|
|
3922
|
+
// Preferred gateways first: a just-published item is only on the gateway it was uploaded through.
|
|
3923
|
+
const arweaveBaseUrls = getArweaveReadBaseUrls();
|
|
3924
|
+
// Where each ItemStorage child of a composite goes (the worker cannot read the DB or the schema).
|
|
3925
|
+
const compositeChildPaths = await getCompositeChildPaths(transactionIdsToDownload).catch((error) => {
|
|
3926
|
+
logger$2('[download] composite child paths lookup failed', error);
|
|
3927
|
+
return {};
|
|
3928
|
+
});
|
|
3929
|
+
let result;
|
|
3930
|
+
if (transactionIdsToDownload.length === 1) {
|
|
3931
|
+
const transactionId = transactionIdsToDownload[0];
|
|
3932
|
+
result = await BaseFileManager.downloadFileByTransactionId({
|
|
3933
|
+
transactionId,
|
|
3934
|
+
arweaveHost: BaseArweaveClient.getBaseUrl(),
|
|
3935
|
+
arweaveBaseUrls,
|
|
3936
|
+
excludedTransactions,
|
|
3937
|
+
expectedContentHash: expectedContentHashes[transactionId],
|
|
3938
|
+
compositeChildPaths: compositeChildPaths[transactionId],
|
|
3939
|
+
});
|
|
3940
|
+
}
|
|
3941
|
+
else {
|
|
3942
|
+
result = await BaseFileManager.downloadAllFiles({
|
|
3943
|
+
transactionIds: transactionIdsToDownload,
|
|
3944
|
+
arweaveHost: BaseArweaveClient.getBaseUrl(),
|
|
3945
|
+
arweaveBaseUrls,
|
|
3946
|
+
excludedTransactions,
|
|
3947
|
+
expectedContentHashes,
|
|
3948
|
+
compositeChildPaths,
|
|
3949
|
+
});
|
|
3950
|
+
}
|
|
3951
|
+
if (result) {
|
|
3952
|
+
for (const transactionId of result.hashMismatchTransactionIds) {
|
|
3953
|
+
// Not saved by the worker; left for a later run (a gateway may serve the right bytes then).
|
|
3954
|
+
logger$2(`[download] ${transactionId}: downloaded bytes do not match Content-SHA-256; skipped`);
|
|
3955
|
+
}
|
|
3956
|
+
await recordVerifiedContentHashes(result.verifiedTransactionIds, expectedContentHashes);
|
|
3957
|
+
}
|
|
3958
|
+
if (options?.resizeAllImages !== false) {
|
|
3959
|
+
await BaseFileManager.resizeAllImages({
|
|
3960
|
+
width: 480,
|
|
3961
|
+
height: 480,
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
return true;
|
|
3965
|
+
};
|
|
3966
|
+
const runBulkDownloadWithDedupe = async (transactionIds) => {
|
|
3967
|
+
if (bulkDownloadInFlight) {
|
|
3968
|
+
return bulkDownloadInFlight;
|
|
3969
|
+
}
|
|
3970
|
+
bulkDownloadInFlight = (async () => {
|
|
3971
|
+
try {
|
|
3972
|
+
await downloadTransactionIds(transactionIds, { resizeAllImages: true });
|
|
3973
|
+
}
|
|
3974
|
+
finally {
|
|
3975
|
+
bulkDownloadInFlight = null;
|
|
3976
|
+
}
|
|
3977
|
+
})();
|
|
3978
|
+
return bulkDownloadInFlight;
|
|
3979
|
+
};
|
|
3980
|
+
const downloadAllFilesRequestHandler = async ({ endpoints, eventId, }) => {
|
|
3981
|
+
if (!supportsOpfsFileDownloads()) {
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
await syncDbFiles();
|
|
3985
|
+
eventEmitter.emit('fs.downloadAll.success', { eventId });
|
|
3986
|
+
eventEmitter.emit('fs.downloadAllBinary.request', { endpoints });
|
|
3987
|
+
};
|
|
3988
|
+
const downloadAllFilesBinaryRequestHandler = async (addressesHintOrPayload) => {
|
|
3989
|
+
if (!supportsOpfsFileDownloads()) {
|
|
3990
|
+
return;
|
|
3991
|
+
}
|
|
3992
|
+
const addressesHint = Array.isArray(addressesHintOrPayload)
|
|
3993
|
+
? addressesHintOrPayload
|
|
3994
|
+
: undefined;
|
|
3995
|
+
let fromDb = [];
|
|
3996
|
+
if (BaseDb.isAppDbReady()) {
|
|
3997
|
+
fromDb = await getAllAddressesFromDb();
|
|
3998
|
+
}
|
|
3999
|
+
if (!BaseDb.isAppDbReady()) {
|
|
4000
|
+
await getAppDbReady();
|
|
4001
|
+
fromDb = await getAllAddressesFromDb();
|
|
4002
|
+
}
|
|
4003
|
+
const addresses = mergeAddressLists(addressesHint, fromDb);
|
|
4004
|
+
if (!addresses || addresses.length === 0) {
|
|
4005
|
+
return;
|
|
4006
|
+
}
|
|
4007
|
+
const queryClient = BaseQueryClient.getQueryClient();
|
|
4008
|
+
const easClient = BaseEasClient.getEasClient();
|
|
4009
|
+
const filesMetadataQueryKey = ['getFilesMetadata', ...addresses];
|
|
4010
|
+
const fetchFilesMetadata = () => easClient.request(GET_FILES_METADATA, {
|
|
4011
|
+
where: {
|
|
4012
|
+
attester: {
|
|
4013
|
+
in: addresses,
|
|
4014
|
+
},
|
|
4015
|
+
schema: {
|
|
4016
|
+
is: {
|
|
4017
|
+
id: {
|
|
4018
|
+
equals: '0x55fdefb36fcbbaebeb7d6b41dc3a1a9666e4e42154267c889de064faa7ede517',
|
|
4019
|
+
},
|
|
4020
|
+
},
|
|
4021
|
+
},
|
|
4022
|
+
},
|
|
4023
|
+
});
|
|
4024
|
+
let { filesMetadata } = await queryClient.fetchQuery({
|
|
4025
|
+
queryKey: filesMetadataQueryKey,
|
|
4026
|
+
queryFn: fetchFilesMetadata,
|
|
4027
|
+
staleTime: GET_FILES_METADATA_STALE_MS,
|
|
4028
|
+
});
|
|
4029
|
+
if ((!Array.isArray(filesMetadata) || filesMetadata.length === 0) && addresses.length > 0) {
|
|
4030
|
+
await queryClient.removeQueries({ queryKey: filesMetadataQueryKey });
|
|
4031
|
+
({ filesMetadata } = await queryClient.fetchQuery({
|
|
4032
|
+
queryKey: filesMetadataQueryKey,
|
|
4033
|
+
queryFn: fetchFilesMetadata,
|
|
4034
|
+
staleTime: GET_FILES_METADATA_STALE_MS,
|
|
4035
|
+
}));
|
|
4036
|
+
}
|
|
4037
|
+
const filesRoot = BaseFileManager.getWorkingDir();
|
|
4038
|
+
await BaseFileManager.createDirIfNotExists(filesRoot);
|
|
4039
|
+
await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('html'));
|
|
4040
|
+
await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('json'));
|
|
4041
|
+
await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('images'));
|
|
4042
|
+
await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('files'));
|
|
4043
|
+
const excludedTransactions = await getExcludedTransactions();
|
|
4044
|
+
const fromEas = parseTransactionIdsFromMetadata(filesMetadata ?? [], excludedTransactions);
|
|
4045
|
+
const fromLocal = await collectTransactionIdsFromLocalMetadata();
|
|
4046
|
+
const transactionIds = Array.from(new Set([...fromEas, ...fromLocal]));
|
|
4047
|
+
await runBulkDownloadWithDedupe(transactionIds);
|
|
4048
|
+
};
|
|
4049
|
+
const downloadTransactionIdWithDedupe = async (transactionId) => {
|
|
4050
|
+
if (!supportsOpfsFileDownloads() || !transactionId || transactionId.trim().length === 0) {
|
|
4051
|
+
return false;
|
|
4052
|
+
}
|
|
4053
|
+
const normalizedTxId = normalizeArweaveTxId(transactionId) ?? transactionId.trim();
|
|
4054
|
+
const existing = lazyDownloadInFlightByTransactionId.get(normalizedTxId);
|
|
4055
|
+
if (existing) {
|
|
4056
|
+
return existing;
|
|
4057
|
+
}
|
|
4058
|
+
const inFlight = (async () => {
|
|
4059
|
+
try {
|
|
4060
|
+
const ok = await downloadTransactionIds([normalizedTxId], { resizeAllImages: false });
|
|
4061
|
+
return ok;
|
|
4062
|
+
}
|
|
4063
|
+
finally {
|
|
4064
|
+
lazyDownloadInFlightByTransactionId.delete(normalizedTxId);
|
|
4065
|
+
}
|
|
4066
|
+
})();
|
|
4067
|
+
lazyDownloadInFlightByTransactionId.set(normalizedTxId, inFlight);
|
|
4068
|
+
return inFlight;
|
|
4069
|
+
};
|
|
4070
|
+
const scheduleBulkFilesDownloadFromEasSyncThrottled = throttle((addressesHint) => {
|
|
4071
|
+
void downloadAllFilesBinaryRequestHandler(addressesHint).catch((error) => {
|
|
4072
|
+
logger$2('[scheduleBulkFilesDownloadFromEasSync] failed', error);
|
|
4073
|
+
});
|
|
4074
|
+
}, AUTO_BULK_DOWNLOAD_THROTTLE_MS, { leading: true, trailing: false });
|
|
4075
|
+
const scheduleBulkFilesDownloadFromEasSync = (addressesHint) => {
|
|
4076
|
+
if (!supportsOpfsFileDownloads()) {
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
4079
|
+
scheduleBulkFilesDownloadFromEasSyncThrottled(addressesHint);
|
|
4080
|
+
};
|
|
4081
|
+
|
|
4082
|
+
var download = /*#__PURE__*/Object.freeze({
|
|
4083
|
+
__proto__: null,
|
|
4084
|
+
downloadAllFilesBinaryRequestHandler: downloadAllFilesBinaryRequestHandler,
|
|
4085
|
+
downloadAllFilesRequestHandler: downloadAllFilesRequestHandler,
|
|
4086
|
+
downloadTransactionIdWithDedupe: downloadTransactionIdWithDedupe,
|
|
4087
|
+
scheduleBulkFilesDownloadFromEasSync: scheduleBulkFilesDownloadFromEasSync
|
|
4088
|
+
});
|
|
4089
|
+
|
|
4090
|
+
// Top-level re-export (not mid-file import + export): Rollup with
|
|
4091
|
+
// external `@seedprotocol/arweave` otherwise emits a side-effect-only import
|
|
4092
|
+
// and an unbound Object.freeze namespace when this module is dynamically imported.
|
|
4093
|
+
// Define zero constants ourselves since they're not exported from eas-sdk in newer versions
|
|
4094
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
4095
|
+
const ZERO_BYTES = '0x';
|
|
4096
|
+
const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
4097
|
+
// Primary config file name for Seed Protocol SDK
|
|
4098
|
+
const SEED_CONFIG_FILE = 'seed.config.ts';
|
|
4099
|
+
// Fallback config file names (in order of preference)
|
|
4100
|
+
const SEED_CONFIG_FALLBACKS = ['seed.schema.ts', 'schema.ts'];
|
|
4101
|
+
// Legacy constant for backward compatibility
|
|
4102
|
+
const SCHEMA_TS = SEED_CONFIG_FILE;
|
|
4103
|
+
const INTERNAL_DATA_TYPES = {
|
|
4104
|
+
Text: {
|
|
4105
|
+
eas: 'string',
|
|
4106
|
+
},
|
|
4107
|
+
Number: {
|
|
4108
|
+
eas: 'uint8',
|
|
4109
|
+
},
|
|
4110
|
+
Image: {
|
|
4111
|
+
eas: 'bytes32',
|
|
4112
|
+
},
|
|
4113
|
+
Relation: {
|
|
4114
|
+
eas: 'bytes32',
|
|
4115
|
+
},
|
|
4116
|
+
List: {
|
|
4117
|
+
eas: 'bytes32[]',
|
|
4118
|
+
},
|
|
4119
|
+
File: {
|
|
4120
|
+
eas: 'bytes32',
|
|
4121
|
+
},
|
|
4122
|
+
Json: {
|
|
4123
|
+
eas: 'bytes32',
|
|
4124
|
+
},
|
|
4125
|
+
Blob: {
|
|
4126
|
+
eas: 'bytes32',
|
|
4127
|
+
},
|
|
4128
|
+
Boolean: {
|
|
4129
|
+
eas: 'bool',
|
|
4130
|
+
},
|
|
4131
|
+
Date: {
|
|
4132
|
+
eas: 'uint256',
|
|
4133
|
+
},
|
|
4134
|
+
Html: {
|
|
4135
|
+
eas: 'bytes32',
|
|
4136
|
+
},
|
|
4137
|
+
};
|
|
4138
|
+
/** Full property names ending in `Id` that must not be stripped to a “base” name for `allProperties` keys. */
|
|
4139
|
+
const PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP = new Set(['storageTransactionId']);
|
|
4140
|
+
const INTERNAL_PROPERTY_NAMES = [
|
|
4141
|
+
'localId',
|
|
4142
|
+
'uid',
|
|
4143
|
+
'seedLocalId',
|
|
4144
|
+
'seedUid',
|
|
4145
|
+
'schemaUid',
|
|
4146
|
+
'attestationCreatedAt',
|
|
4147
|
+
'attestationRaw',
|
|
4148
|
+
'createdAt',
|
|
4149
|
+
'updatedAt',
|
|
4150
|
+
'lastVersionPublishedAt',
|
|
4151
|
+
'latestVersionLocalId',
|
|
4152
|
+
'latestVersionUid',
|
|
4153
|
+
'lastLocalUpdateAt',
|
|
4154
|
+
'modelName',
|
|
4155
|
+
'refSeedType',
|
|
4156
|
+
'refValueType',
|
|
4157
|
+
'refResolvedValue',
|
|
4158
|
+
'refResolvedDisplayValue',
|
|
4159
|
+
'type',
|
|
4160
|
+
// Image
|
|
4161
|
+
'src',
|
|
4162
|
+
'alt',
|
|
4163
|
+
//
|
|
4164
|
+
'versionLocalId',
|
|
4165
|
+
'versionsCount',
|
|
4166
|
+
'versionUid',
|
|
4167
|
+
'_markedForDeletion',
|
|
4168
|
+
// SDK-internal: wallet address of attester; never publish as property attestation
|
|
4169
|
+
'publisher',
|
|
4170
|
+
// SDK-internal: Unix timestamp when attestations were revoked; never a schema field
|
|
4171
|
+
'revokedAt',
|
|
4172
|
+
];
|
|
4173
|
+
/** Schema definition of the Version attestation (zero resolver, revocable). */
|
|
4174
|
+
const VERSION_SCHEMA_DEF = 'bytes32 version';
|
|
4175
|
+
/**
|
|
4176
|
+
* UID of {@link VERSION_SCHEMA_DEF}. Schema UIDs hash the definition, resolver and revocable flag,
|
|
4177
|
+
* so this is the same on every chain; the schema must still be registered on each one.
|
|
4178
|
+
*/
|
|
4179
|
+
const VERSION_SCHEMA_UID = '0x13c0fd59d69dbce40501a41f8b37768d26dd2e2bb0cad64615334d84f7b9bdf6';
|
|
4180
|
+
/** @deprecated The UID is chain-independent. Use {@link VERSION_SCHEMA_UID}. */
|
|
4181
|
+
const VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA = VERSION_SCHEMA_UID;
|
|
4182
|
+
const defaultAttestationData = {
|
|
4183
|
+
recipient: ZERO_ADDRESS,
|
|
4184
|
+
revocable: true,
|
|
4185
|
+
value: BigInt(0),
|
|
4186
|
+
refUID: ZERO_BYTES32,
|
|
4187
|
+
expirationTime: BigInt(0),
|
|
4188
|
+
data: ZERO_BYTES,
|
|
4189
|
+
};
|
|
4190
|
+
var ImageSize;
|
|
4191
|
+
(function (ImageSize) {
|
|
4192
|
+
ImageSize[ImageSize["EXTRA_SMALL"] = 480] = "EXTRA_SMALL";
|
|
4193
|
+
ImageSize[ImageSize["SMALL"] = 760] = "SMALL";
|
|
4194
|
+
ImageSize[ImageSize["MEDIUM"] = 1024] = "MEDIUM";
|
|
4195
|
+
ImageSize[ImageSize["LARGE"] = 1440] = "LARGE";
|
|
4196
|
+
ImageSize[ImageSize["EXTRA_LARGE"] = 1920] = "EXTRA_LARGE";
|
|
4197
|
+
})(ImageSize || (ImageSize = {}));
|
|
4198
|
+
const CLIENT_NOT_INITIALIZED = 'ClientManager is not initialized. Please call init() first.';
|
|
4199
|
+
const INIT_SCRIPT_SUCCESS_MESSAGE = '[Seed Protocol] Finished running init script';
|
|
4200
|
+
var SeedModels;
|
|
4201
|
+
(function (SeedModels) {
|
|
4202
|
+
SeedModels["Seed"] = "Seed";
|
|
4203
|
+
SeedModels["Metadata"] = "Metadata";
|
|
4204
|
+
SeedModels["Version"] = "Version";
|
|
4205
|
+
})(SeedModels || (SeedModels = {}));
|
|
4206
|
+
// Internal SDK schema that should not be created in app's files directory
|
|
4207
|
+
const SEED_PROTOCOL_SCHEMA_NAME = 'Seed Protocol';
|
|
4208
|
+
const INTERNAL_SCHEMA_IDS = ['SEEDPROTOCOL'];
|
|
4209
|
+
/** Fired after `syncDbWithEas` persists EAS seeds/versions/metadata to SQLite (see `Item.rehydrateCachedItemsFromDbAfterEasSync`). */
|
|
4210
|
+
const EAS_SEED_DATA_SYNCED_TO_DB_EVENT = 'easSeedDataSyncedToDb';
|
|
4211
|
+
/**
|
|
4212
|
+
* Check if a schema is an internal SDK schema that should not be created in app files
|
|
4213
|
+
*/
|
|
4214
|
+
function isInternalSchema(schemaName, schemaId) {
|
|
4215
|
+
return schemaName === SEED_PROTOCOL_SCHEMA_NAME ||
|
|
4216
|
+
(schemaId !== undefined && INTERNAL_SCHEMA_IDS.includes(schemaId));
|
|
4217
|
+
}
|
|
4218
|
+
/**
|
|
4219
|
+
* Model ids (schemaFileId) of the internal Seed Protocol storage models. Storage seeds are always
|
|
4220
|
+
* these models, even if a user schema also defines a model named Image/File/Html.
|
|
4221
|
+
*/
|
|
4222
|
+
const INTERNAL_STORAGE_MODEL_FILE_IDS = {
|
|
4223
|
+
image: 'SEEDPROTOCOL_IMAGE',
|
|
4224
|
+
file: 'SEEDPROTOCOL_FILE',
|
|
4225
|
+
html: 'SEEDPROTOCOL_HTML',
|
|
4226
|
+
};
|
|
4227
|
+
|
|
4228
|
+
const logger$1 = debug('seedSdk:helpers:ensureImageLocal');
|
|
4229
|
+
/** Arweave transaction ids are 43 chars in the URL-safe base64 alphabet. */
|
|
4230
|
+
const ARWEAVE_TX_ID_RE = /^[a-z0-9_-]{43}$/i;
|
|
4231
|
+
const IMAGE_SIZES = [
|
|
4232
|
+
ImageSize.EXTRA_SMALL,
|
|
4233
|
+
ImageSize.SMALL,
|
|
4234
|
+
ImageSize.MEDIUM,
|
|
4235
|
+
ImageSize.LARGE,
|
|
4236
|
+
ImageSize.EXTRA_LARGE,
|
|
4237
|
+
];
|
|
4238
|
+
const inFlightByKey = new Map();
|
|
4239
|
+
const normalizeArweaveTxIdForEnsure = (raw) => {
|
|
4240
|
+
if (raw == null || raw === '')
|
|
4241
|
+
return undefined;
|
|
4242
|
+
const t = String(raw).trim();
|
|
4243
|
+
if (ARWEAVE_TX_ID_RE.test(t))
|
|
4244
|
+
return t;
|
|
4245
|
+
const m = t.match(/^([a-z0-9_-]{43})(?:\.[^/]+)?$/i);
|
|
4246
|
+
if (m)
|
|
4247
|
+
return m[1];
|
|
4248
|
+
const embedded = t.match(/[a-z0-9_-]{43}/gi);
|
|
4249
|
+
if (embedded) {
|
|
4250
|
+
for (const candidate of embedded) {
|
|
4251
|
+
if (ARWEAVE_TX_ID_RE.test(candidate))
|
|
4252
|
+
return candidate;
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
return undefined;
|
|
4256
|
+
};
|
|
4257
|
+
const closestImageSize = (width) => {
|
|
4258
|
+
let best = ImageSize.EXTRA_SMALL;
|
|
4259
|
+
let bestDist = Math.abs(best - width);
|
|
4260
|
+
for (const size of IMAGE_SIZES) {
|
|
4261
|
+
const dist = Math.abs(size - width);
|
|
4262
|
+
if (dist < bestDist) {
|
|
4263
|
+
best = size;
|
|
4264
|
+
bestDist = dist;
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
return best;
|
|
4268
|
+
};
|
|
4269
|
+
const mapRequestedWidthsToImageSizes = (widths) => {
|
|
4270
|
+
const requested = widths && widths.length > 0 ? widths : [ImageSize.EXTRA_SMALL];
|
|
4271
|
+
const unique = new Set();
|
|
4272
|
+
for (const w of requested) {
|
|
4273
|
+
if (typeof w !== 'number' || !Number.isFinite(w) || w <= 0)
|
|
4274
|
+
continue;
|
|
4275
|
+
unique.add(closestImageSize(w));
|
|
4276
|
+
}
|
|
4277
|
+
if (unique.size === 0) {
|
|
4278
|
+
unique.add(ImageSize.EXTRA_SMALL);
|
|
4279
|
+
}
|
|
4280
|
+
return Array.from(unique).sort((a, b) => a - b);
|
|
4281
|
+
};
|
|
4282
|
+
const getBasename = (filePath) => {
|
|
4283
|
+
const path = BaseFileManager.getPathModule();
|
|
4284
|
+
return path.basename(filePath);
|
|
4285
|
+
};
|
|
4286
|
+
const getBasenameWithoutExtension = (fileName) => {
|
|
4287
|
+
const match = fileName.match(/^(.*[\/\\])?([^\/\\]+?)(\.[^.\/\\]*)?$/);
|
|
4288
|
+
return match?.[2] ?? fileName;
|
|
4289
|
+
};
|
|
4290
|
+
const resolveMatchingImagePath = async (needle) => {
|
|
4291
|
+
const dirPath = BaseFileManager.getFilesPath('images');
|
|
4292
|
+
const dirExists = await BaseFileManager.pathExists(dirPath);
|
|
4293
|
+
if (!dirExists)
|
|
4294
|
+
return undefined;
|
|
4295
|
+
const fs = await BaseFileManager.getFs();
|
|
4296
|
+
const path = BaseFileManager.getPathModule();
|
|
4297
|
+
const files = await fs.promises.readdir(dirPath);
|
|
4298
|
+
// Prefer exact basename match (files only); skip size dirs.
|
|
4299
|
+
const exact = files.find((file) => path.basename(file) === needle);
|
|
4300
|
+
if (exact) {
|
|
4301
|
+
const candidate = BaseFileManager.getFilesPath('images', exact);
|
|
4302
|
+
try {
|
|
4303
|
+
const zenfs = await BaseFileManager.getFs();
|
|
4304
|
+
const stat = await zenfs.promises.stat(candidate);
|
|
4305
|
+
if (stat.isFile())
|
|
4306
|
+
return candidate;
|
|
4307
|
+
}
|
|
4308
|
+
catch {
|
|
4309
|
+
// fall through
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
const matchingFiles = files.filter((file) => {
|
|
4313
|
+
const base = path.basename(file);
|
|
4314
|
+
return base.includes(needle);
|
|
4315
|
+
});
|
|
4316
|
+
for (const file of matchingFiles) {
|
|
4317
|
+
const candidate = BaseFileManager.getFilesPath('images', file);
|
|
4318
|
+
try {
|
|
4319
|
+
const zenfs = await BaseFileManager.getFs();
|
|
4320
|
+
const stat = await zenfs.promises.stat(candidate);
|
|
4321
|
+
if (stat.isFile())
|
|
4322
|
+
return candidate;
|
|
4323
|
+
}
|
|
4324
|
+
catch {
|
|
4325
|
+
continue;
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
return undefined;
|
|
4329
|
+
};
|
|
4330
|
+
const sizedVariantPath = (originalFilePath, size) => {
|
|
4331
|
+
const path = BaseFileManager.getPathModule();
|
|
4332
|
+
const dir = path.dirname(originalFilePath);
|
|
4333
|
+
const base = getBasenameWithoutExtension(getBasename(originalFilePath));
|
|
4334
|
+
return path.join(dir, String(size), `${base}.webp`);
|
|
4335
|
+
};
|
|
4336
|
+
const ensureKey = (txId, fileName, sizes) => {
|
|
4337
|
+
const idPart = txId ?? fileName ?? '';
|
|
4338
|
+
return `${idPart}|${sizes.join(',')}`;
|
|
4339
|
+
};
|
|
4340
|
+
const runEnsureImageLocal = async (params) => {
|
|
4341
|
+
if (!supportsOpfsFileDownloads()) {
|
|
4342
|
+
return { status: 'unsupported' };
|
|
4343
|
+
}
|
|
4344
|
+
const fileName = params.fileName?.trim() || undefined;
|
|
4345
|
+
const txId = normalizeArweaveTxIdForEnsure(params.transactionId) ??
|
|
4346
|
+
normalizeArweaveTxIdForEnsure(fileName);
|
|
4347
|
+
if (!txId && !fileName) {
|
|
4348
|
+
return { status: 'missing-id' };
|
|
4349
|
+
}
|
|
4350
|
+
const targetSizes = mapRequestedWidthsToImageSizes(params.widths);
|
|
4351
|
+
const needles = [fileName, txId].filter((n) => typeof n === 'string' && n.length > 0);
|
|
4352
|
+
try {
|
|
4353
|
+
let filePath;
|
|
4354
|
+
for (const needle of needles) {
|
|
4355
|
+
filePath = await resolveMatchingImagePath(needle);
|
|
4356
|
+
if (filePath)
|
|
4357
|
+
break;
|
|
4358
|
+
}
|
|
4359
|
+
if (!filePath && txId) {
|
|
4360
|
+
await downloadTransactionIdWithDedupe(txId);
|
|
4361
|
+
for (const needle of needles) {
|
|
4362
|
+
filePath = await resolveMatchingImagePath(needle);
|
|
4363
|
+
if (filePath)
|
|
4364
|
+
break;
|
|
4365
|
+
}
|
|
4366
|
+
// Downloads store as bare tx id
|
|
4367
|
+
if (!filePath) {
|
|
4368
|
+
const direct = BaseFileManager.getFilesPath('images', txId);
|
|
4369
|
+
if (await BaseFileManager.pathExists(direct)) {
|
|
4370
|
+
filePath = direct;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
if (!filePath) {
|
|
4375
|
+
return { status: 'failed' };
|
|
4376
|
+
}
|
|
4377
|
+
const createdWidths = [];
|
|
4378
|
+
for (const size of targetSizes) {
|
|
4379
|
+
const variantPath = sizedVariantPath(filePath, size);
|
|
4380
|
+
const exists = await BaseFileManager.pathExists(variantPath);
|
|
4381
|
+
if (exists)
|
|
4382
|
+
continue;
|
|
4383
|
+
await BaseFileManager.resizeImage({
|
|
4384
|
+
filePath,
|
|
4385
|
+
width: size,
|
|
4386
|
+
height: size,
|
|
4387
|
+
});
|
|
4388
|
+
createdWidths.push(size);
|
|
4389
|
+
}
|
|
4390
|
+
return {
|
|
4391
|
+
status: 'ready',
|
|
4392
|
+
filePath,
|
|
4393
|
+
createdWidths,
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
catch (error) {
|
|
4397
|
+
logger$1('ensureImageLocal failed', error);
|
|
4398
|
+
return { status: 'failed' };
|
|
4399
|
+
}
|
|
4400
|
+
};
|
|
4401
|
+
/**
|
|
4402
|
+
* Ensure an image original exists in OPFS (downloading from Arweave if needed)
|
|
4403
|
+
* and create only the requested ImageSize variant(s) for that one file.
|
|
4404
|
+
*/
|
|
4405
|
+
const ensureImageLocal = async (params) => {
|
|
4406
|
+
if (!supportsOpfsFileDownloads()) {
|
|
4407
|
+
return { status: 'unsupported' };
|
|
4408
|
+
}
|
|
4409
|
+
const fileName = params.fileName?.trim() || undefined;
|
|
4410
|
+
const txId = normalizeArweaveTxIdForEnsure(params.transactionId) ??
|
|
4411
|
+
normalizeArweaveTxIdForEnsure(fileName);
|
|
4412
|
+
if (!txId && !fileName) {
|
|
4413
|
+
return { status: 'missing-id' };
|
|
4414
|
+
}
|
|
4415
|
+
const targetSizes = mapRequestedWidthsToImageSizes(params.widths);
|
|
4416
|
+
const key = ensureKey(txId, fileName, targetSizes);
|
|
4417
|
+
const existing = inFlightByKey.get(key);
|
|
4418
|
+
if (existing) {
|
|
4419
|
+
return existing;
|
|
4420
|
+
}
|
|
4421
|
+
const inFlight = (async () => {
|
|
4422
|
+
try {
|
|
4423
|
+
return await runEnsureImageLocal({
|
|
4424
|
+
transactionId: txId,
|
|
4425
|
+
fileName,
|
|
4426
|
+
widths: targetSizes,
|
|
4427
|
+
});
|
|
4428
|
+
}
|
|
4429
|
+
finally {
|
|
4430
|
+
inFlightByKey.delete(key);
|
|
4431
|
+
}
|
|
4432
|
+
})();
|
|
4433
|
+
inFlightByKey.set(key, inFlight);
|
|
4434
|
+
return inFlight;
|
|
4435
|
+
};
|
|
4436
|
+
|
|
4437
|
+
const randomId = customAlphabet(alphanumeric, 10);
|
|
4438
|
+
/**
|
|
4439
|
+
* A 10-character local id. Never starts with "0x" (about 1 in 3,844 random ids would), so code that
|
|
4440
|
+
* tells local ids from EAS uids by that prefix can't mistake one for the other.
|
|
4441
|
+
*/
|
|
4442
|
+
const generateId = () => {
|
|
4443
|
+
let id = randomId();
|
|
4444
|
+
while (id.startsWith('0x'))
|
|
4445
|
+
id = randomId();
|
|
4446
|
+
return id;
|
|
4447
|
+
};
|
|
4448
|
+
|
|
4449
|
+
var generateId$1 = /*#__PURE__*/Object.freeze({
|
|
4450
|
+
__proto__: null,
|
|
4451
|
+
generateId: generateId
|
|
4452
|
+
});
|
|
4453
|
+
|
|
4454
|
+
const logger = debug('seedSdk:shared:helpers');
|
|
4455
|
+
const toSnakeCase = (str) => {
|
|
4456
|
+
return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
|
|
4457
|
+
};
|
|
4458
|
+
const getMimeType = (base64) => {
|
|
4459
|
+
if (!base64) {
|
|
4460
|
+
return null;
|
|
4461
|
+
}
|
|
4462
|
+
const result = base64.match(/^data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,/);
|
|
4463
|
+
if (result && result.length > 1) {
|
|
4464
|
+
return result[1];
|
|
4465
|
+
}
|
|
4466
|
+
else {
|
|
4467
|
+
return null; // MIME type could not be determined
|
|
4468
|
+
}
|
|
4469
|
+
};
|
|
4470
|
+
const getCorrectId = (localIdOrUid) => {
|
|
4471
|
+
const id = {
|
|
4472
|
+
localId: undefined,
|
|
4473
|
+
uid: undefined,
|
|
4474
|
+
};
|
|
4475
|
+
if (!localIdOrUid) {
|
|
4476
|
+
return id;
|
|
4477
|
+
}
|
|
4478
|
+
if (localIdOrUid.length === 10) {
|
|
4479
|
+
id.localId = localIdOrUid;
|
|
4480
|
+
}
|
|
4481
|
+
if (localIdOrUid.startsWith('0x') && localIdOrUid.length === 66) {
|
|
4482
|
+
id.uid = localIdOrUid;
|
|
4483
|
+
}
|
|
4484
|
+
return id;
|
|
4485
|
+
};
|
|
4486
|
+
const getDataTypeFromString = (data) => {
|
|
4487
|
+
const nonImageBase64Regex = /^(?!data:image\/(?:jpeg|png|gif|bmp|webp);base64,)[A-Za-z0-9+/=]+$/;
|
|
4488
|
+
if (nonImageBase64Regex.test(data)) {
|
|
4489
|
+
return 'base64';
|
|
4490
|
+
}
|
|
4491
|
+
// Regular expression for base64 (simple version, checking for base64 format)
|
|
4492
|
+
const imageBase64Regex = /^data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/]+={0,2}$/;
|
|
4493
|
+
if (imageBase64Regex.test(data)) {
|
|
4494
|
+
return 'imageBase64';
|
|
4495
|
+
}
|
|
4496
|
+
// Blob URLs are fetchable but session-scoped; recognize them as url type
|
|
4497
|
+
if (data.startsWith('blob:')) {
|
|
4498
|
+
return 'url';
|
|
4499
|
+
}
|
|
4500
|
+
// Regular expression for URL (simple version, checking for common URL format)
|
|
4501
|
+
const urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/;
|
|
4502
|
+
if (urlRegex.test(data)) {
|
|
4503
|
+
return 'url';
|
|
4504
|
+
}
|
|
4505
|
+
return null;
|
|
4506
|
+
};
|
|
4507
|
+
const convertTxIdToImage = async (txId) => {
|
|
4508
|
+
const imageFilePath = BaseFileManager.getFilesPath('images', txId);
|
|
4509
|
+
const fileExists = await BaseFileManager.pathExists(imageFilePath);
|
|
4510
|
+
if (!fileExists) {
|
|
4511
|
+
logger(`[ItemView] [updateImage] ${imageFilePath} does not exist`);
|
|
4512
|
+
return;
|
|
4513
|
+
}
|
|
4514
|
+
const buffer = await BaseFileManager.readFileAsBuffer(imageFilePath);
|
|
4515
|
+
// Convert Blob to ArrayBuffer if needed, or use Buffer directly
|
|
4516
|
+
let arrayBuffer;
|
|
4517
|
+
if (buffer instanceof Blob) {
|
|
4518
|
+
arrayBuffer = await buffer.arrayBuffer();
|
|
4519
|
+
}
|
|
4520
|
+
else if (buffer instanceof ArrayBuffer) {
|
|
4521
|
+
arrayBuffer = buffer;
|
|
4522
|
+
}
|
|
4523
|
+
else {
|
|
4524
|
+
// Node.js Buffer
|
|
4525
|
+
const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
4526
|
+
// Handle SharedArrayBuffer case by creating a new ArrayBuffer
|
|
4527
|
+
if (sliced instanceof SharedArrayBuffer) {
|
|
4528
|
+
const uint8Array = new Uint8Array(sliced);
|
|
4529
|
+
arrayBuffer = uint8Array.buffer.slice(0);
|
|
4530
|
+
}
|
|
4531
|
+
else {
|
|
4532
|
+
arrayBuffer = sliced;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
const uint = new Uint8Array(arrayBuffer);
|
|
4536
|
+
const imageBlob = new Blob([uint]);
|
|
4537
|
+
return URL.createObjectURL(imageBlob);
|
|
4538
|
+
};
|
|
4539
|
+
/**
|
|
4540
|
+
* Constructs an Arweave URL for a storage transaction ID
|
|
4541
|
+
* @param storageTransactionId - The Arweave transaction ID
|
|
4542
|
+
* @returns The full URL to access the transaction data on Arweave (e.g., https://arweave.net/raw/{transactionId})
|
|
4543
|
+
* @deprecated Use BaseArweaveClient.getRawUrl() instead for better consistency and testability
|
|
4544
|
+
*/
|
|
4545
|
+
const getArweaveUrlForTransaction = (storageTransactionId) => {
|
|
4546
|
+
return BaseArweaveClient.getRawUrl(storageTransactionId);
|
|
4547
|
+
};
|
|
4548
|
+
|
|
4549
|
+
/**
|
|
4550
|
+
* Save/restore helper for swapping a single facade implementation in tests.
|
|
4551
|
+
*/
|
|
4552
|
+
function createFacadeTestHandle(getImpl, setImpl, next) {
|
|
4553
|
+
const previous = getImpl();
|
|
4554
|
+
setImpl(next);
|
|
4555
|
+
return {
|
|
4556
|
+
restore() {
|
|
4557
|
+
setImpl(previous);
|
|
4558
|
+
},
|
|
4559
|
+
};
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4562
|
+
class BasePathResolver {
|
|
4563
|
+
static configure(impl) {
|
|
4564
|
+
if (!impl) {
|
|
4565
|
+
throw new Error('Cannot configure PathResolver with undefined or null. Ensure the platform-specific PathResolver is properly created.');
|
|
4566
|
+
}
|
|
4567
|
+
BasePathResolver._impl = impl;
|
|
4568
|
+
}
|
|
4569
|
+
static createForTesting(impl) {
|
|
4570
|
+
return createFacadeTestHandle(() => BasePathResolver._impl, (next) => {
|
|
4571
|
+
BasePathResolver._impl = next;
|
|
4572
|
+
}, impl);
|
|
4573
|
+
}
|
|
4574
|
+
static requireImpl() {
|
|
4575
|
+
if (!BasePathResolver._impl) {
|
|
4576
|
+
throw new Error('PathResolver not configured. Please ensure the platform-specific PathResolver is registered. For Node.js, import from @seedprotocol/sdk/node. For browser, the SDK should auto-initialize.');
|
|
4577
|
+
}
|
|
4578
|
+
return BasePathResolver._impl;
|
|
4579
|
+
}
|
|
4580
|
+
/**
|
|
4581
|
+
* @deprecated Prefer static facade methods (e.g. BasePathResolver.getDotSeedDir()).
|
|
4582
|
+
*/
|
|
4583
|
+
static getInstance() {
|
|
4584
|
+
return BasePathResolver.requireImpl();
|
|
4585
|
+
}
|
|
4586
|
+
static getRootWithNodeModules() {
|
|
4587
|
+
return BasePathResolver.requireImpl().getRootWithNodeModules();
|
|
4588
|
+
}
|
|
4589
|
+
static getSdkRootDir() {
|
|
4590
|
+
return BasePathResolver.requireImpl().getSdkRootDir();
|
|
4591
|
+
}
|
|
4592
|
+
static getNodeModulesDir() {
|
|
4593
|
+
return BasePathResolver.requireImpl().getNodeModulesDir();
|
|
4594
|
+
}
|
|
4595
|
+
static getDotSeedDir(schemaFileDir) {
|
|
4596
|
+
return BasePathResolver.requireImpl().getDotSeedDir(schemaFileDir);
|
|
4597
|
+
}
|
|
4598
|
+
static findConfigFile(searchDir) {
|
|
4599
|
+
return BasePathResolver.requireImpl().findConfigFile(searchDir);
|
|
4600
|
+
}
|
|
4601
|
+
static getAppPaths(schemaFileDir) {
|
|
4602
|
+
return BasePathResolver.requireImpl().getAppPaths(schemaFileDir);
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
BasePathResolver._impl = null;
|
|
4606
|
+
|
|
4607
|
+
export { generateId as $, seeds as A, uploadProcesses as B, versions as C, DEFAULT_TEXT_MAX_LENGTH as D, BaseDb as E, AmbiguousModelError as F, eventEmitter as G, toSnakeCase as H, BasePathResolver as I, BaseFileManager as J, getPropertyIdForModelAndName as K, resolveItemModelFileId as L, ModelPropertyDataTypes as M, resolveSeedIdsFromRefString as N, normalizeRelationPropertyValue as O, Property as P, isPublishedSeedRef as Q, getStorageDirForDataType as R, getContentSha256 as S, THtmlEmbeddedDataUriPolicy as T, buildArweaveContentTags as U, getItemStorageSubdir as V, getItemStorageLocalFileName as W, normalizePublisher as X, getAllAddressesFromDb as Y, scheduleBulkFilesDownloadFromEasSync as Z, EAS_SEED_DATA_SYNCED_TO_DB_EVENT as _, TProperty as a, SchemaSchema as a$, normalizeHexAddress as a0, getItemStoragePropertiesForModel as a1, addModelsToDb as a2, INIT_SCRIPT_SUCCESS_MESSAGE as a3, SCHEMA_TS as a4, getCorrectId as a5, SEED_CONFIG_FILE as a6, SEED_CONFIG_FALLBACKS as a7, isNode as a8, ZERO_BYTES32 as a9, normalizeAddressConfig as aA, normalizeAddressList as aB, normalizeArweaveTxIdForEnsure as aC, normalizeFeedItemFields as aD, resolveMediaRef as aE, isInternalSchema as aF, writeModelToDb as aG, writePropertyToDb as aH, addSchemaToDb as aI, renameModelInDb as aJ, loadModelsFromDbForSchema as aK, downloadAllFilesRequestHandler as aL, downloadAllFilesBinaryRequestHandler as aM, INTERNAL_SCHEMA_IDS as aN, linkModelToSchema as aO, getWatchedAddressesFromDb as aP, getOwnedAddressesFromDb as aQ, CLIENT_NOT_INITIALIZED as aR, getStorageTransactionIdForSeedUid as aS, convertTxIdToImage as aT, downloadTransactionIdWithDedupe as aU, getPropertyIdForSchemaFileId as aV, getDataTypeFromString as aW, getMimeType as aX, PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP as aY, ModelSchema as aZ, BaseFileManager$1 as a_, resolveModelRecord as aa, getItemModelScope as ab, VERSION_SCHEMA_UID as ac, INTERNAL_PROPERTY_NAMES as ad, isStorageSeedRef as ae, defaultAttestationData as af, INTERNAL_DATA_TYPES as ag, isBrowser as ah, getModelIdByFileId as ai, getModelId as aj, setCachedPropertyRefresher as ak, getArweaveUrlForTransaction as al, INTERNAL_STORAGE_MODEL_FILE_IDS as am, CONTENT_SHA256_TAG as an, CONTENT_TYPE_TAG as ao, ImageSize as ap, SEED_PROTOCOL_SCHEMA_NAME as aq, SeedModels as ar, VERSION_SCHEMA_DEF as as, VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA as at, classifyMediaRef as au, closestImageSize as av, contentSha256TagMatches as aw, ensureImageLocal as ax, getAddressesForItemsFilter as ay, getFeedItemStringField as az, TPropertyConstructor as b, ModelSchemaSchema as b0, resolveModelRecord$1 as b1, db as b2, download as b3, generateId$1 as b4, TPropertyDataType as c, TPropertyDefs as d, TStorageType as e, TValidationRules as f, normalizePropertyRecordSchema as g, appState as h, isDataType as i, arweaveL1FinalizeJobs as j, config as k, easSyncProcesses as l, metadata as m, normalizeDataType as n, htmlEmbeddedImageCoPublish as o, modelRelations as p, modelSchemas as q, modelUids as r, models as s, modelsRelations as t, properties as u, propertiesRelations as v, propertyUidRelations as w, propertyUids as x, publishProcesses as y, schemas as z };
|
|
4608
|
+
//# sourceMappingURL=BasePathResolver-Cm4apKRb.js.map
|