@seedprotocol/sdk 0.7.9-next.1177.ga3e08ed → 0.7.9-next.1306.g4ff6582
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/BaseFileManager-BAlySv8w.js +650 -0
- package/dist/BaseFileManager-BAlySv8w.js.map +1 -0
- package/dist/{Item-0oDoTPhi.js → Item-Bf4kWKpH.js} +5647 -6573
- package/dist/Item-Bf4kWKpH.js.map +1 -0
- package/dist/{ModelProperty-i5flwzQG.js → ModelProperty-BlaEgb_U.js} +345 -709
- package/dist/ModelProperty-BlaEgb_U.js.map +1 -0
- package/dist/Schema-J_1f6p0c.js +1456 -0
- package/dist/Schema-J_1f6p0c.js.map +1 -0
- package/dist/{SchemaValidationService-DnGKel4V.js → SchemaValidationService-CnmKyzR-.js} +6 -9
- package/dist/{SchemaValidationService-DnGKel4V.js.map → SchemaValidationService-CnmKyzR-.js.map} +1 -1
- package/dist/{configurePlatform-CrMafpMW.js → configurePlatform-CiOJ1R3q.js} +3 -12
- package/dist/configurePlatform-CiOJ1R3q.js.map +1 -0
- package/dist/db/drizzle/0025_add_metadata_retract.sql +1 -0
- package/dist/db/drizzle/0026_add_content_hash_to_schemas.sql +1 -0
- package/dist/db/drizzle/0027_link_metadata_property_ids.sql +74 -0
- package/dist/db/drizzle/0028_add_definition_to_properties.sql +17 -0
- package/dist/db/drizzle/0029_dedupe_chain_uids_add_unique_indexes.sql +120 -0
- package/dist/db/drizzle/meta/0025_snapshot.json +1652 -0
- package/dist/db/drizzle/meta/0026_snapshot.json +1659 -0
- package/dist/db/drizzle/meta/0027_snapshot.json +1659 -0
- package/dist/db/drizzle/meta/0028_snapshot.json +1666 -0
- package/dist/db/drizzle/meta/0029_snapshot.json +1691 -0
- package/dist/db/drizzle/meta/_journal.json +36 -1
- package/dist/{detachRowsBeforeDelete-DXFYSHRR.js → detachRowsBeforeDelete-sEaUwlOU.js} +2 -13
- package/dist/{detachRowsBeforeDelete-DXFYSHRR.js.map → detachRowsBeforeDelete-sEaUwlOU.js.map} +1 -1
- package/dist/dist/{index-Dx_GTdyQ.d.ts → BaseFileManager-BAlySv8w.d.ts} +1424 -2696
- package/dist/dist/BaseFileManager-BAlySv8w.d.ts.map +1 -0
- package/dist/dist/{configurePlatform-CrMafpMW.d.ts → configurePlatform-CiOJ1R3q.d.ts} +1 -1
- package/dist/dist/configurePlatform-CiOJ1R3q.d.ts.map +1 -0
- package/dist/dist/{index-PbnKw2es.d.ts → index-D0V-mjCV.d.ts} +56 -1
- package/dist/dist/{index-PbnKw2es.d.ts.map → index-D0V-mjCV.d.ts.map} +1 -1
- package/dist/dist/{index.node-CzMwMr_d.d.ts → index.node-CYPemH-t.d.ts} +225 -5
- package/dist/dist/{index.node-CzMwMr_d.d.ts.map → index.node-CYPemH-t.d.ts.map} +1 -1
- package/dist/dist/platform.node.d.ts +2 -2
- package/dist/eas-wB_fQ8Tc.js +32 -0
- package/dist/eas-wB_fQ8Tc.js.map +1 -0
- package/dist/errors-DLSoqSHG.js +6550 -0
- package/dist/errors-DLSoqSHG.js.map +1 -0
- package/dist/getPublishPayload-BnIwpwz1.js +760 -0
- package/dist/getPublishPayload-BnIwpwz1.js.map +1 -0
- package/dist/getPublishPendingDiff-D6_-A5ns.js +125 -0
- package/dist/getPublishPendingDiff-D6_-A5ns.js.map +1 -0
- package/dist/getPublishUploads-sS6JfPgz.js +783 -0
- package/dist/getPublishUploads-sS6JfPgz.js.map +1 -0
- package/dist/{index-DJmWkVIK.js → index-BVBmz_cc.js} +11 -14
- package/dist/index-BVBmz_cc.js.map +1 -0
- package/dist/index-CYGWPoOx.js +27 -0
- package/dist/index-CYGWPoOx.js.map +1 -0
- package/dist/{index-PbnKw2es.js → index-D0V-mjCV.js} +2 -2
- package/dist/index-D0V-mjCV.js.map +1 -0
- package/dist/index-DmTtj1nk.js +817 -0
- package/dist/index-DmTtj1nk.js.map +1 -0
- package/dist/{index.browser-i9uPJice.js → index.browser-CUmBVYen.js} +436 -379
- package/dist/index.browser-CUmBVYen.js.map +1 -0
- package/dist/{index.node-CzMwMr_d.js → index.node-CYPemH-t.js} +10 -10
- package/dist/index.node-CYPemH-t.js.map +1 -0
- package/dist/main.js +23 -1105
- package/dist/main.js.map +1 -1
- package/dist/modelPropertyRecordSchemas-C7UgA5TH.js +48 -0
- package/dist/modelPropertyRecordSchemas-C7UgA5TH.js.map +1 -0
- package/dist/node.js +14 -18
- package/dist/node.js.map +1 -1
- package/dist/platform.browser.js +8 -13
- package/dist/platform.browser.js.map +1 -1
- package/dist/platform.node.js +6 -16
- package/dist/platform.node.js.map +1 -1
- package/dist/property-BxUYznH0.js +87 -0
- package/dist/property-BxUYznH0.js.map +1 -0
- package/dist/publishGraph-EcB40Uii.js +311 -0
- package/dist/publishGraph-EcB40Uii.js.map +1 -0
- package/dist/readHtmlBodyForStorageSeed-D6KPtWoR.js +110 -0
- package/dist/readHtmlBodyForStorageSeed-D6KPtWoR.js.map +1 -0
- package/dist/seedSchema/MetadataSchema.ts +3 -0
- package/dist/seedSchema/ModelSchema.ts +4 -0
- package/dist/seedSchema/ModelSchemaSchema.ts +5 -2
- package/dist/seedSchema/PendingWriteSchema.ts +4 -1
- package/dist/seedSchema/SchemaSchema.ts +3 -0
- package/dist/seedSchema/SeedSchema.ts +8 -2
- package/dist/seedSchema/VersionSchema.ts +7 -2
- package/dist/seedSchema/attestedUid.ts +10 -0
- package/dist/src/Item/Item.d.ts +32 -30
- package/dist/src/Item/Item.d.ts.map +1 -1
- package/dist/src/Item/loadItem.d.ts +12 -0
- package/dist/src/Item/loadItem.d.ts.map +1 -0
- package/dist/src/Item/service/itemMachineSingle.d.ts +2 -13
- package/dist/src/Item/service/itemMachineSingle.d.ts.map +1 -1
- package/dist/src/ItemProperty/ItemProperty.d.ts +98 -41
- package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
- package/dist/src/ItemProperty/loadProperty.d.ts +11 -0
- package/dist/src/ItemProperty/loadProperty.d.ts.map +1 -0
- package/dist/src/ItemProperty/propertyWrite.d.ts +54 -0
- package/dist/src/ItemProperty/propertyWrite.d.ts.map +1 -0
- package/dist/src/ItemProperty/{service/actors/saveValueToDb/rowContext.d.ts → rowContext.d.ts} +1 -1
- package/dist/src/ItemProperty/rowContext.d.ts.map +1 -0
- package/dist/src/ItemProperty/service/actors/hydrateFromDb.d.ts +6 -0
- package/dist/src/ItemProperty/service/actors/hydrateFromDb.d.ts.map +1 -1
- package/dist/src/ItemProperty/service/actors/modelPropertyRecordSchemas.d.ts +5 -15
- package/dist/src/ItemProperty/service/actors/modelPropertyRecordSchemas.d.ts.map +1 -1
- package/dist/src/ItemProperty/service/propertyMachine.d.ts +3 -46
- package/dist/src/ItemProperty/service/propertyMachine.d.ts.map +1 -1
- package/dist/src/Model/Model.d.ts +26 -56
- package/dist/src/Model/Model.d.ts.map +1 -1
- package/dist/src/Model/createModelProperties.d.ts +10 -0
- package/dist/src/Model/createModelProperties.d.ts.map +1 -0
- package/dist/src/Model/loadModel.d.ts +13 -0
- package/dist/src/Model/loadModel.d.ts.map +1 -0
- package/dist/src/Model/service/actors/validateModel.d.ts.map +1 -1
- package/dist/src/Model/service/modelMachine.d.ts +5 -114
- package/dist/src/Model/service/modelMachine.d.ts.map +1 -1
- package/dist/src/ModelProperty/ModelProperty.d.ts +44 -37
- package/dist/src/ModelProperty/ModelProperty.d.ts.map +1 -1
- package/dist/src/ModelProperty/service/actors/compareAndMarkDraft.d.ts.map +1 -1
- package/dist/src/ModelProperty/service/actors/validateProperty.d.ts.map +1 -1
- package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +6 -77
- package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts.map +1 -1
- package/dist/src/Schema/Schema.d.ts +36 -37
- package/dist/src/Schema/Schema.d.ts.map +1 -1
- package/dist/src/Schema/loadOrCreateSchema.d.ts +21 -0
- package/dist/src/Schema/loadOrCreateSchema.d.ts.map +1 -0
- package/dist/src/Schema/service/schemaMachine.d.ts +7 -125
- package/dist/src/Schema/service/schemaMachine.d.ts.map +1 -1
- package/dist/src/browser/db/Db.d.ts.map +1 -1
- package/dist/src/browser/db/changeFeed.d.ts +2 -18
- package/dist/src/browser/db/changeFeed.d.ts.map +1 -1
- package/dist/src/browser/db/drizzleFiles.d.ts +7 -2
- package/dist/src/browser/db/drizzleFiles.d.ts.map +1 -1
- package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
- package/dist/src/browser/workers/imageResize.d.ts.map +1 -1
- package/dist/src/client/ClientManager.d.ts +90 -57
- package/dist/src/client/ClientManager.d.ts.map +1 -1
- package/dist/src/client/actors/addModelsToDb.d.ts +9 -7
- package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
- package/dist/src/client/actors/dbInit.d.ts +3 -3
- package/dist/src/client/actors/dbInit.d.ts.map +1 -1
- package/dist/src/client/actors/fileSystemInit.d.ts +3 -3
- package/dist/src/client/actors/fileSystemInit.d.ts.map +1 -1
- package/dist/src/client/actors/platformClassesInit.d.ts +10 -5
- package/dist/src/client/actors/platformClassesInit.d.ts.map +1 -1
- package/dist/src/client/actors/processSchemaFiles.d.ts +18 -3
- package/dist/src/client/actors/processSchemaFiles.d.ts.map +1 -1
- package/dist/src/client/actors/saveConfig.d.ts +6 -3
- package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
- package/dist/src/client/clientManagerMachine.d.ts +22 -20
- package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
- package/dist/src/client/constants.d.ts +1 -9
- package/dist/src/client/constants.d.ts.map +1 -1
- package/dist/src/client/readiness.d.ts +26 -0
- package/dist/src/client/readiness.d.ts.map +1 -0
- package/dist/src/db/read/currentPropertyRow.d.ts +61 -0
- package/dist/src/db/read/currentPropertyRow.d.ts.map +1 -0
- package/dist/src/db/read/findSeedRow.d.ts +20 -0
- package/dist/src/db/read/findSeedRow.d.ts.map +1 -0
- package/dist/src/db/read/findSeedRowsByUid.d.ts +4 -1
- package/dist/src/db/read/findSeedRowsByUid.d.ts.map +1 -1
- package/dist/src/db/read/getAttesterForSeed.d.ts +1 -1
- package/dist/src/db/read/getAttesterForSeed.d.ts.map +1 -1
- package/dist/src/db/read/getItemData.d.ts.map +1 -1
- package/dist/src/db/read/getItemProperties.d.ts +5 -0
- package/dist/src/db/read/getItemProperties.d.ts.map +1 -1
- package/dist/src/db/read/getMetadataAttestationUidsForSeedUid.d.ts.map +1 -1
- package/dist/src/db/read/getModelSchemas.d.ts +0 -7
- package/dist/src/db/read/getModelSchemas.d.ts.map +1 -1
- package/dist/src/db/read/getModelsData.d.ts.map +1 -1
- package/dist/src/db/read/getPropertyClear.d.ts +49 -0
- package/dist/src/db/read/getPropertyClear.d.ts.map +1 -0
- package/dist/src/db/read/getPublishPayload.d.ts +35 -40
- package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
- package/dist/src/db/read/getPublishPendingDiff.d.ts +7 -2
- package/dist/src/db/read/getPublishPendingDiff.d.ts.map +1 -1
- package/dist/src/db/read/getPublishUploads.d.ts +59 -28
- package/dist/src/db/read/getPublishUploads.d.ts.map +1 -1
- package/dist/src/db/read/getSchemaUidForModel.d.ts.map +1 -1
- package/dist/src/db/read/getSeedPublishState.d.ts.map +1 -1
- package/dist/src/db/read/getSeedPublisher.d.ts.map +1 -1
- package/dist/src/db/read/getVersionsForSeedUid.d.ts.map +1 -1
- package/dist/src/db/read/isSeedRevoked.d.ts.map +1 -1
- package/dist/src/db/read/localModelSchemaLookups.d.ts +8 -0
- package/dist/src/db/read/localModelSchemaLookups.d.ts.map +1 -0
- package/dist/src/db/read/metadataProperty.d.ts +104 -0
- package/dist/src/db/read/metadataProperty.d.ts.map +1 -0
- package/dist/src/db/read/publishGraph.d.ts +148 -0
- package/dist/src/db/read/publishGraph.d.ts.map +1 -0
- package/dist/src/db/read/resolveModelRecord.d.ts +26 -2
- package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -1
- package/dist/src/db/read/resolveRelatedSeedRef.d.ts +1 -7
- package/dist/src/db/read/resolveRelatedSeedRef.d.ts.map +1 -1
- package/dist/src/db/read/subqueries/metadataLatest.d.ts +3 -1
- package/dist/src/db/read/subqueries/metadataLatest.d.ts.map +1 -1
- package/dist/src/db/read/summarizePublishWork.d.ts +18 -5
- package/dist/src/db/read/summarizePublishWork.d.ts.map +1 -1
- package/dist/src/db/write/applyPropertyAttestationUidsFromPublish.d.ts +20 -21
- package/dist/src/db/write/applyPropertyAttestationUidsFromPublish.d.ts.map +1 -1
- package/dist/src/db/write/createMetadata.d.ts.map +1 -1
- package/dist/src/db/write/createNewItem.d.ts.map +1 -1
- package/dist/src/db/write/createSeeds.d.ts +5 -1
- package/dist/src/db/write/createSeeds.d.ts.map +1 -1
- package/dist/src/db/write/definitions.d.ts +127 -0
- package/dist/src/db/write/definitions.d.ts.map +1 -0
- package/dist/src/db/write/markPropertyAttestationsRetracted.d.ts +11 -0
- package/dist/src/db/write/markPropertyAttestationsRetracted.d.ts.map +1 -0
- package/dist/src/db/write/mergeChainRows.d.ts +22 -0
- package/dist/src/db/write/mergeChainRows.d.ts.map +1 -0
- package/dist/src/db/write/saveDefinition.d.ts +29 -0
- package/dist/src/db/write/saveDefinition.d.ts.map +1 -0
- package/dist/src/db/write/updateItemPropertyValue.d.ts +2 -0
- package/dist/src/db/write/updateItemPropertyValue.d.ts.map +1 -1
- package/dist/src/db/write/updateSeedUid.d.ts +4 -2
- package/dist/src/db/write/updateSeedUid.d.ts.map +1 -1
- package/dist/src/db/write/updateVersionUid.d.ts +12 -4
- package/dist/src/db/write/updateVersionUid.d.ts.map +1 -1
- package/dist/src/db/write/writeModelUids.d.ts +10 -0
- package/dist/src/db/write/writeModelUids.d.ts.map +1 -0
- package/dist/src/eas.d.ts +8 -6
- package/dist/src/eas.d.ts.map +1 -1
- package/dist/src/events/files/download.d.ts.map +1 -1
- package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
- package/dist/src/helpers/arweaveContentTags.d.ts +1 -1
- package/dist/src/helpers/arweaveContentTags.d.ts.map +1 -1
- package/dist/src/helpers/compareMetadataRowsLatestFirst.d.ts +27 -12
- package/dist/src/helpers/compareMetadataRowsLatestFirst.d.ts.map +1 -1
- package/dist/src/helpers/constants.d.ts +5 -2
- package/dist/src/helpers/constants.d.ts.map +1 -1
- package/dist/src/helpers/dataUrl.d.ts +10 -0
- package/dist/src/helpers/dataUrl.d.ts.map +1 -0
- package/dist/src/helpers/db.d.ts +54 -105
- package/dist/src/helpers/db.d.ts.map +1 -1
- package/dist/src/helpers/ensureImageLocal.d.ts.map +1 -1
- package/dist/src/helpers/entity/contextUpdate.d.ts +20 -0
- package/dist/src/helpers/entity/contextUpdate.d.ts.map +1 -0
- package/dist/src/helpers/entity/draftSaveErrors.d.ts +5 -0
- package/dist/src/helpers/entity/draftSaveErrors.d.ts.map +1 -1
- package/dist/src/helpers/entity/entityCache.d.ts +65 -3
- package/dist/src/helpers/entity/entityCache.d.ts.map +1 -1
- package/dist/src/helpers/entity/entityCommon.d.ts +0 -22
- package/dist/src/helpers/entity/entityCommon.d.ts.map +1 -1
- package/dist/src/helpers/entity/entityLifecycle.d.ts +75 -0
- package/dist/src/helpers/entity/entityLifecycle.d.ts.map +1 -0
- package/dist/src/helpers/entity/entityLiveQuery.d.ts +1 -23
- package/dist/src/helpers/entity/entityLiveQuery.d.ts.map +1 -1
- package/dist/src/helpers/htmlEmbeddedDataUriPublish.d.ts +4 -9
- package/dist/src/helpers/htmlEmbeddedDataUriPublish.d.ts.map +1 -1
- package/dist/src/helpers/index.d.ts +2 -24
- package/dist/src/helpers/index.d.ts.map +1 -1
- package/dist/src/helpers/localImageBlobUrl.d.ts +7 -0
- package/dist/src/helpers/localImageBlobUrl.d.ts.map +1 -0
- package/dist/src/helpers/mediaRef.d.ts +3 -70
- package/dist/src/helpers/mediaRef.d.ts.map +1 -1
- package/dist/src/helpers/metadataPropertyNames.d.ts +34 -21
- package/dist/src/helpers/metadataPropertyNames.d.ts.map +1 -1
- package/dist/src/helpers/mimeTypes.d.ts +2 -1
- package/dist/src/helpers/mimeTypes.d.ts.map +1 -1
- package/dist/src/helpers/model.d.ts.map +1 -1
- package/dist/src/helpers/ownership.d.ts.map +1 -1
- package/dist/src/helpers/property.d.ts +21 -26
- package/dist/src/helpers/property.d.ts.map +1 -1
- package/dist/src/helpers/propertyDefinition.d.ts +59 -0
- package/dist/src/helpers/propertyDefinition.d.ts.map +1 -0
- package/dist/src/helpers/readHtmlBodyForStorageSeed.d.ts.map +1 -1
- package/dist/src/helpers/relationSeedRef.d.ts +0 -7
- package/dist/src/helpers/relationSeedRef.d.ts.map +1 -1
- package/dist/src/helpers/schema.d.ts +24 -38
- package/dist/src/helpers/schema.d.ts.map +1 -1
- package/dist/src/helpers/schemaApply.d.ts +135 -0
- package/dist/src/helpers/schemaApply.d.ts.map +1 -0
- package/dist/src/helpers/tabEvents.d.ts.map +1 -1
- package/dist/src/helpers/updateSchema.d.ts +2 -18
- package/dist/src/helpers/updateSchema.d.ts.map +1 -1
- package/dist/src/imports/index.d.ts +1 -1
- package/dist/src/imports/index.d.ts.map +1 -1
- package/dist/src/imports/json.d.ts +8 -78
- package/dist/src/imports/json.d.ts.map +1 -1
- package/dist/src/index.d.ts +16 -14
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/interfaces/IItem.d.ts +5 -0
- package/dist/src/interfaces/IItem.d.ts.map +1 -1
- package/dist/src/interfaces/IItemProperty.d.ts +13 -7
- package/dist/src/interfaces/IItemProperty.d.ts.map +1 -1
- package/dist/src/node/db/Db.d.ts.map +1 -1
- package/dist/src/node/index.d.ts +3 -2
- package/dist/src/node/index.d.ts.map +1 -1
- package/dist/src/outbox/conflicts.d.ts +2 -0
- package/dist/src/outbox/conflicts.d.ts.map +1 -1
- package/dist/src/outbox/itemAppliers.d.ts +70 -14
- package/dist/src/outbox/itemAppliers.d.ts.map +1 -1
- package/dist/src/outbox/outbox.d.ts +14 -3
- package/dist/src/outbox/outbox.d.ts.map +1 -1
- package/dist/src/outbox/pendingValues.d.ts +3 -1
- package/dist/src/outbox/pendingValues.d.ts.map +1 -1
- package/dist/src/outbox/storageValueRow.d.ts +7 -0
- package/dist/src/outbox/storageValueRow.d.ts.map +1 -0
- package/dist/src/platform/configurePlatform.d.ts.map +1 -1
- package/dist/src/platform/index.browser.d.ts.map +1 -1
- package/dist/src/platform/index.node.d.ts.map +1 -1
- package/dist/src/seedSchema/MetadataSchema.d.ts +17 -0
- package/dist/src/seedSchema/MetadataSchema.d.ts.map +1 -1
- package/dist/src/seedSchema/ModelSchema.d.ts +19 -0
- package/dist/src/seedSchema/ModelSchema.d.ts.map +1 -1
- package/dist/src/seedSchema/ModelSchemaSchema.d.ts.map +1 -1
- package/dist/src/seedSchema/PendingWriteSchema.d.ts.map +1 -1
- package/dist/src/seedSchema/SchemaSchema.d.ts +19 -0
- package/dist/src/seedSchema/SchemaSchema.d.ts.map +1 -1
- package/dist/src/seedSchema/SeedSchema.d.ts.map +1 -1
- package/dist/src/seedSchema/VersionSchema.d.ts.map +1 -1
- package/dist/src/seedSchema/attestedUid.d.ts +9 -0
- package/dist/src/seedSchema/attestedUid.d.ts.map +1 -0
- package/dist/src/storageValue/content.d.ts +45 -0
- package/dist/src/storageValue/content.d.ts.map +1 -0
- package/dist/src/storageValue/findFile.d.ts +7 -0
- package/dist/src/storageValue/findFile.d.ts.map +1 -0
- package/dist/src/storageValue/resolve.d.ts +71 -0
- package/dist/src/storageValue/resolve.d.ts.map +1 -0
- package/dist/src/storageValue/save.d.ts +24 -0
- package/dist/src/storageValue/save.d.ts.map +1 -0
- package/dist/src/storageValue/stage.d.ts +22 -0
- package/dist/src/storageValue/stage.d.ts.map +1 -0
- package/dist/src/types/db.d.ts +0 -14
- package/dist/src/types/db.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +3 -3
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/machines.d.ts +1 -1
- package/dist/src/types/machines.d.ts.map +1 -1
- package/dist/src/types/property.d.ts +9 -16
- package/dist/src/types/property.d.ts.map +1 -1
- package/dist/src/types/publish.d.ts +1 -1
- package/dist/src/types/publish.d.ts.map +1 -1
- package/dist/{syncDbWithEas-DUu_wjUG.js → syncDbWithEas-CA7PvBgu.js} +142 -168
- package/dist/syncDbWithEas-CA7PvBgu.js.map +1 -0
- package/dist/tabEvents-CaVmngqr.js +3804 -0
- package/dist/tabEvents-CaVmngqr.js.map +1 -0
- package/dist/{tolerantWebAccess-DecKDGen.js → tolerantWebAccess-BJqViq08.js} +10 -15
- package/dist/{tolerantWebAccess-DecKDGen.js.map → tolerantWebAccess-BJqViq08.js.map} +1 -1
- package/dist/{resolveModelForSyncedSeed-4gO9Hfn2.js → updateSeedRevokedAt-BL9Bg-G7.js} +30 -4
- package/dist/updateSeedRevokedAt-BL9Bg-G7.js.map +1 -0
- package/package.json +6 -6
- package/dist/ClientManager-GZfXErDX.js +0 -5279
- package/dist/ClientManager-GZfXErDX.js.map +0 -1
- package/dist/Item-0oDoTPhi.js.map +0 -1
- package/dist/ModelProperty-i5flwzQG.js.map +0 -1
- package/dist/Schema-PFZwG9Nl.js +0 -4370
- package/dist/Schema-PFZwG9Nl.js.map +0 -1
- package/dist/backfillMetadataPropertyIds-BbxgavEQ.js +0 -72
- package/dist/backfillMetadataPropertyIds-BbxgavEQ.js.map +0 -1
- package/dist/changeFeed-DKdVlUmt.js +0 -726
- package/dist/changeFeed-DKdVlUmt.js.map +0 -1
- package/dist/configurePlatform-CrMafpMW.js.map +0 -1
- package/dist/dist/ClientManager-GZfXErDX.d.ts +0 -952
- package/dist/dist/ClientManager-GZfXErDX.d.ts.map +0 -1
- package/dist/dist/Item-0oDoTPhi.d.ts +0 -1076
- package/dist/dist/Item-0oDoTPhi.d.ts.map +0 -1
- package/dist/dist/ModelProperty-i5flwzQG.d.ts +0 -315
- package/dist/dist/ModelProperty-i5flwzQG.d.ts.map +0 -1
- package/dist/dist/Schema-PFZwG9Nl.d.ts +0 -432
- package/dist/dist/Schema-PFZwG9Nl.d.ts.map +0 -1
- package/dist/dist/SchemaValidationService-DnGKel4V.d.ts +0 -118
- package/dist/dist/SchemaValidationService-DnGKel4V.d.ts.map +0 -1
- package/dist/dist/backfillMetadataPropertyIds-BbxgavEQ.d.ts +0 -7
- package/dist/dist/backfillMetadataPropertyIds-BbxgavEQ.d.ts.map +0 -1
- package/dist/dist/changeFeed-DKdVlUmt.d.ts +0 -160
- package/dist/dist/changeFeed-DKdVlUmt.d.ts.map +0 -1
- package/dist/dist/configurePlatform-CrMafpMW.d.ts.map +0 -1
- package/dist/dist/detachRowsBeforeDelete-DXFYSHRR.d.ts +0 -13
- package/dist/dist/detachRowsBeforeDelete-DXFYSHRR.d.ts.map +0 -1
- package/dist/dist/eas-dyepi5jz.d.ts +0 -12
- package/dist/dist/eas-dyepi5jz.d.ts.map +0 -1
- package/dist/dist/errors-C1qUEZ7A.d.ts +0 -25
- package/dist/dist/errors-C1qUEZ7A.d.ts.map +0 -1
- package/dist/dist/errors-D1zHAqjr.d.ts +0 -515
- package/dist/dist/errors-D1zHAqjr.d.ts.map +0 -1
- package/dist/dist/eventBus-7vxTCc3a.d.ts +0 -18
- package/dist/dist/eventBus-7vxTCc3a.d.ts.map +0 -1
- package/dist/dist/findSeedRowsByUid-CT3sOXXw.d.ts +0 -13
- package/dist/dist/findSeedRowsByUid-CT3sOXXw.d.ts.map +0 -1
- package/dist/dist/getItem-Bhjecbia.d.ts +0 -6
- package/dist/dist/getItem-Bhjecbia.d.ts.map +0 -1
- package/dist/dist/getPublishPayload-DH4rMqXG.d.ts +0 -47
- package/dist/dist/getPublishPayload-DH4rMqXG.d.ts.map +0 -1
- package/dist/dist/getPublishUploads-BzUIDA2G.d.ts +0 -89
- package/dist/dist/getPublishUploads-BzUIDA2G.d.ts.map +0 -1
- package/dist/dist/index-CkYb3wcU.d.ts +0 -14
- package/dist/dist/index-CkYb3wcU.d.ts.map +0 -1
- package/dist/dist/index-DJmWkVIK.d.ts +0 -6
- package/dist/dist/index-DJmWkVIK.d.ts.map +0 -1
- package/dist/dist/index-Dx_GTdyQ.d.ts.map +0 -1
- package/dist/dist/index.browser-i9uPJice.d.ts +0 -9908
- package/dist/dist/index.browser-i9uPJice.d.ts.map +0 -1
- package/dist/dist/mimeTypes-C9r76DCH.d.ts +0 -30
- package/dist/dist/mimeTypes-C9r76DCH.d.ts.map +0 -1
- package/dist/dist/model-oHA3YhOd.d.ts +0 -6
- package/dist/dist/model-oHA3YhOd.d.ts.map +0 -1
- package/dist/dist/modelPropertyRecordSchemas-C5lW9aA-.d.ts +0 -7
- package/dist/dist/modelPropertyRecordSchemas-C5lW9aA-.d.ts.map +0 -1
- package/dist/dist/platform.browser.d.ts +0 -3
- package/dist/dist/platform.browser.d.ts.map +0 -1
- package/dist/dist/property-BwYtg5Q_.d.ts +0 -37
- package/dist/dist/property-BwYtg5Q_.d.ts.map +0 -1
- package/dist/dist/propertyValidationRules-CyHdpcPP.d.ts +0 -13
- package/dist/dist/propertyValidationRules-CyHdpcPP.d.ts.map +0 -1
- package/dist/dist/publishDraftGraph-C5F6b7GK.d.ts +0 -93
- package/dist/dist/publishDraftGraph-C5F6b7GK.d.ts.map +0 -1
- package/dist/dist/resolveModelForSyncedSeed-4gO9Hfn2.d.ts +0 -21
- package/dist/dist/resolveModelForSyncedSeed-4gO9Hfn2.d.ts.map +0 -1
- package/dist/dist/syncDbWithEas-DUu_wjUG.d.ts +0 -13
- package/dist/dist/syncDbWithEas-DUu_wjUG.d.ts.map +0 -1
- package/dist/dist/tolerantWebAccess-DecKDGen.d.ts +0 -113
- package/dist/dist/tolerantWebAccess-DecKDGen.d.ts.map +0 -1
- package/dist/dist/updateSeedRevokedAt-CP606vKs.d.ts +0 -11
- package/dist/dist/updateSeedRevokedAt-CP606vKs.d.ts.map +0 -1
- package/dist/eas-dyepi5jz.js +0 -67
- package/dist/eas-dyepi5jz.js.map +0 -1
- package/dist/errors-D1zHAqjr.js +0 -4006
- package/dist/errors-D1zHAqjr.js.map +0 -1
- package/dist/eventBus-7vxTCc3a.js +0 -64
- package/dist/eventBus-7vxTCc3a.js.map +0 -1
- package/dist/findSeedRowsByUid-CT3sOXXw.js +0 -47
- package/dist/findSeedRowsByUid-CT3sOXXw.js.map +0 -1
- package/dist/getItem-Bhjecbia.js +0 -45
- package/dist/getItem-Bhjecbia.js.map +0 -1
- package/dist/getPublishPayload-DH4rMqXG.js +0 -1634
- package/dist/getPublishPayload-DH4rMqXG.js.map +0 -1
- package/dist/getPublishUploads-BzUIDA2G.js +0 -1080
- package/dist/getPublishUploads-BzUIDA2G.js.map +0 -1
- package/dist/index-CkYb3wcU.js +0 -29
- package/dist/index-CkYb3wcU.js.map +0 -1
- package/dist/index-DJmWkVIK.js.map +0 -1
- package/dist/index-Dx_GTdyQ.js +0 -4745
- package/dist/index-Dx_GTdyQ.js.map +0 -1
- package/dist/index-PbnKw2es.js.map +0 -1
- package/dist/index.browser-i9uPJice.js.map +0 -1
- package/dist/index.node-CzMwMr_d.js.map +0 -1
- package/dist/mimeTypes-C9r76DCH.js +0 -209
- package/dist/mimeTypes-C9r76DCH.js.map +0 -1
- package/dist/model-oHA3YhOd.js +0 -29
- package/dist/model-oHA3YhOd.js.map +0 -1
- package/dist/modelPropertyRecordSchemas-C5lW9aA-.js +0 -94
- package/dist/modelPropertyRecordSchemas-C5lW9aA-.js.map +0 -1
- package/dist/property-BwYtg5Q_.js +0 -289
- package/dist/property-BwYtg5Q_.js.map +0 -1
- package/dist/propertyValidationRules-CyHdpcPP.js +0 -73
- package/dist/propertyValidationRules-CyHdpcPP.js.map +0 -1
- package/dist/publishDraftGraph-C5F6b7GK.js +0 -505
- package/dist/publishDraftGraph-C5F6b7GK.js.map +0 -1
- package/dist/resolveModelForSyncedSeed-4gO9Hfn2.js.map +0 -1
- package/dist/src/Item/service/actors/loadOrCreateItem.d.ts +0 -5
- package/dist/src/Item/service/actors/loadOrCreateItem.d.ts.map +0 -1
- package/dist/src/Item/service/actors/waitForDb.d.ts +0 -4
- package/dist/src/Item/service/actors/waitForDb.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/loadOrCreateProperty.d.ts +0 -5
- package/dist/src/ItemProperty/service/actors/loadOrCreateProperty.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts +0 -5
- package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/index.d.ts +0 -6
- package/dist/src/ItemProperty/service/actors/saveValueToDb/index.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/rowContext.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveFile.d.ts +0 -24
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveFile.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveHtml.d.ts +0 -5
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveHtml.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveImage.d.ts +0 -5
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveImage.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts +0 -10
- package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/saveValueToDb/storageValue.d.ts +0 -22
- package/dist/src/ItemProperty/service/actors/saveValueToDb/storageValue.d.ts.map +0 -1
- package/dist/src/ItemProperty/service/actors/waitForDb.d.ts +0 -4
- package/dist/src/ItemProperty/service/actors/waitForDb.d.ts.map +0 -1
- package/dist/src/Model/service/actors/createModelProperties.d.ts +0 -9
- package/dist/src/Model/service/actors/createModelProperties.d.ts.map +0 -1
- package/dist/src/Model/service/actors/loadOrCreateModel.d.ts +0 -5
- package/dist/src/Model/service/actors/loadOrCreateModel.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/checkExistingSchema.d.ts +0 -5
- package/dist/src/Schema/service/actors/checkExistingSchema.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/createModelInstances.d.ts +0 -11
- package/dist/src/Schema/service/actors/createModelInstances.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/createPropertyInstances.d.ts +0 -12
- package/dist/src/Schema/service/actors/createPropertyInstances.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/validateSchema.d.ts +0 -5
- package/dist/src/Schema/service/actors/validateSchema.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/verifyModelInstancesInCache.d.ts +0 -6
- package/dist/src/Schema/service/actors/verifyModelInstancesInCache.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/verifyModelsInDb.d.ts +0 -7
- package/dist/src/Schema/service/actors/verifyModelsInDb.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/verifyPropertiesInDb.d.ts +0 -8
- package/dist/src/Schema/service/actors/verifyPropertiesInDb.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/verifyPropertyInstancesInCache.d.ts +0 -6
- package/dist/src/Schema/service/actors/verifyPropertyInstancesInCache.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/verifySchemaInDb.d.ts +0 -7
- package/dist/src/Schema/service/actors/verifySchemaInDb.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/writeModelsToDb.d.ts +0 -9
- package/dist/src/Schema/service/actors/writeModelsToDb.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/writePropertiesToDb.d.ts +0 -10
- package/dist/src/Schema/service/actors/writePropertiesToDb.d.ts.map +0 -1
- package/dist/src/Schema/service/actors/writeSchemaToDb.d.ts +0 -11
- package/dist/src/Schema/service/actors/writeSchemaToDb.d.ts.map +0 -1
- package/dist/src/browser/helpers/ArweaveClient.d.ts +0 -14
- package/dist/src/browser/helpers/ArweaveClient.d.ts.map +0 -1
- package/dist/src/browser/helpers/EasClient.d.ts +0 -10
- package/dist/src/browser/helpers/EasClient.d.ts.map +0 -1
- package/dist/src/browser/helpers/QueryClient.d.ts +0 -9
- package/dist/src/browser/helpers/QueryClient.d.ts.map +0 -1
- package/dist/src/client/actors/addModelsToStore.d.ts +0 -4
- package/dist/src/client/actors/addModelsToStore.d.ts.map +0 -1
- package/dist/src/db/backfillMetadataPropertyIds.d.ts +0 -7
- package/dist/src/db/backfillMetadataPropertyIds.d.ts.map +0 -1
- package/dist/src/db/read/getItem.d.ts +0 -3
- package/dist/src/db/read/getItem.d.ts.map +0 -1
- package/dist/src/db/read/getPropertyData.d.ts +0 -4
- package/dist/src/db/read/getPropertyData.d.ts.map +0 -1
- package/dist/src/db/read/getRelatedItemsForPublish.d.ts +0 -10
- package/dist/src/db/read/getRelatedItemsForPublish.d.ts.map +0 -1
- package/dist/src/db/read/getSeedData.d.ts +0 -12
- package/dist/src/db/read/getSeedData.d.ts.map +0 -1
- package/dist/src/db/read/itemLoads.d.ts +0 -20
- package/dist/src/db/read/itemLoads.d.ts.map +0 -1
- package/dist/src/db/read/publishDraftGraph.d.ts +0 -64
- package/dist/src/db/read/publishDraftGraph.d.ts.map +0 -1
- package/dist/src/db/write/linkModelToSchema.d.ts +0 -12
- package/dist/src/db/write/linkModelToSchema.d.ts.map +0 -1
- package/dist/src/helpers/ArweaveClient/BaseArweaveClient.d.ts +0 -2
- package/dist/src/helpers/ArweaveClient/BaseArweaveClient.d.ts.map +0 -1
- package/dist/src/helpers/ArweaveClient/arweaveL1UploadApi.d.ts +0 -2
- package/dist/src/helpers/ArweaveClient/arweaveL1UploadApi.d.ts.map +0 -1
- package/dist/src/helpers/ArweaveClient/selectReadGateway.d.ts +0 -2
- package/dist/src/helpers/ArweaveClient/selectReadGateway.d.ts.map +0 -1
- package/dist/src/helpers/ArweaveClient/uploadApiVerification.d.ts +0 -2
- package/dist/src/helpers/ArweaveClient/uploadApiVerification.d.ts.map +0 -1
- package/dist/src/helpers/EasClient/BaseEasClient.d.ts +0 -2
- package/dist/src/helpers/EasClient/BaseEasClient.d.ts.map +0 -1
- package/dist/src/helpers/QueryClient/BaseQueryClient.d.ts +0 -2
- package/dist/src/helpers/QueryClient/BaseQueryClient.d.ts.map +0 -1
- package/dist/src/helpers/easUid.d.ts +0 -2
- package/dist/src/helpers/easUid.d.ts.map +0 -1
- package/dist/src/helpers/entity/entityDestroy.d.ts +0 -49
- package/dist/src/helpers/entity/entityDestroy.d.ts.map +0 -1
- package/dist/src/helpers/entity/entityFind.d.ts +0 -52
- package/dist/src/helpers/entity/entityFind.d.ts.map +0 -1
- package/dist/src/helpers/entity/entityState.d.ts +0 -28
- package/dist/src/helpers/entity/entityState.d.ts.map +0 -1
- package/dist/src/helpers/entity/entityUnload.d.ts +0 -56
- package/dist/src/helpers/entity/entityUnload.d.ts.map +0 -1
- package/dist/src/helpers/entity/evictionEpoch.d.ts +0 -43
- package/dist/src/helpers/entity/evictionEpoch.d.ts.map +0 -1
- package/dist/src/helpers/entity/index.d.ts +0 -12
- package/dist/src/helpers/entity/index.d.ts.map +0 -1
- package/dist/src/helpers/gateway/gatewayState.d.ts +0 -2
- package/dist/src/helpers/gateway/gatewayState.d.ts.map +0 -1
- package/dist/src/helpers/gateway/resolveSeedGatewayEndpoints.d.ts +0 -2
- package/dist/src/helpers/gateway/resolveSeedGatewayEndpoints.d.ts.map +0 -1
- package/dist/src/helpers/gateway/seedGatewayConfig.d.ts +0 -2
- package/dist/src/helpers/gateway/seedGatewayConfig.d.ts.map +0 -1
- package/dist/src/helpers/getSchemaForItemProperty.d.ts +0 -15
- package/dist/src/helpers/getSchemaForItemProperty.d.ts.map +0 -1
- package/dist/src/helpers/getSegmentedItemProperties.d.ts +0 -10
- package/dist/src/helpers/getSegmentedItemProperties.d.ts.map +0 -1
- package/dist/src/helpers/isSqliteUniqueConstraintError.d.ts +0 -7
- package/dist/src/helpers/isSqliteUniqueConstraintError.d.ts.map +0 -1
- package/dist/src/helpers/propertyValidationRules.d.ts +0 -20
- package/dist/src/helpers/propertyValidationRules.d.ts.map +0 -1
- package/dist/src/helpers/propertyWriteTickets.d.ts +0 -11
- package/dist/src/helpers/propertyWriteTickets.d.ts.map +0 -1
- package/dist/src/helpers/reconcilePropertyDraftRows.d.ts +0 -10
- package/dist/src/helpers/reconcilePropertyDraftRows.d.ts.map +0 -1
- package/dist/src/helpers/waitForEntityIdle.d.ts +0 -21
- package/dist/src/helpers/waitForEntityIdle.d.ts.map +0 -1
- package/dist/src/interfaces/IQueryClient.d.ts +0 -15
- package/dist/src/interfaces/IQueryClient.d.ts.map +0 -1
- package/dist/src/node/helpers/ArweaveClient.d.ts +0 -5
- package/dist/src/node/helpers/ArweaveClient.d.ts.map +0 -1
- package/dist/src/node/helpers/EasClient.d.ts +0 -5
- package/dist/src/node/helpers/EasClient.d.ts.map +0 -1
- package/dist/src/node/helpers/QueryClient.d.ts +0 -5
- package/dist/src/node/helpers/QueryClient.d.ts.map +0 -1
- package/dist/src/services/write/actors/validateEntity.d.ts +0 -6
- package/dist/src/services/write/actors/validateEntity.d.ts.map +0 -1
- package/dist/src/services/write/actors/writeToDatabase.d.ts +0 -14
- package/dist/src/services/write/actors/writeToDatabase.d.ts.map +0 -1
- package/dist/src/services/write/writeProcessMachine.d.ts +0 -56
- package/dist/src/services/write/writeProcessMachine.d.ts.map +0 -1
- package/dist/src/stores/eas.d.ts +0 -2
- package/dist/src/stores/eas.d.ts.map +0 -1
- package/dist/src/types/arweave.d.ts +0 -57
- package/dist/src/types/arweave.d.ts.map +0 -1
- package/dist/src/types/gateway.d.ts +0 -2
- package/dist/src/types/gateway.d.ts.map +0 -1
- package/dist/syncDbWithEas-DUu_wjUG.js.map +0 -1
- package/dist/updateSeedRevokedAt-CP606vKs.js +0 -31
- package/dist/updateSeedRevokedAt-CP606vKs.js.map +0 -1
package/dist/index-Dx_GTdyQ.js
DELETED
|
@@ -1,4745 +0,0 @@
|
|
|
1
|
-
import debug from 'debug';
|
|
2
|
-
import { isEasAttestationExplorerUrl, BaseArweaveClient, ensureReadGatewaySelected, getArweaveReadBaseUrls, getTransactionTagsByIds } from '@seedprotocol/arweave';
|
|
3
|
-
import { VERSION_SCHEMA_UID, easName, DEFAULT_NAME_MATCH_LEVEL, compareNameMatchTiers, isNameMatchLevel, localNamesInBestMatchTier, BaseQueryClient, BaseEasClient, GET_FILES_METADATA } from '@seedprotocol/eas';
|
|
4
|
-
import { waitFor } from 'xstate';
|
|
5
|
-
import pluralize from 'pluralize';
|
|
6
|
-
import { Type } from '@sinclair/typebox';
|
|
7
|
-
import { sqliteTable, integer, text, unique, int, index, blob, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
8
|
-
import { relations, and, eq, inArray, desc, isNull, isNotNull, or } from 'drizzle-orm';
|
|
9
|
-
import { customAlphabet } from 'nanoid';
|
|
10
|
-
import { alphanumeric } from 'nanoid-dictionary';
|
|
11
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
12
|
-
import { sha3_256 } from '@noble/hashes/sha3';
|
|
13
|
-
import { bytesToHex } from '@noble/hashes/utils';
|
|
14
|
-
import { throttle } from 'lodash-es';
|
|
15
|
-
|
|
16
|
-
const logger$5 = debug('seedSdk:browser:db:sqliteBusyRetry');
|
|
17
|
-
/** Waits between attempts; about 1 s in total. */
|
|
18
|
-
const SQLITE_BUSY_RETRY_DELAYS_MS = [25, 75, 150, 300, 450];
|
|
19
|
-
/**
|
|
20
|
-
* Another connection (usually another tab) holds the database lock. SQLite reports this as
|
|
21
|
-
* SQLITE_BUSY ("database is locked") or SQLITE_LOCKED ("database table is locked").
|
|
22
|
-
*/
|
|
23
|
-
function isSqliteBusyError(error) {
|
|
24
|
-
// Drizzle wraps a driver's error ("Failed query: ...") and keeps it as `cause`
|
|
25
|
-
for (let current = error, depth = 0; current != null && depth < 5; depth++) {
|
|
26
|
-
const message = current instanceof Error ? current.message : String(current);
|
|
27
|
-
if (/SQLITE_BUSY|SQLITE_LOCKED|database (table )?is locked/i.test(message))
|
|
28
|
-
return true;
|
|
29
|
-
current = current.cause;
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Retries a statement another tab's lock rejected. Safe for the SDK's writes because each is a
|
|
35
|
-
* single autocommit statement: one that failed with SQLITE_BUSY didn't apply. Don't use this for
|
|
36
|
-
* statements inside a transaction, where a retry would replay part of it.
|
|
37
|
-
*/
|
|
38
|
-
function withSqliteBusyRetry(driver, delaysMs = SQLITE_BUSY_RETRY_DELAYS_MS) {
|
|
39
|
-
return async (...args) => {
|
|
40
|
-
for (let attempt = 0;; attempt++) {
|
|
41
|
-
try {
|
|
42
|
-
return await driver(...args);
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
if (!isSqliteBusyError(error) || attempt >= delaysMs.length)
|
|
46
|
-
throw error;
|
|
47
|
-
logger$5(`database busy (attempt ${attempt + 1}); retrying`);
|
|
48
|
-
await new Promise((resolve) => setTimeout(resolve, delaysMs[attempt]));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
class BaseDb {
|
|
55
|
-
static configure(impl) {
|
|
56
|
-
if (!impl) {
|
|
57
|
-
throw new Error('Cannot configure Db with undefined or null. Ensure the platform-specific Db is properly created.');
|
|
58
|
-
}
|
|
59
|
-
BaseDb._impl = impl;
|
|
60
|
-
}
|
|
61
|
-
static requireImpl() {
|
|
62
|
-
if (!BaseDb._impl) {
|
|
63
|
-
throw new Error('Db not configured. Call BaseDb.configure() during platform init.');
|
|
64
|
-
}
|
|
65
|
-
return BaseDb._impl;
|
|
66
|
-
}
|
|
67
|
-
static getAppDb() {
|
|
68
|
-
return BaseDb.requireImpl().getAppDb();
|
|
69
|
-
}
|
|
70
|
-
static prepareDb(filesDir, config) {
|
|
71
|
-
return BaseDb.requireImpl().prepareDb(filesDir, config);
|
|
72
|
-
}
|
|
73
|
-
static isAppDbReady() {
|
|
74
|
-
if (!BaseDb._impl) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
return BaseDb._impl.isAppDbReady();
|
|
78
|
-
}
|
|
79
|
-
static connectToDb(pathToDir) {
|
|
80
|
-
return BaseDb.requireImpl().connectToDb(pathToDir);
|
|
81
|
-
}
|
|
82
|
-
static async migrate(pathToDbDir, dbName, dbId) {
|
|
83
|
-
return BaseDb.requireImpl().migrate(pathToDbDir, dbName, dbId);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Execute a reactive query that emits new results whenever the underlying data changes.
|
|
87
|
-
*
|
|
88
|
-
* Supports two usage patterns:
|
|
89
|
-
* 1. SQL tag function (browser only): liveQuery((sql) => sql`SELECT * FROM models`)
|
|
90
|
-
* 2. Drizzle query builder: liveQuery(db.select().from(models))
|
|
91
|
-
*
|
|
92
|
-
* On Node only the query builder works (a tag function throws), and it is re-run every second;
|
|
93
|
-
* the observable emits when the result changes.
|
|
94
|
-
*/
|
|
95
|
-
static liveQuery(query) {
|
|
96
|
-
return BaseDb.requireImpl().liveQuery(query);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Runs `fn` in one transaction: every query it runs on `tx` (a Drizzle database, used like
|
|
100
|
-
* `getAppDb()`) commits together, or none does when `fn` throws. Queries on `getAppDb()` inside
|
|
101
|
-
* `fn` are not part of the transaction, and in the browser they wait for it to end, so don't
|
|
102
|
-
* call `getAppDb()` or `BaseDb.transaction` from `fn`.
|
|
103
|
-
*
|
|
104
|
-
* When another connection (another tab) holds the database lock, the whole transaction is
|
|
105
|
-
* retried, so `fn` must have no side effects outside `tx`.
|
|
106
|
-
*/
|
|
107
|
-
static async transaction(fn) {
|
|
108
|
-
const impl = BaseDb.requireImpl();
|
|
109
|
-
for (let attempt = 0;; attempt++) {
|
|
110
|
-
try {
|
|
111
|
-
return await impl.transaction(fn);
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
if (!isSqliteBusyError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length)
|
|
115
|
-
throw error;
|
|
116
|
-
await new Promise((resolve) => setTimeout(resolve, SQLITE_BUSY_RETRY_DELAYS_MS[attempt]));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
BaseDb._impl = null;
|
|
122
|
-
|
|
123
|
-
var BaseDb$1 = /*#__PURE__*/Object.freeze({
|
|
124
|
-
__proto__: null,
|
|
125
|
-
BaseDb: BaseDb
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const models = sqliteTable('models', {
|
|
129
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
130
|
-
name: text('name').notNull(),
|
|
131
|
-
schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking - must be unique
|
|
132
|
-
isEdited: integer('is_edited', { mode: 'boolean' }), // true if model has been edited locally, false if matches schema file
|
|
133
|
-
}, (table) => {
|
|
134
|
-
return {
|
|
135
|
-
uniqueSchemaFileId: unique('unique_schema_file_id').on(table.schemaFileId),
|
|
136
|
-
};
|
|
137
|
-
});
|
|
138
|
-
const modelsRelations = relations(models, ({ many }) => ({
|
|
139
|
-
properties: many(properties),
|
|
140
|
-
}));
|
|
141
|
-
const properties = sqliteTable('properties', {
|
|
142
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
143
|
-
name: text('name').notNull(),
|
|
144
|
-
dataType: text('data_type').notNull(),
|
|
145
|
-
modelId: integer('model_id')
|
|
146
|
-
.notNull()
|
|
147
|
-
.references(() => models.id),
|
|
148
|
-
refModelId: integer('ref_model_id').references(() => models.id),
|
|
149
|
-
refValueType: text('ref_value_type'),
|
|
150
|
-
schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking
|
|
151
|
-
isEdited: integer('is_edited', { mode: 'boolean' }), // true if property has been edited locally, false if matches schema file
|
|
152
|
-
required: integer('required', { mode: 'boolean' }), // true if relation/image property is required (publish fails when related item not found)
|
|
153
|
-
// Storage settings from the schema file's `storage: { type, path, extension }`, in the internal
|
|
154
|
-
// shape ModelProperty uses. Null when the property has no storage config.
|
|
155
|
-
storageType: text('storage_type'), // 'ItemStorage' | 'PropertyStorage'
|
|
156
|
-
localStorageDir: text('local_storage_dir'), // storage.path, e.g. '/html'
|
|
157
|
-
filenameSuffix: text('filename_suffix'), // storage.extension, e.g. '.html'
|
|
158
|
-
}, (table) => {
|
|
159
|
-
return {
|
|
160
|
-
uniqueNameModelId: unique('unique_name_model_id').on(table.name, table.modelId),
|
|
161
|
-
uniqueSchemaFileId: unique('unique_property_schema_file_id').on(table.schemaFileId),
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
const propertiesRelations = relations(properties, ({ one }) => ({
|
|
165
|
-
model: one(models),
|
|
166
|
-
refModel: one(models),
|
|
167
|
-
}));
|
|
168
|
-
|
|
169
|
-
var ModelSchema = /*#__PURE__*/Object.freeze({
|
|
170
|
-
__proto__: null,
|
|
171
|
-
models: models,
|
|
172
|
-
modelsRelations: modelsRelations,
|
|
173
|
-
properties: properties,
|
|
174
|
-
propertiesRelations: propertiesRelations
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
class BaseFileManager {
|
|
178
|
-
static configure(impl) {
|
|
179
|
-
if (!impl) {
|
|
180
|
-
throw new Error('Cannot configure FileManager with undefined or null. Ensure the platform-specific FileManager is properly created.');
|
|
181
|
-
}
|
|
182
|
-
BaseFileManager._impl = impl;
|
|
183
|
-
}
|
|
184
|
-
static requireImpl() {
|
|
185
|
-
if (!BaseFileManager._impl) {
|
|
186
|
-
throw new Error('FileManager not configured. Call BaseFileManager.configure() during platform init.');
|
|
187
|
-
}
|
|
188
|
-
return BaseFileManager._impl;
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Clears FS init flags so a later call can retry. Used after a failed init
|
|
192
|
-
* and by tests. Does not unmount or delete any files.
|
|
193
|
-
*/
|
|
194
|
-
static resetInitializationState() {
|
|
195
|
-
this.fileSystemInitialized = false;
|
|
196
|
-
this.initializing = false;
|
|
197
|
-
this.initializePromise = null;
|
|
198
|
-
this.workingDir = undefined;
|
|
199
|
-
}
|
|
200
|
-
static initializeFileSystem(workingDir) {
|
|
201
|
-
if (this.fileSystemInitialized) {
|
|
202
|
-
return Promise.resolve();
|
|
203
|
-
}
|
|
204
|
-
if (this.initializePromise) {
|
|
205
|
-
return this.initializePromise;
|
|
206
|
-
}
|
|
207
|
-
this.initializing = true;
|
|
208
|
-
const pending = this.runInitializeFileSystem(workingDir).finally(() => {
|
|
209
|
-
this.initializing = false;
|
|
210
|
-
if (this.initializePromise === pending) {
|
|
211
|
-
this.initializePromise = null;
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
this.initializePromise = pending;
|
|
215
|
-
return pending;
|
|
216
|
-
}
|
|
217
|
-
static async runInitializeFileSystem(workingDir) {
|
|
218
|
-
await BaseFileManager.requireImpl().initializeFileSystem(workingDir);
|
|
219
|
-
this.fileSystemInitialized = true;
|
|
220
|
-
this.workingDir = workingDir;
|
|
221
|
-
}
|
|
222
|
-
static getWorkingDir() {
|
|
223
|
-
if (!this.workingDir) {
|
|
224
|
-
throw new Error('Working directory is not set');
|
|
225
|
-
}
|
|
226
|
-
return this.workingDir;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Build a path under the configured files root (e.g. /app-files).
|
|
230
|
-
* Use this instead of hardcoding /files/ for images, html, json, etc.
|
|
231
|
-
* @param subpaths - path segments to join (e.g. 'images', fileName)
|
|
232
|
-
* @returns full path like /app-files/images/egg.jpg
|
|
233
|
-
*/
|
|
234
|
-
static getFilesPath(...subpaths) {
|
|
235
|
-
const root = this.getWorkingDir().replace(/\/$/, '');
|
|
236
|
-
const parts = [root, ...subpaths].filter(Boolean);
|
|
237
|
-
return parts.join('/').replace(/\/+/g, '/');
|
|
238
|
-
}
|
|
239
|
-
static getContentUrlFromPath(path) {
|
|
240
|
-
return BaseFileManager.requireImpl().getContentUrlFromPath(path);
|
|
241
|
-
}
|
|
242
|
-
static downloadAllFiles(params) {
|
|
243
|
-
return BaseFileManager.requireImpl().downloadAllFiles(params);
|
|
244
|
-
}
|
|
245
|
-
static downloadFileByTransactionId({ transactionId, arweaveHost, arweaveBaseUrls, excludedTransactions, expectedContentHash, compositeChildPaths, skipExisting, }) {
|
|
246
|
-
return BaseFileManager.requireImpl().downloadAllFiles({
|
|
247
|
-
transactionIds: [transactionId],
|
|
248
|
-
arweaveHost,
|
|
249
|
-
arweaveBaseUrls,
|
|
250
|
-
excludedTransactions,
|
|
251
|
-
expectedContentHashes: expectedContentHash ? { [transactionId]: expectedContentHash } : undefined,
|
|
252
|
-
compositeChildPaths: compositeChildPaths ? { [transactionId]: compositeChildPaths } : undefined,
|
|
253
|
-
skipExisting,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
static resizeImage({ filePath, width, height }) {
|
|
257
|
-
return BaseFileManager.requireImpl().resizeImage({ filePath, width, height });
|
|
258
|
-
}
|
|
259
|
-
static resizeAllImages({ width, height }) {
|
|
260
|
-
return BaseFileManager.requireImpl().resizeAllImages({ width, height });
|
|
261
|
-
}
|
|
262
|
-
static pathExists(filePath) {
|
|
263
|
-
return BaseFileManager.requireImpl().pathExists(filePath);
|
|
264
|
-
}
|
|
265
|
-
static invalidateCachedPaths(filePaths) {
|
|
266
|
-
return BaseFileManager.requireImpl().invalidateCachedPaths(filePaths);
|
|
267
|
-
}
|
|
268
|
-
/** File size in bytes, or null if the path does not exist / cannot be stated. */
|
|
269
|
-
static getFileSize(filePath) {
|
|
270
|
-
return BaseFileManager.requireImpl().getFileSize(filePath);
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Returns a list of filenames in the given directory (e.g. 'images', 'files').
|
|
274
|
-
*/
|
|
275
|
-
static listFiles(dir) {
|
|
276
|
-
return BaseFileManager.requireImpl().listFiles(dir);
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Returns a list of image filenames in the images folder (originals only, excludes size subdirs).
|
|
280
|
-
* Use this to get all stored images without traversing 480/760/1024/1440/1920 subdirectories.
|
|
281
|
-
*/
|
|
282
|
-
static listImageFiles() {
|
|
283
|
-
return this.listFiles('images');
|
|
284
|
-
}
|
|
285
|
-
static createDirIfNotExists(filePath) {
|
|
286
|
-
return BaseFileManager.requireImpl().createDirIfNotExists(filePath);
|
|
287
|
-
}
|
|
288
|
-
static async waitForFile(filePath) {
|
|
289
|
-
return BaseFileManager.requireImpl().waitForFile(filePath);
|
|
290
|
-
}
|
|
291
|
-
static async waitForFileWithContent(filePath, interval, timeout) {
|
|
292
|
-
return BaseFileManager.requireImpl().waitForFileWithContent(filePath, interval, timeout);
|
|
293
|
-
}
|
|
294
|
-
static async saveFile(filePath, content) {
|
|
295
|
-
return BaseFileManager.requireImpl().saveFile(filePath, content);
|
|
296
|
-
}
|
|
297
|
-
static saveFileSync(filePath, content) {
|
|
298
|
-
return BaseFileManager.requireImpl().saveFileSync(filePath, content);
|
|
299
|
-
}
|
|
300
|
-
static async readFile(filePath) {
|
|
301
|
-
return BaseFileManager.requireImpl().readFile(filePath);
|
|
302
|
-
}
|
|
303
|
-
static readFileSync(filePath) {
|
|
304
|
-
return BaseFileManager.requireImpl().readFileSync(filePath);
|
|
305
|
-
}
|
|
306
|
-
static async readFileAsBuffer(filePath) {
|
|
307
|
-
return BaseFileManager.requireImpl().readFileAsBuffer(filePath);
|
|
308
|
-
}
|
|
309
|
-
static async readFileAsString(filePath) {
|
|
310
|
-
return BaseFileManager.requireImpl().readFileAsString(filePath);
|
|
311
|
-
}
|
|
312
|
-
static async getFs() {
|
|
313
|
-
return BaseFileManager.requireImpl().getFs();
|
|
314
|
-
}
|
|
315
|
-
static getFsSync() {
|
|
316
|
-
return BaseFileManager.requireImpl().getFsSync();
|
|
317
|
-
}
|
|
318
|
-
static getPathModule() {
|
|
319
|
-
return BaseFileManager.requireImpl().getPathModule();
|
|
320
|
-
}
|
|
321
|
-
static getParentDirPath(filePath) {
|
|
322
|
-
return BaseFileManager.requireImpl().getParentDirPath(filePath);
|
|
323
|
-
}
|
|
324
|
-
static getFilenameFromPath(filePath) {
|
|
325
|
-
return BaseFileManager.requireImpl().getFilenameFromPath(filePath);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
BaseFileManager.fileSystemInitialized = false;
|
|
329
|
-
BaseFileManager.initializing = false;
|
|
330
|
-
BaseFileManager._impl = null;
|
|
331
|
-
BaseFileManager.initializePromise = null;
|
|
332
|
-
|
|
333
|
-
var BaseFileManager$1 = /*#__PURE__*/Object.freeze({
|
|
334
|
-
__proto__: null,
|
|
335
|
-
BaseFileManager: BaseFileManager
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
/** Default maximum length for Text properties when not explicitly set. Html holds arbitrary-length content and does not use this limit. */
|
|
339
|
-
const DEFAULT_TEXT_MAX_LENGTH = 255;
|
|
340
|
-
/**
|
|
341
|
-
* TypeBox schema for ValidationRules
|
|
342
|
-
*/
|
|
343
|
-
const TValidationRules = Type.Object({
|
|
344
|
-
pattern: Type.Optional(Type.String()),
|
|
345
|
-
minLength: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
346
|
-
maxLength: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
347
|
-
enum: Type.Optional(Type.Array(Type.Any())),
|
|
348
|
-
custom: Type.Optional(Type.String()),
|
|
349
|
-
}, { additionalProperties: true });
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Enum for all property data types
|
|
353
|
-
*/
|
|
354
|
-
var ModelPropertyDataTypes;
|
|
355
|
-
(function (ModelPropertyDataTypes) {
|
|
356
|
-
ModelPropertyDataTypes["Text"] = "Text";
|
|
357
|
-
ModelPropertyDataTypes["Number"] = "Number";
|
|
358
|
-
ModelPropertyDataTypes["List"] = "List";
|
|
359
|
-
ModelPropertyDataTypes["Relation"] = "Relation";
|
|
360
|
-
ModelPropertyDataTypes["Image"] = "Image";
|
|
361
|
-
ModelPropertyDataTypes["Json"] = "Json";
|
|
362
|
-
ModelPropertyDataTypes["File"] = "File";
|
|
363
|
-
ModelPropertyDataTypes["Boolean"] = "Boolean";
|
|
364
|
-
ModelPropertyDataTypes["Date"] = "Date";
|
|
365
|
-
ModelPropertyDataTypes["Html"] = "Html";
|
|
366
|
-
})(ModelPropertyDataTypes || (ModelPropertyDataTypes = {}));
|
|
367
|
-
/** Known data types in lowercase for normalization */
|
|
368
|
-
const DATA_TYPE_LOWER_MAP = {
|
|
369
|
-
text: 'Text',
|
|
370
|
-
string: 'Text', // JSON Schema convention
|
|
371
|
-
number: 'Number',
|
|
372
|
-
list: 'List',
|
|
373
|
-
relation: 'Relation',
|
|
374
|
-
image: 'Image',
|
|
375
|
-
json: 'Json',
|
|
376
|
-
file: 'File',
|
|
377
|
-
boolean: 'Boolean',
|
|
378
|
-
date: 'Date',
|
|
379
|
-
html: 'Html',
|
|
380
|
-
blob: 'Blob',
|
|
381
|
-
};
|
|
382
|
-
/**
|
|
383
|
-
* Normalize dataType to canonical PascalCase form.
|
|
384
|
-
* Accepts lowercase (e.g. 'text') or mixed case and returns 'Text'.
|
|
385
|
-
* Use when ingesting dataType from JSON schema files.
|
|
386
|
-
*/
|
|
387
|
-
function normalizeDataType(value) {
|
|
388
|
-
if (!value || typeof value !== 'string')
|
|
389
|
-
return value ?? '';
|
|
390
|
-
const lower = value.toLowerCase().trim();
|
|
391
|
-
return DATA_TYPE_LOWER_MAP[lower] ?? value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Case-insensitive dataType check. Use instead of `dataType === 'List'` so schemas
|
|
395
|
-
* (or existing DB rows) with lowercase types like "list" still match.
|
|
396
|
-
*/
|
|
397
|
-
function isDataType(value, expected) {
|
|
398
|
-
if (!value)
|
|
399
|
-
return false;
|
|
400
|
-
return normalizeDataType(value) === expected;
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Normalize a property definition to the internal shape ItemProperty reads (`dataType`,
|
|
404
|
-
* `refValueType`, `ref`, `storageType`, ...). Accepts schema-file shape (`type`, `model`,
|
|
405
|
-
* `items`, `storage`) and any casing. Mirrors the conversion in imports/json createModelFromJson
|
|
406
|
-
* for the fallback paths that read schema-file properties directly.
|
|
407
|
-
*/
|
|
408
|
-
function normalizePropertyRecordSchema(def) {
|
|
409
|
-
if (!def || typeof def !== 'object')
|
|
410
|
-
return def;
|
|
411
|
-
const src = def;
|
|
412
|
-
const out = { ...src };
|
|
413
|
-
const rawType = src.dataType ?? src.type;
|
|
414
|
-
if (typeof rawType === 'string' && rawType)
|
|
415
|
-
out.dataType = normalizeDataType(rawType);
|
|
416
|
-
const ref = src.ref ?? src.refModelName ?? src.items?.model ?? src.model;
|
|
417
|
-
if (ref && !out.ref)
|
|
418
|
-
out.ref = ref;
|
|
419
|
-
if (ref && !out.refModelName)
|
|
420
|
-
out.refModelName = ref;
|
|
421
|
-
const rawRefValueType = src.refValueType ?? src.items?.type;
|
|
422
|
-
if (typeof rawRefValueType === 'string' && rawRefValueType) {
|
|
423
|
-
out.refValueType = normalizeDataType(rawRefValueType);
|
|
424
|
-
}
|
|
425
|
-
else if (out.dataType === ModelPropertyDataTypes.List && ref) {
|
|
426
|
-
out.refValueType = ModelPropertyDataTypes.Relation;
|
|
427
|
-
}
|
|
428
|
-
if (src.storage && typeof src.storage === 'object' && !out.storageType) {
|
|
429
|
-
out.storageType = src.storage.type === 'ItemStorage' ? 'ItemStorage' : 'PropertyStorage';
|
|
430
|
-
if (src.storage.path !== undefined && out.localStorageDir === undefined)
|
|
431
|
-
out.localStorageDir = src.storage.path;
|
|
432
|
-
if (src.storage.extension !== undefined && out.filenameSuffix === undefined)
|
|
433
|
-
out.filenameSuffix = src.storage.extension;
|
|
434
|
-
}
|
|
435
|
-
return out;
|
|
436
|
-
}
|
|
437
|
-
// String literals, not enum members: PropertyDataType must accept plain strings
|
|
438
|
-
// such as `{ dataType: 'Text' }`. ModelPropertyDataTypes members still fit.
|
|
439
|
-
const TPropertyDataType = Type.Union([
|
|
440
|
-
Type.Literal('Text'),
|
|
441
|
-
Type.Literal('Number'),
|
|
442
|
-
Type.Literal('List'),
|
|
443
|
-
Type.Literal('Relation'),
|
|
444
|
-
Type.Literal('Image'),
|
|
445
|
-
Type.Literal('Json'),
|
|
446
|
-
Type.Literal('File'),
|
|
447
|
-
Type.Literal('Boolean'),
|
|
448
|
-
Type.Literal('Date'),
|
|
449
|
-
Type.Literal('Html'),
|
|
450
|
-
]);
|
|
451
|
-
const TStorageType = Type.Union([
|
|
452
|
-
Type.Literal('ItemStorage'), // Looks for a storageTransactionId property on the item
|
|
453
|
-
Type.Literal('PropertyStorage'), // Looks for a storageTransactionId value on the property
|
|
454
|
-
]);
|
|
455
|
-
/** How publish handles `data:image/*` URIs inside Html storage. Only applies when `dataType === 'Html'`. */
|
|
456
|
-
const THtmlEmbeddedDataUriPolicy = Type.Union([
|
|
457
|
-
Type.Literal('materialize'),
|
|
458
|
-
Type.Literal('preserve'),
|
|
459
|
-
]);
|
|
460
|
-
const TProperty = Type.Object({
|
|
461
|
-
id: Type.Optional(Type.String()), // schemaFileId (string) - public ID
|
|
462
|
-
_dbId: Type.Optional(Type.Number()), // Database integer ID - internal only
|
|
463
|
-
name: Type.Optional(Type.String()),
|
|
464
|
-
dataType: TPropertyDataType,
|
|
465
|
-
ref: Type.Optional(Type.String()),
|
|
466
|
-
// modelId: string (modelFileId) in code; number only for DB foreign keys when resolved
|
|
467
|
-
modelId: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
|
468
|
-
modelName: Type.Optional(Type.String()),
|
|
469
|
-
refModelId: Type.Optional(Type.Number()),
|
|
470
|
-
refModelName: Type.Optional(Type.String()),
|
|
471
|
-
refValueType: Type.Optional(TPropertyDataType),
|
|
472
|
-
storageType: Type.Optional(TStorageType),
|
|
473
|
-
localStorageDir: Type.Optional(Type.String()),
|
|
474
|
-
filenameSuffix: Type.Optional(Type.String()),
|
|
475
|
-
validation: Type.Optional(TValidationRules),
|
|
476
|
-
required: Type.Optional(Type.Boolean()),
|
|
477
|
-
/** When set on an Html property: `materialize` uploads embedded images as Image seeds and rewrites src; `preserve` leaves data URIs. */
|
|
478
|
-
htmlEmbeddedDataUriPolicy: Type.Optional(THtmlEmbeddedDataUriPolicy),
|
|
479
|
-
});
|
|
480
|
-
const TPropertyConstructor = Type.Function([
|
|
481
|
-
Type.Optional(Type.Union([Type.String(), TStorageType, Type.Undefined()])),
|
|
482
|
-
Type.Optional(Type.Union([Type.String(), TPropertyDataType])),
|
|
483
|
-
Type.Optional(Type.Union([Type.String(), Type.Boolean()])),
|
|
484
|
-
], TProperty);
|
|
485
|
-
const TPropertyDefs = Type.Record(TPropertyDataType, TPropertyConstructor);
|
|
486
|
-
function htmlPropertyFactory(opts) {
|
|
487
|
-
return {
|
|
488
|
-
dataType: ModelPropertyDataTypes.Html,
|
|
489
|
-
...(opts?.htmlEmbeddedDataUriPolicy
|
|
490
|
-
? { htmlEmbeddedDataUriPolicy: opts.htmlEmbeddedDataUriPolicy }
|
|
491
|
-
: {}),
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
const Property = {
|
|
495
|
-
Text: (storageType, localStorageDir, filenameSuffix) => ({
|
|
496
|
-
dataType: ModelPropertyDataTypes.Text,
|
|
497
|
-
storageType,
|
|
498
|
-
localStorageDir,
|
|
499
|
-
filenameSuffix,
|
|
500
|
-
TObject: Type.String(),
|
|
501
|
-
}),
|
|
502
|
-
Json: () => ({ dataType: ModelPropertyDataTypes.Json }),
|
|
503
|
-
File: () => ({ dataType: ModelPropertyDataTypes.File }),
|
|
504
|
-
Number: () => ({ dataType: ModelPropertyDataTypes.Number }),
|
|
505
|
-
/**
|
|
506
|
-
* Create a List property. refValueType is required; ref is required only for list of relations.
|
|
507
|
-
* @param refValueType - Element type: 'Text', 'Number', 'Relation', etc.
|
|
508
|
-
* @param ref - Model name when refValueType is 'Relation' (optional for primitives)
|
|
509
|
-
* @example List('Text') - list of strings
|
|
510
|
-
* @example List('Relation', 'Tag') - list of relations to Tag model
|
|
511
|
-
*/
|
|
512
|
-
List: (refValueType, ref) => ({
|
|
513
|
-
dataType: ModelPropertyDataTypes.List,
|
|
514
|
-
ref,
|
|
515
|
-
refValueType,
|
|
516
|
-
}),
|
|
517
|
-
Relation: (ref, refValueType, required) => ({
|
|
518
|
-
dataType: ModelPropertyDataTypes.Relation,
|
|
519
|
-
ref,
|
|
520
|
-
refValueType,
|
|
521
|
-
required,
|
|
522
|
-
}),
|
|
523
|
-
Image: () => ({ dataType: ModelPropertyDataTypes.Image }),
|
|
524
|
-
Boolean: () => ({ dataType: ModelPropertyDataTypes.Boolean }),
|
|
525
|
-
Date: () => ({ dataType: ModelPropertyDataTypes.Date }),
|
|
526
|
-
Html: htmlPropertyFactory,
|
|
527
|
-
/** `Html` opts are not representable in `TPropertyConstructor`; keep runtime shape via double assert. */
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
// Define zero constants ourselves since they're not exported from eas-sdk in newer versions
|
|
531
|
-
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
532
|
-
const ZERO_BYTES = '0x';
|
|
533
|
-
const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
534
|
-
const INTERNAL_DATA_TYPES = {
|
|
535
|
-
Text: {
|
|
536
|
-
eas: 'string',
|
|
537
|
-
},
|
|
538
|
-
// ADR 0010: decimal strings, so negatives, fractions and values above 255 publish.
|
|
539
|
-
Number: {
|
|
540
|
-
eas: 'string',
|
|
541
|
-
},
|
|
542
|
-
Image: {
|
|
543
|
-
eas: 'bytes32',
|
|
544
|
-
},
|
|
545
|
-
Relation: {
|
|
546
|
-
eas: 'bytes32',
|
|
547
|
-
},
|
|
548
|
-
List: {
|
|
549
|
-
eas: 'bytes32[]',
|
|
550
|
-
},
|
|
551
|
-
File: {
|
|
552
|
-
eas: 'bytes32',
|
|
553
|
-
},
|
|
554
|
-
Json: {
|
|
555
|
-
eas: 'bytes32',
|
|
556
|
-
},
|
|
557
|
-
Blob: {
|
|
558
|
-
eas: 'bytes32',
|
|
559
|
-
},
|
|
560
|
-
Boolean: {
|
|
561
|
-
eas: 'bool',
|
|
562
|
-
},
|
|
563
|
-
Date: {
|
|
564
|
-
eas: 'uint256',
|
|
565
|
-
},
|
|
566
|
-
Html: {
|
|
567
|
-
eas: 'bytes32',
|
|
568
|
-
},
|
|
569
|
-
};
|
|
570
|
-
/** Full property names ending in `Id` that must not be stripped to a “base” name for `allProperties` keys. */
|
|
571
|
-
const PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP = new Set(['storageTransactionId']);
|
|
572
|
-
const INTERNAL_PROPERTY_NAMES = [
|
|
573
|
-
'localId',
|
|
574
|
-
'uid',
|
|
575
|
-
'seedLocalId',
|
|
576
|
-
'seedUid',
|
|
577
|
-
'schemaUid',
|
|
578
|
-
'attestationCreatedAt',
|
|
579
|
-
'attestationRaw',
|
|
580
|
-
'createdAt',
|
|
581
|
-
'updatedAt',
|
|
582
|
-
'lastVersionPublishedAt',
|
|
583
|
-
'latestVersionLocalId',
|
|
584
|
-
'latestVersionUid',
|
|
585
|
-
'lastLocalUpdateAt',
|
|
586
|
-
'modelName',
|
|
587
|
-
'refSeedType',
|
|
588
|
-
'refValueType',
|
|
589
|
-
'refResolvedValue',
|
|
590
|
-
'refResolvedDisplayValue',
|
|
591
|
-
'type',
|
|
592
|
-
// Image
|
|
593
|
-
'src',
|
|
594
|
-
'alt',
|
|
595
|
-
//
|
|
596
|
-
'versionLocalId',
|
|
597
|
-
'versionsCount',
|
|
598
|
-
'versionUid',
|
|
599
|
-
'_markedForDeletion',
|
|
600
|
-
// SDK-internal: wallet address of attester; never publish as property attestation
|
|
601
|
-
'publisher',
|
|
602
|
-
// SDK-internal: Unix timestamp when attestations were revoked; never a schema field
|
|
603
|
-
'revokedAt',
|
|
604
|
-
];
|
|
605
|
-
/** @deprecated The UID is chain-independent. Use {@link VERSION_SCHEMA_UID}. */
|
|
606
|
-
const VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA = VERSION_SCHEMA_UID;
|
|
607
|
-
const defaultAttestationData = {
|
|
608
|
-
recipient: ZERO_ADDRESS,
|
|
609
|
-
revocable: true,
|
|
610
|
-
value: BigInt(0),
|
|
611
|
-
refUID: ZERO_BYTES32,
|
|
612
|
-
expirationTime: BigInt(0),
|
|
613
|
-
data: ZERO_BYTES,
|
|
614
|
-
};
|
|
615
|
-
var ImageSize;
|
|
616
|
-
(function (ImageSize) {
|
|
617
|
-
ImageSize[ImageSize["EXTRA_SMALL"] = 480] = "EXTRA_SMALL";
|
|
618
|
-
ImageSize[ImageSize["SMALL"] = 760] = "SMALL";
|
|
619
|
-
ImageSize[ImageSize["MEDIUM"] = 1024] = "MEDIUM";
|
|
620
|
-
ImageSize[ImageSize["LARGE"] = 1440] = "LARGE";
|
|
621
|
-
ImageSize[ImageSize["EXTRA_LARGE"] = 1920] = "EXTRA_LARGE";
|
|
622
|
-
})(ImageSize || (ImageSize = {}));
|
|
623
|
-
const CLIENT_NOT_INITIALIZED = 'ClientManager is not initialized. Please call init() first.';
|
|
624
|
-
var SeedModels;
|
|
625
|
-
(function (SeedModels) {
|
|
626
|
-
SeedModels["Seed"] = "Seed";
|
|
627
|
-
SeedModels["Metadata"] = "Metadata";
|
|
628
|
-
SeedModels["Version"] = "Version";
|
|
629
|
-
})(SeedModels || (SeedModels = {}));
|
|
630
|
-
// Internal SDK schema that should not be created in app's files directory
|
|
631
|
-
const SEED_PROTOCOL_SCHEMA_NAME = 'Seed Protocol';
|
|
632
|
-
const INTERNAL_SCHEMA_IDS = ['SEEDPROTOCOL'];
|
|
633
|
-
/** Fired after `syncDbWithEas` persists EAS seeds/versions/metadata to SQLite (see `Item.rehydrateCachedItemsFromDbAfterEasSync`). */
|
|
634
|
-
const EAS_SEED_DATA_SYNCED_TO_DB_EVENT = 'easSeedDataSyncedToDb';
|
|
635
|
-
/**
|
|
636
|
-
* Check if a schema is an internal SDK schema that should not be created in app files
|
|
637
|
-
*/
|
|
638
|
-
function isInternalSchema(schemaName, schemaId) {
|
|
639
|
-
return schemaName === SEED_PROTOCOL_SCHEMA_NAME ||
|
|
640
|
-
(schemaId !== undefined && INTERNAL_SCHEMA_IDS.includes(schemaId));
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Model ids (schemaFileId) of the internal Seed Protocol storage models. Storage seeds are always
|
|
644
|
-
* these models, even if a user schema also defines a model named Image/File/Html.
|
|
645
|
-
*/
|
|
646
|
-
const INTERNAL_STORAGE_MODEL_FILE_IDS = {
|
|
647
|
-
image: 'SEEDPROTOCOL_IMAGE',
|
|
648
|
-
file: 'SEEDPROTOCOL_FILE',
|
|
649
|
-
html: 'SEEDPROTOCOL_HTML',
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
/** Ref model name when propDef is a List of Relation, else undefined. */
|
|
653
|
-
function listRelationRef(propDef) {
|
|
654
|
-
if (!propDef)
|
|
655
|
-
return undefined;
|
|
656
|
-
if (normalizeDataType(propDef.dataType) !== 'List')
|
|
657
|
-
return undefined;
|
|
658
|
-
return propDef.ref || propDef.refModelName || undefined;
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* Storage/EAS name for a List-of-relation property (`authorIdentityIds`), given either its
|
|
662
|
-
* schema key (`authors`) or its storage name. A name already in storage form is returned as-is,
|
|
663
|
-
* so this is safe on ItemProperties built before their schema was known.
|
|
664
|
-
* Returns undefined when propDef is not a List of Relation.
|
|
665
|
-
*/
|
|
666
|
-
function listRelationEasPropertyName(propertyName, propDef) {
|
|
667
|
-
const ref = listRelationRef(propDef);
|
|
668
|
-
if (!ref || !propertyName)
|
|
669
|
-
return undefined;
|
|
670
|
-
if (propertyName.endsWith(`${ref}Ids`))
|
|
671
|
-
return propertyName;
|
|
672
|
-
return `${pluralize(propertyName, 1)}${ref}Ids`;
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* For a schema property key like `authors` (List of Relation to Identity), returns the
|
|
676
|
-
* internal storage/EAS name: `authorIdentityIds` (singular + ref + Ids).
|
|
677
|
-
*/
|
|
678
|
-
function listRelationStoragePropertyName(propertySchemas, schemaPropertyKey) {
|
|
679
|
-
return listRelationEasPropertyName(schemaPropertyKey, propertySchemas[schemaPropertyKey]);
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Reverse of listRelationStoragePropertyName: maps DB/EAS name `authorIdentityIds` to schema key `authors`.
|
|
683
|
-
*/
|
|
684
|
-
function resolveStorageNameToSchemaName(propertySchemas, storagePropertyName) {
|
|
685
|
-
for (const [schemaKey, propDef] of Object.entries(propertySchemas)) {
|
|
686
|
-
if (normalizeDataType(propDef?.dataType) !== 'List')
|
|
687
|
-
continue;
|
|
688
|
-
const ref = propDef.ref || propDef.refModelName;
|
|
689
|
-
if (!ref)
|
|
690
|
-
continue;
|
|
691
|
-
const internalName = `${pluralize(schemaKey, 1)}${ref}Ids`;
|
|
692
|
-
if (internalName === storagePropertyName) {
|
|
693
|
-
return schemaKey;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
return undefined;
|
|
697
|
-
}
|
|
698
|
-
/** Internal property names that never use Id-suffix metadata convention */
|
|
699
|
-
const ID_SUFFIX_EXCLUDE = new Set(['storageTransactionId', 'transactionId']);
|
|
700
|
-
/** Data types that store metadata with Id suffix in DB */
|
|
701
|
-
const METADATA_ID_SUFFIX_TYPES = ['Image', 'File', 'Html', 'Relation'];
|
|
702
|
-
/**
|
|
703
|
-
* Whether this type uses Id suffix in metadata table.
|
|
704
|
-
*/
|
|
705
|
-
function needsMetadataIdSuffix(dataType, refValueType) {
|
|
706
|
-
const dt = normalizeDataType(dataType);
|
|
707
|
-
const rvt = normalizeDataType(refValueType);
|
|
708
|
-
return (METADATA_ID_SUFFIX_TYPES.includes(dt) ||
|
|
709
|
-
METADATA_ID_SUFFIX_TYPES.includes(rvt));
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Canonical metadata property name for WRITES.
|
|
713
|
-
* Use when creating/updating metadata.
|
|
714
|
-
*/
|
|
715
|
-
function toMetadataPropertyName(propertyName, dataType, refValueType) {
|
|
716
|
-
if (ID_SUFFIX_EXCLUDE.has(propertyName))
|
|
717
|
-
return propertyName;
|
|
718
|
-
if (propertyName.endsWith('Ids'))
|
|
719
|
-
return propertyName;
|
|
720
|
-
if (propertyName.endsWith('Id'))
|
|
721
|
-
return propertyName;
|
|
722
|
-
if (needsMetadataIdSuffix(dataType, refValueType)) {
|
|
723
|
-
return `${propertyName}Id`;
|
|
724
|
-
}
|
|
725
|
-
return propertyName;
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* For READS: property names to include in OR query (both variants).
|
|
729
|
-
* When dataType is undefined, includes all variants for safety (backwards compat with getPropertyData).
|
|
730
|
-
*/
|
|
731
|
-
function getMetadataPropertyNamesForQuery(propertyName, dataType, refValueType) {
|
|
732
|
-
if (ID_SUFFIX_EXCLUDE.has(propertyName))
|
|
733
|
-
return [propertyName];
|
|
734
|
-
if (propertyName.endsWith('Ids'))
|
|
735
|
-
return [propertyName];
|
|
736
|
-
if (propertyName.endsWith('Id')) {
|
|
737
|
-
const base = propertyName.slice(0, -2);
|
|
738
|
-
return [propertyName, base];
|
|
739
|
-
}
|
|
740
|
-
const needsId = needsMetadataIdSuffix(dataType, refValueType) ||
|
|
741
|
-
(dataType === undefined && refValueType === undefined);
|
|
742
|
-
if (needsId) {
|
|
743
|
-
return [propertyName, `${propertyName}Id`, `${propertyName}Ids`];
|
|
744
|
-
}
|
|
745
|
-
return [propertyName];
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* The name an item exposes a property under, from the name it is stored under: its alias when it
|
|
749
|
-
* has one (List of relation), else `tagIds` → `tags`, `authorId` → `author`. Names in
|
|
750
|
-
* PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP keep their `Id`.
|
|
751
|
-
*/
|
|
752
|
-
function toItemPropertyKey(storedName, alias) {
|
|
753
|
-
if (alias)
|
|
754
|
-
return alias;
|
|
755
|
-
if (storedName.endsWith('Ids'))
|
|
756
|
-
return pluralize(storedName.slice(0, -3));
|
|
757
|
-
if (storedName.endsWith('Id') && !PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP.has(storedName)) {
|
|
758
|
-
return storedName.slice(0, -2);
|
|
759
|
-
}
|
|
760
|
-
return storedName;
|
|
761
|
-
}
|
|
762
|
-
/**
|
|
763
|
-
* Schema property name from metadata name (strip Id/Ids suffix).
|
|
764
|
-
*/
|
|
765
|
-
function toSchemaPropertyName(propertyName) {
|
|
766
|
-
if (propertyName.endsWith('Ids'))
|
|
767
|
-
return propertyName.slice(0, -3);
|
|
768
|
-
if (propertyName.endsWith('Id'))
|
|
769
|
-
return propertyName.slice(0, -2);
|
|
770
|
-
return undefined;
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Alternate property name for instance lookup (base <-> Id variant).
|
|
774
|
-
*/
|
|
775
|
-
function getAlternatePropertyNameForInstanceLookup(propertyName) {
|
|
776
|
-
if (ID_SUFFIX_EXCLUDE.has(propertyName))
|
|
777
|
-
return undefined;
|
|
778
|
-
if (propertyName.endsWith('Id'))
|
|
779
|
-
return propertyName.slice(0, -2);
|
|
780
|
-
return `${propertyName}Id`;
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Resolve the best metadata record when multiple variants exist (base + Id).
|
|
784
|
-
* For Image/File/Html: prefer the record with refResolvedValue (Id variant has filename).
|
|
785
|
-
*/
|
|
786
|
-
function resolveMetadataRecord(records, preferredPropertyName, dataType, refValueType) {
|
|
787
|
-
if (records.length === 0)
|
|
788
|
-
throw new Error('No records to resolve');
|
|
789
|
-
if (records.length === 1)
|
|
790
|
-
return records[0];
|
|
791
|
-
const needsId = needsMetadataIdSuffix(dataType, refValueType) ||
|
|
792
|
-
(dataType === undefined && refValueType === undefined);
|
|
793
|
-
// For Image/File/Html: prefer the record with refResolvedValue (Id variant has filename)
|
|
794
|
-
if (needsId) {
|
|
795
|
-
const withRef = records.find((r) => r.refResolvedValue);
|
|
796
|
-
if (withRef)
|
|
797
|
-
return withRef;
|
|
798
|
-
}
|
|
799
|
-
// Also prefer by refSeedType when we need file ref
|
|
800
|
-
if (needsId) {
|
|
801
|
-
const byRefSeed = records.find((r) => r.refSeedType === 'image' ||
|
|
802
|
-
r.refSeedType === 'file' ||
|
|
803
|
-
r.refSeedType === 'html');
|
|
804
|
-
if (byRefSeed)
|
|
805
|
-
return byRefSeed;
|
|
806
|
-
}
|
|
807
|
-
// Fallback: prefer exact propertyName match, else first
|
|
808
|
-
return (records.find((r) => r.propertyName === preferredPropertyName) ?? records[0]);
|
|
809
|
-
}
|
|
810
|
-
/**
|
|
811
|
-
* The EAS property name publish writes for an item property (ADR 0008 §3), already in EAS form:
|
|
812
|
-
*
|
|
813
|
-
* - List of relations: its `<name><Model>Ids` storage name (`author_identity_ids`).
|
|
814
|
-
* - Single Relation, Image, File or Html: its schema key (`author`), never the local `<name>Id`
|
|
815
|
-
* storage name. The key is `propertyDef.name` when known, else `propertyName` without its `Id`.
|
|
816
|
-
* - Anything else: the property name.
|
|
817
|
-
*/
|
|
818
|
-
function easPropertyNameForPublish(propertyName, propertyDef) {
|
|
819
|
-
const listRelationName = listRelationEasPropertyName(propertyName, propertyDef ?? undefined);
|
|
820
|
-
if (listRelationName)
|
|
821
|
-
return easName(listRelationName);
|
|
822
|
-
if (propertyDef &&
|
|
823
|
-
normalizeDataType(propertyDef.dataType) !== 'List' &&
|
|
824
|
-
needsMetadataIdSuffix(propertyDef.dataType, propertyDef.refValueType) &&
|
|
825
|
-
!ID_SUFFIX_EXCLUDE.has(propertyName)) {
|
|
826
|
-
const key = propertyDef.name ?? (propertyName.endsWith('Id') ? propertyName.slice(0, -2) : propertyName);
|
|
827
|
-
return easName(key);
|
|
828
|
-
}
|
|
829
|
-
return easName(propertyName);
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* The EAS schema definition publish attests a property under (`string title`, `bytes32 author`),
|
|
833
|
-
* or undefined when its data type has no EAS type. Look schemas up by this, not by name: a name
|
|
834
|
-
* can have schemas of several types (Optimism Sepolia has `bytes32 storage_transaction_id` beside
|
|
835
|
-
* `string storage_transaction_id`).
|
|
836
|
-
*/
|
|
837
|
-
function easSchemaDefinitionForProperty(propertyName, propertyDef) {
|
|
838
|
-
if (!propertyDef?.dataType)
|
|
839
|
-
return undefined;
|
|
840
|
-
const dataType = normalizeDataType(String(propertyDef.dataType));
|
|
841
|
-
const easType = INTERNAL_DATA_TYPES[dataType]?.eas;
|
|
842
|
-
if (!easType)
|
|
843
|
-
return undefined;
|
|
844
|
-
return `${easType} ${easPropertyNameForPublish(propertyName, propertyDef)}`;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/** Same rules as getCorrectId in helpers/index (avoid barrel circular import). */
|
|
848
|
-
function parseLocalIdOrUid(localIdOrUid) {
|
|
849
|
-
const id = { localId: undefined, uid: undefined };
|
|
850
|
-
if (!localIdOrUid)
|
|
851
|
-
return id;
|
|
852
|
-
if (localIdOrUid.length === 10) {
|
|
853
|
-
id.localId = localIdOrUid;
|
|
854
|
-
}
|
|
855
|
-
if (localIdOrUid.startsWith('0x') && localIdOrUid.length === 66) {
|
|
856
|
-
id.uid = localIdOrUid;
|
|
857
|
-
}
|
|
858
|
-
return id;
|
|
859
|
-
}
|
|
860
|
-
/** Relation/image refs may be string ids or `{ seedLocalId?, seedUid? }`. */
|
|
861
|
-
function normalizeRelationPropertyValue(value) {
|
|
862
|
-
if (value == null)
|
|
863
|
-
return undefined;
|
|
864
|
-
if (typeof value === 'string') {
|
|
865
|
-
const t = value.trim();
|
|
866
|
-
return t !== '' ? t : undefined;
|
|
867
|
-
}
|
|
868
|
-
if (typeof value === 'object' && value !== null) {
|
|
869
|
-
const o = value;
|
|
870
|
-
if (typeof o.seedLocalId === 'string' && o.seedLocalId.trim() !== '') {
|
|
871
|
-
return o.seedLocalId.trim();
|
|
872
|
-
}
|
|
873
|
-
if (typeof o.seedUid === 'string' && o.seedUid.trim() !== '') {
|
|
874
|
-
return o.seedUid.trim();
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
return undefined;
|
|
878
|
-
}
|
|
879
|
-
/** getCorrectId only accepts 10-char local ids and 0x66 uids; local seed refs can be up to 21 chars. */
|
|
880
|
-
const LOCAL_SEED_REF = /^[a-zA-Z0-9_-]{10,21}$/;
|
|
881
|
-
/** Resolve seed ids from a single string (context propertyValue or DB row). */
|
|
882
|
-
function resolveSeedIdsFromRefString(s) {
|
|
883
|
-
const fromCorrect = parseLocalIdOrUid(s);
|
|
884
|
-
if (fromCorrect.localId || fromCorrect.uid) {
|
|
885
|
-
return { seedLocalId: fromCorrect.localId, seedUid: fromCorrect.uid };
|
|
886
|
-
}
|
|
887
|
-
if (LOCAL_SEED_REF.test(s) && !s.startsWith('0x')) {
|
|
888
|
-
return { seedLocalId: s };
|
|
889
|
-
}
|
|
890
|
-
return {};
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* True when a relation ref is an attested seed uid (0x…) rather than a local id. A uid only exists once
|
|
894
|
-
* the seed is on EAS, so publish can attest the uid as-is when no local copy exists (e.g. a server
|
|
895
|
-
* publishing on someone's behalf that only holds the uids it was given). Callers must not walk into it.
|
|
896
|
-
*/
|
|
897
|
-
function isPublishedSeedRef(value) {
|
|
898
|
-
const ref = normalizeRelationPropertyValue(value);
|
|
899
|
-
if (!ref)
|
|
900
|
-
return false;
|
|
901
|
-
const { seedLocalId, seedUid } = resolveSeedIdsFromRefString(ref);
|
|
902
|
-
return !!seedUid && !seedLocalId;
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Storage seed local id (nanoid) or 0x uid. Stricter than resolveSeedIdsFromRefString, which accepts
|
|
906
|
-
* any 10-char string (so a 10-char html snippet like "<p>raw</p>" would pass as a local id).
|
|
907
|
-
*/
|
|
908
|
-
const STORAGE_SEED_REF = /^(?:[A-Za-z0-9_-]{10,21}|0x[0-9a-fA-F]{64})$/;
|
|
909
|
-
/** True when a File/Image/Html value is already a storage seed reference rather than raw content. */
|
|
910
|
-
function isStorageSeedRef(value) {
|
|
911
|
-
return typeof value === 'string' && STORAGE_SEED_REF.test(value.trim());
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
const seeds = sqliteTable('seeds', {
|
|
915
|
-
localId: text('local_id').unique(),
|
|
916
|
-
uid: text('uid'),
|
|
917
|
-
schemaUid: text('schema_uid'),
|
|
918
|
-
type: text('type'),
|
|
919
|
-
// schemaFileId of the models row this item belongs to (= Model.id). Disambiguates items whose
|
|
920
|
-
// model name exists in more than one schema; null for legacy rows that couldn't be backfilled.
|
|
921
|
-
modelFileId: text('model_file_id'),
|
|
922
|
-
publisher: text('publisher'),
|
|
923
|
-
attestationRaw: text('attestation_raw'),
|
|
924
|
-
attestationCreatedAt: int('attestation_created_at'),
|
|
925
|
-
// Set on insert when the writer leaves it out. No $onUpdate: readers rank rows by
|
|
926
|
-
// COALESCE(attestation_created_at, created_at), and some writers move it on purpose.
|
|
927
|
-
createdAt: int('created_at').$defaultFn(() => Date.now()),
|
|
928
|
-
// Set on every insert and update unless the writer passes a value.
|
|
929
|
-
updatedAt: int('updated_at')
|
|
930
|
-
.$defaultFn(() => Date.now())
|
|
931
|
-
.$onUpdate(() => Date.now()),
|
|
932
|
-
_markedForDeletion: int('_marked_for_deletion'),
|
|
933
|
-
revokedAt: int('revoked_at'),
|
|
934
|
-
}, (table) => [index('seeds_uid_idx').on(table.uid), index('seeds_type_idx').on(table.type)]);
|
|
935
|
-
|
|
936
|
-
const versions = sqliteTable('versions', {
|
|
937
|
-
localId: text('local_id').unique(),
|
|
938
|
-
uid: text('uid'),
|
|
939
|
-
seedLocalId: text('seed_local_id'),
|
|
940
|
-
seedUid: text('seed_uid'),
|
|
941
|
-
seedType: text('seed_type'),
|
|
942
|
-
note: text('note'),
|
|
943
|
-
// Set on insert when the writer leaves it out. No $onUpdate: readers rank rows by
|
|
944
|
-
// COALESCE(attestation_created_at, created_at), and some writers move it on purpose.
|
|
945
|
-
createdAt: int('created_at').$defaultFn(() => Date.now()),
|
|
946
|
-
// Set on every insert and update unless the writer passes a value.
|
|
947
|
-
updatedAt: int('updated_at')
|
|
948
|
-
.$defaultFn(() => Date.now())
|
|
949
|
-
.$onUpdate(() => Date.now()),
|
|
950
|
-
attestationCreatedAt: int('attestation_created_at'),
|
|
951
|
-
attestationRaw: text('attestation_raw'),
|
|
952
|
-
publisher: text('publisher'),
|
|
953
|
-
/**
|
|
954
|
-
* When the version attestation was revoked, in Unix seconds (EAS `revocationTime`, or the local
|
|
955
|
-
* unpublish time until sync confirms it). Null while live or unpublished.
|
|
956
|
-
*/
|
|
957
|
-
revokedAt: int('revoked_at'),
|
|
958
|
-
}, (table) => [index('versions_seed_local_id_idx').on(table.seedLocalId)]);
|
|
959
|
-
|
|
960
|
-
const metadata = sqliteTable('metadata', {
|
|
961
|
-
localId: text('local_id').unique(),
|
|
962
|
-
uid: text('uid'),
|
|
963
|
-
propertyId: integer('property_id').references(() => properties.id),
|
|
964
|
-
propertyName: text('property_name'),
|
|
965
|
-
propertyValue: text('property_value'),
|
|
966
|
-
schemaUid: text('schema_uid'),
|
|
967
|
-
modelType: text('model_type'),
|
|
968
|
-
seedLocalId: text('seed_local_id'),
|
|
969
|
-
seedUid: text('seed_uid'),
|
|
970
|
-
versionLocalId: text('version_local_id'),
|
|
971
|
-
versionUid: text('version_uid'),
|
|
972
|
-
easDataType: text('eas_data_type'),
|
|
973
|
-
refValueType: text('ref_value_type'),
|
|
974
|
-
refModelUid: text('ref_schema_uid'),
|
|
975
|
-
refSeedType: text('ref_seed_type'),
|
|
976
|
-
refResolvedValue: text('ref_resolved_value'),
|
|
977
|
-
refResolvedDisplayValue: text('ref_resolved_display_value'),
|
|
978
|
-
localStorageDir: text('local_storage_dir'),
|
|
979
|
-
attestationRaw: text('attestation_raw'),
|
|
980
|
-
attestationCreatedAt: int('attestation_created_at'),
|
|
981
|
-
contentHash: text('content_hash'),
|
|
982
|
-
// Set on insert when the writer leaves it out. No $onUpdate: readers rank rows by
|
|
983
|
-
// COALESCE(attestation_created_at, created_at), and some writers move it on purpose.
|
|
984
|
-
createdAt: int('created_at').$defaultFn(() => Date.now()),
|
|
985
|
-
// Set on every insert and update unless the writer passes a value.
|
|
986
|
-
updatedAt: int('updated_at')
|
|
987
|
-
.$defaultFn(() => Date.now())
|
|
988
|
-
.$onUpdate(() => Date.now()),
|
|
989
|
-
publisher: text('publisher'),
|
|
990
|
-
// Unix seconds when this row's attestation was revoked on EAS; null while it's live (or for a
|
|
991
|
-
// local, unpublished row). EAS sync keeps only the canonical attestation per (version, property
|
|
992
|
-
// schema): the newest live one, or the newest revoked one when all are revoked. So a non-null
|
|
993
|
-
// value marks a property kept only for its last value, not one that is present on the version.
|
|
994
|
-
revokedAt: int('revoked_at'),
|
|
995
|
-
// For a row EAS sync derived for an ItemStorage property from a `storage_transaction_id`
|
|
996
|
-
// attestation (uid null, ref_value_type 'file', value = the transaction id): that attestation's
|
|
997
|
-
// uid. Null for every other row. Marks the row as published content, not a local draft.
|
|
998
|
-
derivedFromUid: text('derived_from_uid'),
|
|
999
|
-
}, (table) => [
|
|
1000
|
-
index('metadata_seed_local_id_property_name_idx').on(table.seedLocalId, table.propertyName),
|
|
1001
|
-
index('metadata_seed_uid_idx').on(table.seedUid),
|
|
1002
|
-
]);
|
|
1003
|
-
|
|
1004
|
-
const appState = sqliteTable('appState', {
|
|
1005
|
-
key: text('key').unique(),
|
|
1006
|
-
value: text('value'),
|
|
1007
|
-
createdAt: int('created_at'),
|
|
1008
|
-
updatedAt: int('updated_at'),
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
const modelUids = sqliteTable('model_uids', {
|
|
1012
|
-
id: int('id').primaryKey({ autoIncrement: true }),
|
|
1013
|
-
uid: text('uid').notNull(),
|
|
1014
|
-
modelId: int('model_id')
|
|
1015
|
-
.notNull()
|
|
1016
|
-
.unique()
|
|
1017
|
-
.references(() => models.id),
|
|
1018
|
-
});
|
|
1019
|
-
const modelRelations = relations(modelUids, ({ one }) => ({
|
|
1020
|
-
model: one(models),
|
|
1021
|
-
}));
|
|
1022
|
-
|
|
1023
|
-
const propertyUids = sqliteTable('property_uids', {
|
|
1024
|
-
id: int('id').primaryKey({ autoIncrement: true }),
|
|
1025
|
-
uid: text('uid').notNull(),
|
|
1026
|
-
propertyId: int('property_id')
|
|
1027
|
-
.notNull()
|
|
1028
|
-
.unique()
|
|
1029
|
-
.references(() => properties.id),
|
|
1030
|
-
});
|
|
1031
|
-
const propertyUidRelations = relations(propertyUids, ({ one }) => ({
|
|
1032
|
-
property: one(properties),
|
|
1033
|
-
}));
|
|
1034
|
-
|
|
1035
|
-
const config = sqliteTable('config', {
|
|
1036
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
1037
|
-
key: text('key').notNull(),
|
|
1038
|
-
text: text('text'),
|
|
1039
|
-
json: text('json'),
|
|
1040
|
-
blob: blob('blob'),
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
const schemas = sqliteTable('schemas', {
|
|
1044
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
1045
|
-
name: text('name').notNull(),
|
|
1046
|
-
version: integer('version').notNull(),
|
|
1047
|
-
schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking - must be unique
|
|
1048
|
-
schemaData: text('schema_data'), // Full JSON schema content (SchemaFileFormat as JSON string)
|
|
1049
|
-
isDraft: integer('is_draft', { mode: 'boolean' }), // true if schema is in draft/editing state, false if published to file
|
|
1050
|
-
isEdited: integer('is_edited', { mode: 'boolean' }), // true if schema has been edited locally, false if matches schema file
|
|
1051
|
-
createdAt: int('created_at'),
|
|
1052
|
-
updatedAt: int('updated_at'),
|
|
1053
|
-
}, (table) => {
|
|
1054
|
-
return {
|
|
1055
|
-
uniqueSchemaFileId: unique('unique_schema_schema_file_id').on(table.schemaFileId),
|
|
1056
|
-
};
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
var SchemaSchema = /*#__PURE__*/Object.freeze({
|
|
1060
|
-
__proto__: null,
|
|
1061
|
-
schemas: schemas
|
|
1062
|
-
});
|
|
1063
|
-
|
|
1064
|
-
const modelSchemas = sqliteTable('model_schemas', {
|
|
1065
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
1066
|
-
modelId: integer('model_id').references(() => models.id),
|
|
1067
|
-
schemaId: integer('schema_id').references(() => schemas.id),
|
|
1068
|
-
});
|
|
1069
|
-
|
|
1070
|
-
var ModelSchemaSchema = /*#__PURE__*/Object.freeze({
|
|
1071
|
-
__proto__: null,
|
|
1072
|
-
modelSchemas: modelSchemas
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1075
|
-
const publishProcesses = sqliteTable('publish_processes', {
|
|
1076
|
-
id: int('id').primaryKey({ autoIncrement: true }),
|
|
1077
|
-
seedLocalId: text('seed_local_id').notNull(),
|
|
1078
|
-
modelName: text('model_name').notNull(),
|
|
1079
|
-
schemaId: text('schema_id'),
|
|
1080
|
-
status: text('status').notNull(), // 'in_progress' | 'completed' | 'failed' | 'interrupted'
|
|
1081
|
-
startedAt: int('started_at').notNull(),
|
|
1082
|
-
completedAt: int('completed_at'),
|
|
1083
|
-
errorMessage: text('error_message'),
|
|
1084
|
-
errorStep: text('error_step'),
|
|
1085
|
-
errorDetails: text('error_details'),
|
|
1086
|
-
errorCode: text('error_code'), // e.g. ManagedAccountPublishError.code
|
|
1087
|
-
persistedSnapshot: text('persisted_snapshot').notNull(),
|
|
1088
|
-
seedId: text('seed_id'),
|
|
1089
|
-
existingSeedUid: text('existing_seed_uid'),
|
|
1090
|
-
createdAt: int('created_at'),
|
|
1091
|
-
updatedAt: int('updated_at'),
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
const uploadProcesses = sqliteTable('upload_processes', {
|
|
1095
|
-
id: int('id').primaryKey({ autoIncrement: true }),
|
|
1096
|
-
reimbursementConfirmed: int('reimbursement_confirmed').notNull(), // 0 or 1 for boolean
|
|
1097
|
-
reimbursementTransactionId: text('reimbursement_transaction_id'),
|
|
1098
|
-
transactionKeys: text('transaction_keys'),
|
|
1099
|
-
persistedSnapshot: text('persisted_snapshot').notNull(), // JSON string
|
|
1100
|
-
createdAt: int('created_at'),
|
|
1101
|
-
updatedAt: int('updated_at'),
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
/** Background job: resolve L1 tx id and wait for L1 confirmation after bundler publish. */
|
|
1105
|
-
const arweaveL1FinalizeJobs = sqliteTable('arweave_l1_finalize_jobs', {
|
|
1106
|
-
id: int('id').primaryKey({ autoIncrement: true }),
|
|
1107
|
-
seedLocalId: text('seed_local_id').notNull(),
|
|
1108
|
-
dataItemId: text('data_item_id').notNull(),
|
|
1109
|
-
/** L1 bundle / anchor transaction id from gateway GraphQL `bundledIn.id`. */
|
|
1110
|
-
l1TransactionId: text('l1_transaction_id'),
|
|
1111
|
-
bundleId: text('bundle_id'),
|
|
1112
|
-
versionLocalId: text('version_local_id'),
|
|
1113
|
-
itemPropertyName: text('item_property_name'),
|
|
1114
|
-
/** `pending_l1` | `confirmed` | `failed` */
|
|
1115
|
-
phase: text('phase').notNull(),
|
|
1116
|
-
statusJson: text('status_json'),
|
|
1117
|
-
errorMessage: text('error_message'),
|
|
1118
|
-
createdAt: int('created_at').notNull(),
|
|
1119
|
-
updatedAt: int('updated_at').notNull(),
|
|
1120
|
-
}, (table) => ({
|
|
1121
|
-
dataItemIdUnique: uniqueIndex('arweave_l1_finalize_jobs_data_item_id_unique').on(table.dataItemId),
|
|
1122
|
-
}));
|
|
1123
|
-
|
|
1124
|
-
const easSyncProcesses = sqliteTable('eas_sync_processes', {
|
|
1125
|
-
id: int('id').primaryKey({ autoIncrement: true }),
|
|
1126
|
-
status: text('status').notNull(), // 'in_progress' | 'completed' | 'failed'
|
|
1127
|
-
startedAt: int('started_at').notNull(),
|
|
1128
|
-
completedAt: int('completed_at'),
|
|
1129
|
-
requestPayload: text('request_payload').notNull(),
|
|
1130
|
-
errorMessage: text('error_message'),
|
|
1131
|
-
errorDetails: text('error_details'),
|
|
1132
|
-
persistedSnapshot: text('persisted_snapshot').notNull(),
|
|
1133
|
-
createdAt: int('created_at'),
|
|
1134
|
-
updatedAt: int('updated_at'),
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
/**
|
|
1138
|
-
* Links Image seeds materialized from Html `data:image/*` URIs to the parent Item seed for co-publish.
|
|
1139
|
-
* Rows are removed after a successful publish (see clearHtmlEmbeddedImageCoPublishRows).
|
|
1140
|
-
*/
|
|
1141
|
-
const htmlEmbeddedImageCoPublish = sqliteTable('html_embedded_image_co_publish', {
|
|
1142
|
-
id: int('id').primaryKey({ autoIncrement: true }),
|
|
1143
|
-
parentSeedLocalId: text('parent_seed_local_id').notNull(),
|
|
1144
|
-
htmlSeedLocalId: text('html_seed_local_id').notNull(),
|
|
1145
|
-
imageSeedLocalId: text('image_seed_local_id').notNull(),
|
|
1146
|
-
/** Stable dedup key (e.g. sha256 of data URI or normalized URI string). */
|
|
1147
|
-
stableKey: text('stable_key').notNull(),
|
|
1148
|
-
createdAt: int('created_at').notNull(),
|
|
1149
|
-
}, (t) => [
|
|
1150
|
-
uniqueIndex('html_embed_co_pub_parent_html_stable').on(t.parentSeedLocalId, t.htmlSeedLocalId, t.stableKey),
|
|
1151
|
-
]);
|
|
1152
|
-
|
|
1153
|
-
/**
|
|
1154
|
-
* The item write outbox (ADR 0019): one row per edit to an item, appended when the value is
|
|
1155
|
-
* assigned and applied to `seeds`, `versions` and `metadata` by the leader tab's drainer.
|
|
1156
|
-
*/
|
|
1157
|
-
const pendingWrites = sqliteTable('pending_writes', {
|
|
1158
|
-
id: int('id').primaryKey({ autoIncrement: true }),
|
|
1159
|
-
/** Unique per op; the drainer and `save()` refer to an op by it. */
|
|
1160
|
-
opId: text('op_id').notNull(),
|
|
1161
|
-
/** Unused: nothing appends grouped ops. Kept until a migration drops it. */
|
|
1162
|
-
groupId: text('group_id'),
|
|
1163
|
-
seedLocalId: text('seed_local_id').notNull(),
|
|
1164
|
-
/** Null for ops on the seed itself (`create`, `delete`). */
|
|
1165
|
-
propertyName: text('property_name'),
|
|
1166
|
-
/** `create` | `set` | `delete` | `storage` */
|
|
1167
|
-
kind: text('kind').notNull(),
|
|
1168
|
-
/** JSON-encoded value. */
|
|
1169
|
-
value: text('value'),
|
|
1170
|
-
/** Unused: a storage op's staged path is in its JSON value. Kept until a migration drops it. */
|
|
1171
|
-
valueRef: text('value_ref'),
|
|
1172
|
-
/** The stored metadata row the edit was made against, to detect a synced row written since. */
|
|
1173
|
-
baseRowId: text('base_row_id'),
|
|
1174
|
-
/** Unused: the base uid is read through base_row_id. Kept until a migration drops it. */
|
|
1175
|
-
baseUid: text('base_uid'),
|
|
1176
|
-
createdAt: int('created_at').notNull(),
|
|
1177
|
-
/** The tab that appended the op. */
|
|
1178
|
-
tabId: text('tab_id'),
|
|
1179
|
-
/** `pending` | `applied` | `superseded` | `failed`. An op is claimed and applied in one transaction. */
|
|
1180
|
-
status: text('status').notNull(),
|
|
1181
|
-
error: text('error'),
|
|
1182
|
-
/** The name of the error that failed the op (`OwnershipError`, say), so a waiter can tell why. */
|
|
1183
|
-
errorName: text('error_name'),
|
|
1184
|
-
attempts: int('attempts').notNull().default(0),
|
|
1185
|
-
appliedAt: int('applied_at'),
|
|
1186
|
-
/** JSON the applier recorded when the op applied (for a `set`: the metadata row written). */
|
|
1187
|
-
result: text('result'),
|
|
1188
|
-
/** JSON: the synced value this op was applied over, when there was one (ADR 0019). */
|
|
1189
|
-
conflict: text('conflict'),
|
|
1190
|
-
conflictResolvedAt: int('conflict_resolved_at'),
|
|
1191
|
-
}, (table) => ({
|
|
1192
|
-
opIdUnique: uniqueIndex('pending_writes_op_id_unique').on(table.opId),
|
|
1193
|
-
/** The drainer's order: per property, oldest first, among one status. */
|
|
1194
|
-
drainOrder: index('pending_writes_drain_order').on(table.status, table.seedLocalId, table.propertyName, table.createdAt),
|
|
1195
|
-
}));
|
|
1196
|
-
|
|
1197
|
-
/**
|
|
1198
|
-
* The entity change log (ADR 0020): one row per row written to `seeds`, `versions` or `metadata`,
|
|
1199
|
-
* inserted by triggers (migration 0023), so it commits or rolls back with the write. The browser's
|
|
1200
|
-
* change feed reads it to tell loaded Items and ItemProperties which seeds changed. Nothing else
|
|
1201
|
-
* writes it; the leader tab prunes rows older than 10 minutes.
|
|
1202
|
-
*/
|
|
1203
|
-
const entityChanges = sqliteTable('entity_changes', {
|
|
1204
|
-
seq: int('seq').primaryKey({ autoIncrement: true }),
|
|
1205
|
-
/** `seeds` | `versions` | `metadata` */
|
|
1206
|
-
tbl: text('tbl').notNull(),
|
|
1207
|
-
seedLocalId: text('seed_local_id'),
|
|
1208
|
-
seedUid: text('seed_uid'),
|
|
1209
|
-
/** Set for `metadata` rows only. */
|
|
1210
|
-
propertyName: text('property_name'),
|
|
1211
|
-
/** Unix ms when the trigger ran. */
|
|
1212
|
-
at: int('at').notNull(),
|
|
1213
|
-
}, (table) => ({
|
|
1214
|
-
at: index('entity_changes_at').on(table.at),
|
|
1215
|
-
}));
|
|
1216
|
-
|
|
1217
|
-
const seedUidToStorageTransactionId = new Map();
|
|
1218
|
-
const getStorageTransactionIdForSeedUid = async (seedUid) => {
|
|
1219
|
-
if (seedUidToStorageTransactionId.has(seedUid)) {
|
|
1220
|
-
return seedUidToStorageTransactionId.get(seedUid);
|
|
1221
|
-
}
|
|
1222
|
-
const appDb = BaseDb.getAppDb();
|
|
1223
|
-
const results = (await appDb
|
|
1224
|
-
.select({
|
|
1225
|
-
storageTransactionId: metadata.propertyValue,
|
|
1226
|
-
})
|
|
1227
|
-
.from(metadata)
|
|
1228
|
-
.where(and(eq(metadata.seedUid, seedUid), eq(metadata.propertyName, 'storageTransactionId'))));
|
|
1229
|
-
if (!results || results.length === 0) {
|
|
1230
|
-
return;
|
|
1231
|
-
}
|
|
1232
|
-
seedUidToStorageTransactionId.set(seedUid, results[0].storageTransactionId);
|
|
1233
|
-
return results[0].storageTransactionId;
|
|
1234
|
-
};
|
|
1235
|
-
|
|
1236
|
-
const logger$4 = debug('seedSdk:helpers:mediaRef');
|
|
1237
|
-
/** Typical Arweave transaction id: 43 URL-safe base64 characters. */
|
|
1238
|
-
const ARWEAVE_TX_ID = /^[a-zA-Z0-9_-]{43}$/;
|
|
1239
|
-
function trimOrEmpty(raw) {
|
|
1240
|
-
if (raw == null)
|
|
1241
|
-
return '';
|
|
1242
|
-
return typeof raw === 'string' ? raw.trim() : String(raw).trim();
|
|
1243
|
-
}
|
|
1244
|
-
function coalesceStringField(item, key) {
|
|
1245
|
-
const v = item[key];
|
|
1246
|
-
if (v == null)
|
|
1247
|
-
return '';
|
|
1248
|
-
if (typeof v === 'string')
|
|
1249
|
-
return v.trim();
|
|
1250
|
-
if (typeof v === 'number' || typeof v === 'boolean')
|
|
1251
|
-
return String(v);
|
|
1252
|
-
return '';
|
|
1253
|
-
}
|
|
1254
|
-
/**
|
|
1255
|
-
* Try JSON relation payloads and plain relation id strings.
|
|
1256
|
-
*/
|
|
1257
|
-
function unwrapRelationString(raw) {
|
|
1258
|
-
const t = raw.trim();
|
|
1259
|
-
if (!t)
|
|
1260
|
-
return t;
|
|
1261
|
-
try {
|
|
1262
|
-
const parsed = JSON.parse(t);
|
|
1263
|
-
const fromObj = normalizeRelationPropertyValue(parsed);
|
|
1264
|
-
if (fromObj)
|
|
1265
|
-
return fromObj;
|
|
1266
|
-
}
|
|
1267
|
-
catch {
|
|
1268
|
-
// not JSON
|
|
1269
|
-
}
|
|
1270
|
-
return t;
|
|
1271
|
-
}
|
|
1272
|
-
/**
|
|
1273
|
-
* Pure, sync classification of a media-related string from feeds or XML.
|
|
1274
|
-
*/
|
|
1275
|
-
function classifyMediaRef(raw, options) {
|
|
1276
|
-
const s = trimOrEmpty(raw);
|
|
1277
|
-
if (!s) {
|
|
1278
|
-
return { kind: 'empty' };
|
|
1279
|
-
}
|
|
1280
|
-
const treatAs = options?.treatAs;
|
|
1281
|
-
if (treatAs === 'url') {
|
|
1282
|
-
return { kind: 'url', href: s };
|
|
1283
|
-
}
|
|
1284
|
-
if (treatAs === 'arweaveTx') {
|
|
1285
|
-
return { kind: 'arweaveTxId', txId: s };
|
|
1286
|
-
}
|
|
1287
|
-
if (treatAs === 'seedUid') {
|
|
1288
|
-
if (s.startsWith('0x') && s.length === 66) {
|
|
1289
|
-
return { kind: 'seedUid', uid: s };
|
|
1290
|
-
}
|
|
1291
|
-
return { kind: 'unknown', raw: s };
|
|
1292
|
-
}
|
|
1293
|
-
if (s.startsWith('http://') || s.startsWith('https://') || s.startsWith('blob:') || s.startsWith('data:')) {
|
|
1294
|
-
if (isEasAttestationExplorerUrl(s)) {
|
|
1295
|
-
return { kind: 'nonMediaUrl', href: s };
|
|
1296
|
-
}
|
|
1297
|
-
return { kind: 'url', href: s };
|
|
1298
|
-
}
|
|
1299
|
-
const unwrapped = unwrapRelationString(s);
|
|
1300
|
-
const ids = resolveSeedIdsFromRefString(unwrapped);
|
|
1301
|
-
if (ids.seedUid) {
|
|
1302
|
-
return { kind: 'seedUid', uid: ids.seedUid };
|
|
1303
|
-
}
|
|
1304
|
-
if (ids.seedLocalId) {
|
|
1305
|
-
return { kind: 'seedLocalId', localId: ids.seedLocalId };
|
|
1306
|
-
}
|
|
1307
|
-
if (unwrapped.startsWith('0x') && unwrapped.length === 66) {
|
|
1308
|
-
return { kind: 'seedUid', uid: unwrapped };
|
|
1309
|
-
}
|
|
1310
|
-
if (ARWEAVE_TX_ID.test(unwrapped) && !unwrapped.startsWith('0x')) {
|
|
1311
|
-
return { kind: 'arweaveTxId', txId: unwrapped };
|
|
1312
|
-
}
|
|
1313
|
-
return { kind: 'unknown', raw: s };
|
|
1314
|
-
}
|
|
1315
|
-
/**
|
|
1316
|
-
* Same as convertTxIdToImage but local to this module to avoid circular imports with helpers/index.
|
|
1317
|
-
*/
|
|
1318
|
-
async function tryLocalBlobUrlFromTxId(txId) {
|
|
1319
|
-
const imageFilePath = BaseFileManager.getFilesPath('images', txId);
|
|
1320
|
-
const fileExists = await BaseFileManager.pathExists(imageFilePath);
|
|
1321
|
-
if (!fileExists) {
|
|
1322
|
-
logger$4(`[tryLocalBlobUrlFromTxId] ${imageFilePath} does not exist`);
|
|
1323
|
-
return undefined;
|
|
1324
|
-
}
|
|
1325
|
-
const buffer = await BaseFileManager.readFileAsBuffer(imageFilePath);
|
|
1326
|
-
let arrayBuffer;
|
|
1327
|
-
if (buffer instanceof Blob) {
|
|
1328
|
-
arrayBuffer = await buffer.arrayBuffer();
|
|
1329
|
-
}
|
|
1330
|
-
else if (buffer instanceof ArrayBuffer) {
|
|
1331
|
-
arrayBuffer = buffer;
|
|
1332
|
-
}
|
|
1333
|
-
else {
|
|
1334
|
-
const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
1335
|
-
if (sliced instanceof SharedArrayBuffer) {
|
|
1336
|
-
const uint8Array = new Uint8Array(sliced);
|
|
1337
|
-
arrayBuffer = uint8Array.buffer.slice(0);
|
|
1338
|
-
}
|
|
1339
|
-
else {
|
|
1340
|
-
arrayBuffer = sliced;
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
const uint = new Uint8Array(arrayBuffer);
|
|
1344
|
-
const imageBlob = new Blob([uint]);
|
|
1345
|
-
return URL.createObjectURL(imageBlob);
|
|
1346
|
-
}
|
|
1347
|
-
async function resolveTxId(txId) {
|
|
1348
|
-
const local = await tryLocalBlobUrlFromTxId(txId);
|
|
1349
|
-
if (local) {
|
|
1350
|
-
return { status: 'ready', href: local, source: 'localBlob' };
|
|
1351
|
-
}
|
|
1352
|
-
return {
|
|
1353
|
-
status: 'ready',
|
|
1354
|
-
href: BaseArweaveClient.getRawUrl(txId),
|
|
1355
|
-
source: 'gateway',
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1358
|
-
/**
|
|
1359
|
-
* Resolve a classified media reference to a display URL (https/blob/data), using local files when present.
|
|
1360
|
-
*/
|
|
1361
|
-
async function resolveMediaRef(raw, options) {
|
|
1362
|
-
const classification = classifyMediaRef(raw, options);
|
|
1363
|
-
switch (classification.kind) {
|
|
1364
|
-
case 'empty':
|
|
1365
|
-
return { status: 'empty' };
|
|
1366
|
-
case 'url':
|
|
1367
|
-
return {
|
|
1368
|
-
status: 'ready',
|
|
1369
|
-
href: classification.href,
|
|
1370
|
-
source: 'direct',
|
|
1371
|
-
};
|
|
1372
|
-
case 'nonMediaUrl':
|
|
1373
|
-
return {
|
|
1374
|
-
status: 'unresolved',
|
|
1375
|
-
reason: 'non_media_url',
|
|
1376
|
-
classification,
|
|
1377
|
-
};
|
|
1378
|
-
case 'seedLocalId':
|
|
1379
|
-
return {
|
|
1380
|
-
status: 'unresolved',
|
|
1381
|
-
reason: 'seed_local_id_not_portable',
|
|
1382
|
-
classification,
|
|
1383
|
-
};
|
|
1384
|
-
case 'unknown':
|
|
1385
|
-
return {
|
|
1386
|
-
status: 'unresolved',
|
|
1387
|
-
reason: 'unknown_ref',
|
|
1388
|
-
classification,
|
|
1389
|
-
};
|
|
1390
|
-
case 'arweaveTxId':
|
|
1391
|
-
return resolveTxId(classification.txId);
|
|
1392
|
-
case 'seedUid': {
|
|
1393
|
-
const appDb = BaseDb.getAppDb();
|
|
1394
|
-
if (!appDb) {
|
|
1395
|
-
return {
|
|
1396
|
-
status: 'unresolved',
|
|
1397
|
-
reason: 'seed_uid_requires_app_db',
|
|
1398
|
-
classification,
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
const storageTransactionId = await getStorageTransactionIdForSeedUid(classification.uid);
|
|
1402
|
-
if (!storageTransactionId) {
|
|
1403
|
-
return {
|
|
1404
|
-
status: 'unresolved',
|
|
1405
|
-
reason: 'seed_uid_no_storage_transaction',
|
|
1406
|
-
classification,
|
|
1407
|
-
};
|
|
1408
|
-
}
|
|
1409
|
-
return resolveTxId(storageTransactionId);
|
|
1410
|
-
}
|
|
1411
|
-
default:
|
|
1412
|
-
return {
|
|
1413
|
-
status: 'unresolved',
|
|
1414
|
-
reason: 'unknown_ref',
|
|
1415
|
-
classification,
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
/**
|
|
1420
|
-
* Read a string field from a parsed item, trying camelCase and snake_case keys.
|
|
1421
|
-
*/
|
|
1422
|
-
function getFeedItemStringField(item, key) {
|
|
1423
|
-
const direct = coalesceStringField(item, key);
|
|
1424
|
-
if (direct)
|
|
1425
|
-
return direct;
|
|
1426
|
-
const snake = key.replace(/[A-Z]/g, (l) => `_${l.toLowerCase()}`);
|
|
1427
|
-
if (snake !== key) {
|
|
1428
|
-
const alt = coalesceStringField(item, snake);
|
|
1429
|
-
if (alt)
|
|
1430
|
-
return alt;
|
|
1431
|
-
}
|
|
1432
|
-
const parts = key.split('_');
|
|
1433
|
-
if (parts.length > 1) {
|
|
1434
|
-
const camel = parts[0] + parts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
|
|
1435
|
-
const alt2 = coalesceStringField(item, camel);
|
|
1436
|
-
if (alt2)
|
|
1437
|
-
return alt2;
|
|
1438
|
-
}
|
|
1439
|
-
return '';
|
|
1440
|
-
}
|
|
1441
|
-
/**
|
|
1442
|
-
* Apply a field manifest to a plain feed item: classify media/file fields; pass through html/text.
|
|
1443
|
-
*/
|
|
1444
|
-
function normalizeFeedItemFields(item, manifest) {
|
|
1445
|
-
const out = {};
|
|
1446
|
-
for (const [fieldKey, descriptor] of Object.entries(manifest)) {
|
|
1447
|
-
const raw = getFeedItemStringField(item, fieldKey);
|
|
1448
|
-
if (!raw) {
|
|
1449
|
-
out[fieldKey] = undefined;
|
|
1450
|
-
continue;
|
|
1451
|
-
}
|
|
1452
|
-
if (descriptor.role === 'html') {
|
|
1453
|
-
out[fieldKey] = { role: 'html', raw };
|
|
1454
|
-
continue;
|
|
1455
|
-
}
|
|
1456
|
-
if (descriptor.role === 'text') {
|
|
1457
|
-
out[fieldKey] = { role: 'text', raw };
|
|
1458
|
-
continue;
|
|
1459
|
-
}
|
|
1460
|
-
const classification = classifyMediaRef(raw, { treatAs: descriptor.treatAs });
|
|
1461
|
-
out[fieldKey] = {
|
|
1462
|
-
role: descriptor.role,
|
|
1463
|
-
raw,
|
|
1464
|
-
classification,
|
|
1465
|
-
};
|
|
1466
|
-
}
|
|
1467
|
-
return out;
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
let refresher;
|
|
1471
|
-
function setCachedPropertyRefresher(fn) {
|
|
1472
|
-
refresher = fn;
|
|
1473
|
-
}
|
|
1474
|
-
/** Give cached ModelProperty instances of these properties their rows' current values. */
|
|
1475
|
-
async function refreshCachedProperties(propertyFileIds) {
|
|
1476
|
-
if (!refresher || propertyFileIds.length === 0)
|
|
1477
|
-
return;
|
|
1478
|
-
await refresher(propertyFileIds);
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
/**
|
|
1482
|
-
* Detect SQLite UNIQUE constraint failures across better-sqlite3 and drizzle/wasm wrappers.
|
|
1483
|
-
* Drizzle often surfaces `Failed query: insert into...` without "UNIQUE constraint" on message,
|
|
1484
|
-
* with the real SQLITE_CONSTRAINT_UNIQUE on cause / nested error.
|
|
1485
|
-
*/
|
|
1486
|
-
function isSqliteUniqueConstraintError(error) {
|
|
1487
|
-
const seen = new Set();
|
|
1488
|
-
const queue = [error];
|
|
1489
|
-
while (queue.length > 0) {
|
|
1490
|
-
const current = queue.shift();
|
|
1491
|
-
if (current == null || seen.has(current))
|
|
1492
|
-
continue;
|
|
1493
|
-
seen.add(current);
|
|
1494
|
-
if (typeof current === 'string') {
|
|
1495
|
-
if (matchesUniqueConstraintText(current))
|
|
1496
|
-
return true;
|
|
1497
|
-
continue;
|
|
1498
|
-
}
|
|
1499
|
-
if (typeof current !== 'object')
|
|
1500
|
-
continue;
|
|
1501
|
-
const err = current;
|
|
1502
|
-
const code = err.code;
|
|
1503
|
-
if (code === 'SQLITE_CONSTRAINT_UNIQUE' ||
|
|
1504
|
-
code === 2067 ||
|
|
1505
|
-
code === '2067') {
|
|
1506
|
-
return true;
|
|
1507
|
-
}
|
|
1508
|
-
for (const key of ['message', 'msg', 'stack']) {
|
|
1509
|
-
const raw = err[key];
|
|
1510
|
-
const value = key === 'stack' && typeof raw === 'string' ? stackWithoutFrames(raw) : raw;
|
|
1511
|
-
if (typeof value === 'string' && matchesUniqueConstraintText(value)) {
|
|
1512
|
-
return true;
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
// Drizzle / wasm often nest the real SQLite error
|
|
1516
|
-
for (const key of ['cause', 'reason', 'error', 'originalError', 'err']) {
|
|
1517
|
-
if (err[key] != null)
|
|
1518
|
-
queue.push(err[key]);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
return false;
|
|
1522
|
-
}
|
|
1523
|
-
function matchesUniqueConstraintText(text) {
|
|
1524
|
-
const lower = text.toLowerCase();
|
|
1525
|
-
return (lower.includes('unique constraint') ||
|
|
1526
|
-
lower.includes('sqlite_constraint_unique') ||
|
|
1527
|
-
(lower.includes('constraint failed') && lower.includes('unique')) ||
|
|
1528
|
-
/\brc\s*=\s*2067\b/i.test(text) ||
|
|
1529
|
-
(text.includes('2067') && lower.includes('constraint')));
|
|
1530
|
-
}
|
|
1531
|
-
/**
|
|
1532
|
-
* A stack's message lines without its frames. Frames hold file names and line:column numbers, so
|
|
1533
|
-
* a frame in a file named like this one, with a 2067 in its position or URL timestamp, would match.
|
|
1534
|
-
*/
|
|
1535
|
-
function stackWithoutFrames(stack) {
|
|
1536
|
-
return stack
|
|
1537
|
-
.split('\n')
|
|
1538
|
-
.filter((line) => !/^\s*at\s/.test(line) && !/@\S*:\d+:\d+\s*$/.test(line))
|
|
1539
|
-
.join('\n');
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
const level = (value) => (isNameMatchLevel(value) ? value : undefined);
|
|
1543
|
-
const tighter = (a, b) => a === undefined || compareNameMatchTiers(b, a) < 0 ? b : a;
|
|
1544
|
-
/** The levels in one schema file for one of its models, or undefined when the file lacks the model. */
|
|
1545
|
-
const modelLevelsFromFile = (file, modelName) => {
|
|
1546
|
-
const modelDef = file.models?.[modelName];
|
|
1547
|
-
if (!modelDef)
|
|
1548
|
-
return undefined;
|
|
1549
|
-
const model = level(modelDef.nameMatch) ?? level(file.nameMatch) ?? DEFAULT_NAME_MATCH_LEVEL;
|
|
1550
|
-
const properties = new Map();
|
|
1551
|
-
for (const [propertyName, def] of Object.entries(modelDef.properties ?? {})) {
|
|
1552
|
-
properties.set(propertyName, level(def?.nameMatch) ?? model);
|
|
1553
|
-
}
|
|
1554
|
-
return { model, properties };
|
|
1555
|
-
};
|
|
1556
|
-
/**
|
|
1557
|
-
* Builds NameMatchLevels from the schema files that own each model row. Exported for tests;
|
|
1558
|
-
* use {@link nameMatchLevels}.
|
|
1559
|
-
*/
|
|
1560
|
-
const nameMatchLevelsFromRows = (rows) => {
|
|
1561
|
-
const byFileId = new Map();
|
|
1562
|
-
const byName = new Map();
|
|
1563
|
-
const parsed = new Map();
|
|
1564
|
-
for (const row of rows) {
|
|
1565
|
-
if (!row.schemaData)
|
|
1566
|
-
continue;
|
|
1567
|
-
if (!parsed.has(row.schemaData)) {
|
|
1568
|
-
try {
|
|
1569
|
-
parsed.set(row.schemaData, JSON.parse(row.schemaData));
|
|
1570
|
-
}
|
|
1571
|
-
catch {
|
|
1572
|
-
parsed.set(row.schemaData, undefined);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
const file = parsed.get(row.schemaData);
|
|
1576
|
-
const levels = file && modelLevelsFromFile(file, row.modelName);
|
|
1577
|
-
if (!levels)
|
|
1578
|
-
continue;
|
|
1579
|
-
if (row.modelFileId)
|
|
1580
|
-
byFileId.set(row.modelFileId, levels);
|
|
1581
|
-
const seen = byName.get(row.modelName);
|
|
1582
|
-
if (!seen) {
|
|
1583
|
-
byName.set(row.modelName, { model: levels.model, properties: new Map(levels.properties) });
|
|
1584
|
-
continue;
|
|
1585
|
-
}
|
|
1586
|
-
seen.model = tighter(seen.model, levels.model);
|
|
1587
|
-
for (const [name, value] of levels.properties)
|
|
1588
|
-
seen.properties.set(name, tighter(seen.properties.get(name), value));
|
|
1589
|
-
}
|
|
1590
|
-
return {
|
|
1591
|
-
model: (modelName) => byName.get(modelName)?.model ?? DEFAULT_NAME_MATCH_LEVEL,
|
|
1592
|
-
property: (modelName, propertyName, modelFileId) => {
|
|
1593
|
-
const levels = (modelFileId ? byFileId.get(modelFileId) : undefined) ?? byName.get(modelName);
|
|
1594
|
-
return levels?.properties.get(propertyName) ?? levels?.model ?? DEFAULT_NAME_MATCH_LEVEL;
|
|
1595
|
-
},
|
|
1596
|
-
};
|
|
1597
|
-
};
|
|
1598
|
-
const DEFAULT_LEVELS = {
|
|
1599
|
-
model: () => DEFAULT_NAME_MATCH_LEVEL,
|
|
1600
|
-
property: () => DEFAULT_NAME_MATCH_LEVEL,
|
|
1601
|
-
};
|
|
1602
|
-
let current;
|
|
1603
|
-
let loading;
|
|
1604
|
-
/** Reads the levels from the schema files stored with each model's schema row (schemas.schema_data). */
|
|
1605
|
-
const loadNameMatchLevels = async (db = BaseDb.getAppDb()) => {
|
|
1606
|
-
if (!db)
|
|
1607
|
-
return DEFAULT_LEVELS;
|
|
1608
|
-
const load = (async () => {
|
|
1609
|
-
const rows = (await db
|
|
1610
|
-
.select({ modelName: models.name, modelFileId: models.schemaFileId, schemaData: schemas.schemaData })
|
|
1611
|
-
.from(models)
|
|
1612
|
-
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
1613
|
-
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id)));
|
|
1614
|
-
return nameMatchLevelsFromRows(rows);
|
|
1615
|
-
})();
|
|
1616
|
-
loading = load;
|
|
1617
|
-
try {
|
|
1618
|
-
const levels = await load;
|
|
1619
|
-
if (loading === load) {
|
|
1620
|
-
current = levels;
|
|
1621
|
-
previous = undefined;
|
|
1622
|
-
}
|
|
1623
|
-
return levels;
|
|
1624
|
-
}
|
|
1625
|
-
finally {
|
|
1626
|
-
if (loading === load)
|
|
1627
|
-
loading = undefined;
|
|
1628
|
-
}
|
|
1629
|
-
};
|
|
1630
|
-
/** The levels, loaded once and reused until a schema write clears them. */
|
|
1631
|
-
const nameMatchLevels = async (db = BaseDb.getAppDb()) => current ?? loading ?? loadNameMatchLevels(db);
|
|
1632
|
-
/**
|
|
1633
|
-
* The last loaded levels, for synchronous callers (Model.findByModelType). Init loads them before
|
|
1634
|
-
* it reports ready. After a schema write clears them, this returns the levels from before the
|
|
1635
|
-
* write until the reload that clear starts finishes.
|
|
1636
|
-
*/
|
|
1637
|
-
const currentNameMatchLevels = () => current ?? previous ?? DEFAULT_LEVELS;
|
|
1638
|
-
let previous;
|
|
1639
|
-
let reloadScheduled = false;
|
|
1640
|
-
/**
|
|
1641
|
-
* Called when a schema row or its models are written. The next {@link nameMatchLevels} read
|
|
1642
|
-
* reloads, and a reload starts on the next tick (one per burst of writes) for synchronous callers.
|
|
1643
|
-
*/
|
|
1644
|
-
const clearNameMatchLevels = () => {
|
|
1645
|
-
if (current)
|
|
1646
|
-
previous = current;
|
|
1647
|
-
current = undefined;
|
|
1648
|
-
loading = undefined;
|
|
1649
|
-
if (reloadScheduled)
|
|
1650
|
-
return;
|
|
1651
|
-
reloadScheduled = true;
|
|
1652
|
-
setTimeout(() => {
|
|
1653
|
-
reloadScheduled = false;
|
|
1654
|
-
if (current || loading || !BaseDb.isAppDbReady())
|
|
1655
|
-
return;
|
|
1656
|
-
loadNameMatchLevels().catch(() => {
|
|
1657
|
-
// The next nameMatchLevels() read retries.
|
|
1658
|
-
});
|
|
1659
|
-
}, 0);
|
|
1660
|
-
};
|
|
1661
|
-
/**
|
|
1662
|
-
* `nameMatch` values in a schema file that aren't a level. They are ignored (the next level up
|
|
1663
|
-
* applies); schema loads warn about them.
|
|
1664
|
-
*/
|
|
1665
|
-
const invalidNameMatchValues = (file) => {
|
|
1666
|
-
const problems = [];
|
|
1667
|
-
const check = (where, value) => {
|
|
1668
|
-
if (value !== undefined && !isNameMatchLevel(value)) {
|
|
1669
|
-
problems.push(`${where} nameMatch ${JSON.stringify(value)} is not 'exact', 'normalized' or 'fuzzy'`);
|
|
1670
|
-
}
|
|
1671
|
-
};
|
|
1672
|
-
check('schema', file.nameMatch);
|
|
1673
|
-
for (const [modelName, modelDef] of Object.entries(file.models ?? {})) {
|
|
1674
|
-
check(modelName, modelDef?.nameMatch);
|
|
1675
|
-
for (const [propertyName, def] of Object.entries(modelDef?.properties ?? {})) {
|
|
1676
|
-
check(`${modelName}.${propertyName}`, def?.nameMatch);
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
return problems;
|
|
1680
|
-
};
|
|
1681
|
-
/** Warns about {@link invalidNameMatchValues}. */
|
|
1682
|
-
const warnOnInvalidNameMatch = (file) => {
|
|
1683
|
-
const problems = invalidNameMatchValues(file);
|
|
1684
|
-
for (const problem of problems) {
|
|
1685
|
-
console.warn(`[seed] schema ${file.metadata?.name ?? ''}: ${problem}; it is ignored.`);
|
|
1686
|
-
}
|
|
1687
|
-
return problems;
|
|
1688
|
-
};
|
|
1689
|
-
|
|
1690
|
-
/**
|
|
1691
|
-
* Thrown when a model name exists in more than one schema and nothing says which one is meant.
|
|
1692
|
-
* Model names are only unique within a schema; pass `schemaName` (or `modelFileId`) to choose.
|
|
1693
|
-
*/
|
|
1694
|
-
class AmbiguousModelError extends Error {
|
|
1695
|
-
constructor(modelName, schemaNames) {
|
|
1696
|
-
const names = [...new Set(schemaNames)].sort();
|
|
1697
|
-
super(`Model "${modelName}" exists in schemas ${names.map((n) => `"${n}"`).join(', ')}; ` +
|
|
1698
|
-
`pass schemaName (or modelFileId) to choose one.`);
|
|
1699
|
-
this.name = 'AmbiguousModelError';
|
|
1700
|
-
this.modelName = modelName;
|
|
1701
|
-
this.schemaNames = names;
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
const columns = {
|
|
1706
|
-
id: models.id,
|
|
1707
|
-
name: models.name,
|
|
1708
|
-
schemaFileId: models.schemaFileId,
|
|
1709
|
-
};
|
|
1710
|
-
/**
|
|
1711
|
-
* Resolve a models row from a model name plus whatever scope is known, most specific first:
|
|
1712
|
-
* modelFileId, modelId, then schemaId/schemaName + name, then the name alone. The name alone is
|
|
1713
|
-
* only accepted when it is unambiguous; otherwise this throws AmbiguousModelError.
|
|
1714
|
-
*/
|
|
1715
|
-
const resolveModelRecord = async (modelName, scope = {}, db = BaseDb.getAppDb()) => {
|
|
1716
|
-
if (!db)
|
|
1717
|
-
return undefined;
|
|
1718
|
-
if (scope.modelFileId) {
|
|
1719
|
-
const rows = await db
|
|
1720
|
-
.select(columns)
|
|
1721
|
-
.from(models)
|
|
1722
|
-
.where(eq(models.schemaFileId, scope.modelFileId))
|
|
1723
|
-
.limit(1);
|
|
1724
|
-
if (rows.length > 0)
|
|
1725
|
-
return rows[0];
|
|
1726
|
-
}
|
|
1727
|
-
if (scope.modelId) {
|
|
1728
|
-
const rows = await db.select(columns).from(models).where(eq(models.id, scope.modelId)).limit(1);
|
|
1729
|
-
if (rows.length > 0)
|
|
1730
|
-
return rows[0];
|
|
1731
|
-
}
|
|
1732
|
-
if (!modelName)
|
|
1733
|
-
return undefined;
|
|
1734
|
-
if (scope.schemaId || scope.schemaName) {
|
|
1735
|
-
const rows = await db
|
|
1736
|
-
.select(columns)
|
|
1737
|
-
.from(models)
|
|
1738
|
-
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
1739
|
-
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
1740
|
-
.where(and(eq(models.name, modelName), scope.schemaId ? eq(schemas.id, scope.schemaId) : eq(schemas.name, scope.schemaName)))
|
|
1741
|
-
.limit(1);
|
|
1742
|
-
if (rows.length > 0)
|
|
1743
|
-
return rows[0];
|
|
1744
|
-
}
|
|
1745
|
-
return resolveModelRecordByNameOnly(modelName, db);
|
|
1746
|
-
};
|
|
1747
|
-
/**
|
|
1748
|
-
* Name-only resolution. Rows linked to a schema win over unlinked stubs (null-schemaFileId rows
|
|
1749
|
-
* from ref resolution); if more than one linked row has the name it is ambiguous and this throws.
|
|
1750
|
-
* `label` names the lookup in the error (defaults to the model name).
|
|
1751
|
-
*/
|
|
1752
|
-
const resolveModelRecordByNameOnly = async (modelName, db, label = Array.isArray(modelName) ? modelName.join(', ') : modelName) => {
|
|
1753
|
-
const rows = (await db
|
|
1754
|
-
.select({ ...columns, schemaName: schemas.name })
|
|
1755
|
-
.from(models)
|
|
1756
|
-
.leftJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
1757
|
-
.leftJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
1758
|
-
.where(Array.isArray(modelName) ? inArray(models.name, modelName) : eq(models.name, modelName))
|
|
1759
|
-
.orderBy(desc(models.id)));
|
|
1760
|
-
if (rows.length === 0)
|
|
1761
|
-
return undefined;
|
|
1762
|
-
const linked = rows.filter((r) => r.schemaName);
|
|
1763
|
-
const linkedIds = new Set(linked.map((r) => r.id));
|
|
1764
|
-
if (linkedIds.size > 1) {
|
|
1765
|
-
throw new AmbiguousModelError(label, linked.map((r) => r.schemaName));
|
|
1766
|
-
}
|
|
1767
|
-
const chosen = linked[0] ?? rows[0];
|
|
1768
|
-
return { id: chosen.id, name: chosen.name, schemaFileId: chosen.schemaFileId };
|
|
1769
|
-
};
|
|
1770
|
-
/**
|
|
1771
|
-
* The model names a model name or EAS model type can refer to: the name itself, else the names
|
|
1772
|
-
* that match it in the tightest tier (ADR 0008, as Model.findByModelType matches): `new_model` →
|
|
1773
|
-
* "New model", `sync_storage_post` → "SyncStoragePost", `htmlpage` → "HTMLPage". Each name at its
|
|
1774
|
-
* nameMatch level (ADR 0009).
|
|
1775
|
-
*/
|
|
1776
|
-
const modelNamesForNameOrType = async (nameOrType, db) => {
|
|
1777
|
-
const rows = (await db.selectDistinct({ name: models.name }).from(models));
|
|
1778
|
-
const names = rows.map((r) => r.name);
|
|
1779
|
-
if (names.includes(nameOrType))
|
|
1780
|
-
return [nameOrType];
|
|
1781
|
-
const levels = await nameMatchLevels(db);
|
|
1782
|
-
return [...localNamesInBestMatchTier(nameOrType, names, { level: (name) => levels.model(name) })];
|
|
1783
|
-
};
|
|
1784
|
-
/**
|
|
1785
|
-
* resolveModelRecord for a model name **or** an EAS model type (snake_case model name, what sync
|
|
1786
|
-
* stores in seeds.type): "New model" resolves from `new_model`. Scope and ambiguity behave as in
|
|
1787
|
-
* resolveModelRecord: modelFileId/modelId first, then the schema, then the name(s) alone, which throws
|
|
1788
|
-
* AmbiguousModelError when models in several schemas match.
|
|
1789
|
-
*/
|
|
1790
|
-
const resolveModelRecordByNameOrType = async (nameOrType, scope = {}, db = BaseDb.getAppDb()) => {
|
|
1791
|
-
if (!db)
|
|
1792
|
-
return undefined;
|
|
1793
|
-
if (scope.modelFileId || scope.modelId) {
|
|
1794
|
-
const byId = await resolveModelRecord(undefined, scope, db);
|
|
1795
|
-
if (byId)
|
|
1796
|
-
return byId;
|
|
1797
|
-
}
|
|
1798
|
-
if (!nameOrType)
|
|
1799
|
-
return undefined;
|
|
1800
|
-
const names = await modelNamesForNameOrType(nameOrType, db);
|
|
1801
|
-
if (names.length === 0)
|
|
1802
|
-
return undefined;
|
|
1803
|
-
if (names.length === 1)
|
|
1804
|
-
return resolveModelRecord(names[0], { ...scope, modelFileId: undefined, modelId: undefined }, db);
|
|
1805
|
-
// Several model names map to this type (e.g. "New model" and "NewModel" in different schemas).
|
|
1806
|
-
if (scope.schemaId || scope.schemaName) {
|
|
1807
|
-
const rows = (await db
|
|
1808
|
-
.select(columns)
|
|
1809
|
-
.from(models)
|
|
1810
|
-
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
1811
|
-
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
1812
|
-
.where(and(inArray(models.name, names), scope.schemaId ? eq(schemas.id, scope.schemaId) : eq(schemas.name, scope.schemaName))));
|
|
1813
|
-
if (new Set(rows.map((r) => r.id)).size === 1)
|
|
1814
|
-
return rows[0];
|
|
1815
|
-
}
|
|
1816
|
-
return resolveModelRecordByNameOnly(names, db, nameOrType);
|
|
1817
|
-
};
|
|
1818
|
-
/**
|
|
1819
|
-
* Work out which model (schemaFileId) an item belongs to from whatever is at hand: an explicit
|
|
1820
|
-
* modelFileId, the seed's model_file_id, or a metadata row's property_id → properties.model_id.
|
|
1821
|
-
*/
|
|
1822
|
-
const resolveItemModelFileId = async (hints, db = BaseDb.getAppDb()) => {
|
|
1823
|
-
if (hints.modelFileId)
|
|
1824
|
-
return hints.modelFileId;
|
|
1825
|
-
if (!db)
|
|
1826
|
-
return undefined;
|
|
1827
|
-
if (hints.seedLocalId || hints.seedUid) {
|
|
1828
|
-
const rows = await db
|
|
1829
|
-
.select({ modelFileId: seeds.modelFileId })
|
|
1830
|
-
.from(seeds)
|
|
1831
|
-
.where(hints.seedLocalId ? eq(seeds.localId, hints.seedLocalId) : eq(seeds.uid, hints.seedUid))
|
|
1832
|
-
.limit(1);
|
|
1833
|
-
if (rows[0]?.modelFileId)
|
|
1834
|
-
return rows[0].modelFileId;
|
|
1835
|
-
}
|
|
1836
|
-
if (hints.propertyId) {
|
|
1837
|
-
const rows = await db
|
|
1838
|
-
.select({ modelFileId: models.schemaFileId })
|
|
1839
|
-
.from(properties)
|
|
1840
|
-
.innerJoin(models, eq(properties.modelId, models.id))
|
|
1841
|
-
.where(eq(properties.id, hints.propertyId))
|
|
1842
|
-
.limit(1);
|
|
1843
|
-
if (rows[0]?.modelFileId)
|
|
1844
|
-
return rows[0].modelFileId;
|
|
1845
|
-
}
|
|
1846
|
-
return undefined;
|
|
1847
|
-
};
|
|
1848
|
-
/** ModelScope for an Item (or ItemProperty) instance: its recorded modelFileId (context or seed row) and schemaName. */
|
|
1849
|
-
const getItemModelScope = async (item) => {
|
|
1850
|
-
let context;
|
|
1851
|
-
try {
|
|
1852
|
-
context = item.getService?.().getSnapshot().context;
|
|
1853
|
-
}
|
|
1854
|
-
catch {
|
|
1855
|
-
context = undefined;
|
|
1856
|
-
}
|
|
1857
|
-
const modelFileId = await resolveItemModelFileId({
|
|
1858
|
-
modelFileId: context?.modelFileId,
|
|
1859
|
-
seedLocalId: item.seedLocalId ?? context?.seedLocalId,
|
|
1860
|
-
seedUid: item.seedUid ?? context?.seedUid,
|
|
1861
|
-
propertyId: context?.propertyId,
|
|
1862
|
-
});
|
|
1863
|
-
return { modelFileId, schemaName: context?.schemaName };
|
|
1864
|
-
};
|
|
1865
|
-
/**
|
|
1866
|
-
* Resolve a ref (e.g. a Relation's target model) by name from the schema(s) its owning model belongs
|
|
1867
|
-
* to, so "Tag" means the owner schema's Tag rather than any Tag. Falls back to resolveModelRecord.
|
|
1868
|
-
*/
|
|
1869
|
-
const resolveRefModelRecord = async (refModelName, owner, db = BaseDb.getAppDb()) => {
|
|
1870
|
-
if (!db || !refModelName)
|
|
1871
|
-
return undefined;
|
|
1872
|
-
const ownerRow = owner.modelFileId || owner.modelId ? await resolveModelRecord(undefined, owner, db) : undefined;
|
|
1873
|
-
const schemaFilter = ownerRow
|
|
1874
|
-
? inArray(modelSchemas.schemaId,
|
|
1875
|
-
// any schema the owner is linked to
|
|
1876
|
-
db.select({ schemaId: modelSchemas.schemaId }).from(modelSchemas).where(eq(modelSchemas.modelId, ownerRow.id)))
|
|
1877
|
-
: owner.schemaId
|
|
1878
|
-
? eq(modelSchemas.schemaId, owner.schemaId)
|
|
1879
|
-
: owner.schemaName
|
|
1880
|
-
? eq(schemas.name, owner.schemaName)
|
|
1881
|
-
: undefined;
|
|
1882
|
-
if (schemaFilter) {
|
|
1883
|
-
const rows = await db
|
|
1884
|
-
.select(columns)
|
|
1885
|
-
.from(models)
|
|
1886
|
-
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
1887
|
-
.innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
|
|
1888
|
-
.where(and(eq(models.name, refModelName), schemaFilter))
|
|
1889
|
-
.limit(1);
|
|
1890
|
-
if (rows.length > 0)
|
|
1891
|
-
return rows[0];
|
|
1892
|
-
}
|
|
1893
|
-
return resolveModelRecord(refModelName, {}, db);
|
|
1894
|
-
};
|
|
1895
|
-
|
|
1896
|
-
var resolveModelRecord$1 = /*#__PURE__*/Object.freeze({
|
|
1897
|
-
__proto__: null,
|
|
1898
|
-
getItemModelScope: getItemModelScope,
|
|
1899
|
-
resolveItemModelFileId: resolveItemModelFileId,
|
|
1900
|
-
resolveModelRecord: resolveModelRecord,
|
|
1901
|
-
resolveModelRecordByNameOrType: resolveModelRecordByNameOrType,
|
|
1902
|
-
resolveRefModelRecord: resolveRefModelRecord
|
|
1903
|
-
});
|
|
1904
|
-
|
|
1905
|
-
/**
|
|
1906
|
-
* Lowercase + trim an address for persist and compare.
|
|
1907
|
-
* Does not checksum or validate — test and placeholder addresses must keep working.
|
|
1908
|
-
*/
|
|
1909
|
-
function normalizeHexAddress(addr) {
|
|
1910
|
-
return addr.trim().toLowerCase();
|
|
1911
|
-
}
|
|
1912
|
-
/**
|
|
1913
|
-
* Lowercase, drop empties, and dedupe (first-seen order).
|
|
1914
|
-
*/
|
|
1915
|
-
function normalizeAddressList(addrs) {
|
|
1916
|
-
if (!addrs?.length)
|
|
1917
|
-
return [];
|
|
1918
|
-
const seen = new Set();
|
|
1919
|
-
const out = [];
|
|
1920
|
-
for (const addr of addrs) {
|
|
1921
|
-
if (addr == null || addr === '')
|
|
1922
|
-
continue;
|
|
1923
|
-
const normalized = normalizeHexAddress(String(addr));
|
|
1924
|
-
if (!normalized || seen.has(normalized))
|
|
1925
|
-
continue;
|
|
1926
|
-
seen.add(normalized);
|
|
1927
|
-
out.push(normalized);
|
|
1928
|
-
}
|
|
1929
|
-
return out;
|
|
1930
|
-
}
|
|
1931
|
-
/**
|
|
1932
|
-
* Lowercase a publisher stamp. Empty / null → undefined (omit on write).
|
|
1933
|
-
*/
|
|
1934
|
-
function normalizePublisher(publisher) {
|
|
1935
|
-
if (publisher == null || publisher === '')
|
|
1936
|
-
return undefined;
|
|
1937
|
-
const normalized = normalizeHexAddress(publisher);
|
|
1938
|
-
return normalized || undefined;
|
|
1939
|
-
}
|
|
1940
|
-
/**
|
|
1941
|
-
* Normalizes AddressConfiguration to { owned, watched }.
|
|
1942
|
-
* - string[] -> { owned: addresses, watched: [] }
|
|
1943
|
-
* - { owned, watched? } -> { owned, watched: watched ?? [] }
|
|
1944
|
-
* Addresses are lowercased and deduped.
|
|
1945
|
-
*/
|
|
1946
|
-
function normalizeAddressConfig(addresses) {
|
|
1947
|
-
if (!addresses) {
|
|
1948
|
-
return { owned: [], watched: [] };
|
|
1949
|
-
}
|
|
1950
|
-
if (Array.isArray(addresses)) {
|
|
1951
|
-
return { owned: normalizeAddressList(addresses), watched: [] };
|
|
1952
|
-
}
|
|
1953
|
-
return {
|
|
1954
|
-
owned: normalizeAddressList(addresses.owned),
|
|
1955
|
-
watched: normalizeAddressList(addresses.watched),
|
|
1956
|
-
};
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
const pendingLinks = new Map();
|
|
1960
|
-
/**
|
|
1961
|
-
* Insert the `model_schemas` row linking a model to a schema, unless it already exists.
|
|
1962
|
-
*
|
|
1963
|
-
* `model_schemas` has no unique constraint, and several writers link the same model during one
|
|
1964
|
-
* import (importJsonSchema's addModelsToDb and the write service's writeModelToDb). Each used to
|
|
1965
|
-
* check-then-insert on its own, so concurrent writers inserted the link twice. The check and insert
|
|
1966
|
-
* run serialized per (model, schema) here.
|
|
1967
|
-
*
|
|
1968
|
-
* @returns true when this call inserted the link
|
|
1969
|
-
*/
|
|
1970
|
-
function linkModelToSchema(db, modelId, schemaId) {
|
|
1971
|
-
const key = `${modelId}:${schemaId}`;
|
|
1972
|
-
const previous = pendingLinks.get(key) ?? Promise.resolve(false);
|
|
1973
|
-
const current = previous
|
|
1974
|
-
.catch(() => false)
|
|
1975
|
-
.then(async () => {
|
|
1976
|
-
const existing = await db
|
|
1977
|
-
.select({ id: modelSchemas.id })
|
|
1978
|
-
.from(modelSchemas)
|
|
1979
|
-
.where(and(eq(modelSchemas.modelId, modelId), eq(modelSchemas.schemaId, schemaId)))
|
|
1980
|
-
.limit(1);
|
|
1981
|
-
if (existing.length > 0)
|
|
1982
|
-
return false;
|
|
1983
|
-
await db.insert(modelSchemas).values({ modelId, schemaId });
|
|
1984
|
-
return true;
|
|
1985
|
-
});
|
|
1986
|
-
pendingLinks.set(key, current);
|
|
1987
|
-
const cleanup = () => {
|
|
1988
|
-
if (pendingLinks.get(key) === current)
|
|
1989
|
-
pendingLinks.delete(key);
|
|
1990
|
-
};
|
|
1991
|
-
current.then(cleanup, cleanup);
|
|
1992
|
-
return current;
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
const logger$3 = debug('seedSdk:helpers:db');
|
|
1996
|
-
/**
|
|
1997
|
-
* Posts one message and closes the channel. A message already posted is still delivered after its
|
|
1998
|
-
* sender closes, and an open BroadcastChannel keeps a Node process alive (each was a MessagePort
|
|
1999
|
-
* handle that stopped scripts exiting).
|
|
2000
|
-
*/
|
|
2001
|
-
const postBroadcast = (channelName, message) => {
|
|
2002
|
-
const channel = new BroadcastChannel(channelName);
|
|
2003
|
-
try {
|
|
2004
|
-
channel.postMessage(message);
|
|
2005
|
-
}
|
|
2006
|
-
finally {
|
|
2007
|
-
channel.close();
|
|
2008
|
-
}
|
|
2009
|
-
};
|
|
2010
|
-
/**
|
|
2011
|
-
* The `properties` storage columns for a property definition, in either the internal shape
|
|
2012
|
-
* (`storageType`, `localStorageDir`, `filenameSuffix`) or the schema-file shape
|
|
2013
|
-
* (`storage: { type, path, extension }`). All null when the definition has no storage config.
|
|
2014
|
-
*/
|
|
2015
|
-
function propertyStorageColumns(def) {
|
|
2016
|
-
const normalized = normalizePropertyRecordSchema((def ?? {}));
|
|
2017
|
-
return {
|
|
2018
|
-
storageType: normalized.storageType ?? null,
|
|
2019
|
-
localStorageDir: normalized.localStorageDir ?? null,
|
|
2020
|
-
filenameSuffix: normalized.filenameSuffix ?? null,
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
/** True when the definition carries any storage setting (so writing its columns can't wipe them by omission). */
|
|
2024
|
-
const hasStorageSettings = (columns) => columns.storageType != null || columns.localStorageDir != null || columns.filenameSuffix != null;
|
|
2025
|
-
/**
|
|
2026
|
-
* Resolve property_id (integer properties.id) from a property's schemaFileId (the string `id` in
|
|
2027
|
-
* schema files and propertyRecordSchema). Scoped to the model since schemaFileIds are per schema file.
|
|
2028
|
-
* Reads `db` (a transaction's `tx`, say), the app database by default.
|
|
2029
|
-
* @returns properties.id or null if not found
|
|
2030
|
-
*/
|
|
2031
|
-
async function getPropertyIdForSchemaFileId(modelNameOrType, schemaFileId, scope = {}, db = BaseDb.getAppDb()) {
|
|
2032
|
-
if (!db || !modelNameOrType || !schemaFileId)
|
|
2033
|
-
return null;
|
|
2034
|
-
const modelRecord = await resolveModelRecordByNameOrType(modelNameOrType, scope, db);
|
|
2035
|
-
if (!modelRecord)
|
|
2036
|
-
return null;
|
|
2037
|
-
const rows = await db
|
|
2038
|
-
.select({ id: properties.id })
|
|
2039
|
-
.from(properties)
|
|
2040
|
-
.where(and(eq(properties.modelId, modelRecord.id), eq(properties.schemaFileId, schemaFileId)))
|
|
2041
|
-
.limit(1);
|
|
2042
|
-
return rows[0]?.id ?? null;
|
|
2043
|
-
}
|
|
2044
|
-
/**
|
|
2045
|
-
* Resolve property_id from model name/type and property name.
|
|
2046
|
-
* Handles property name variants (e.g. htmlId -> html, avatarImageIds -> avatarImages).
|
|
2047
|
-
* @param modelNameOrType - Model name (PascalCase) or model type (snake_case)
|
|
2048
|
-
* @param propertyName - Property name as stored in metadata (may have Id/Ids suffix)
|
|
2049
|
-
* @param scope - Which model is meant when several schemas define one with this name
|
|
2050
|
-
* @param db - The database or transaction to read; the app database by default
|
|
2051
|
-
* @returns properties.id or null if not found
|
|
2052
|
-
*/
|
|
2053
|
-
async function getPropertyIdForModelAndName(modelNameOrType, propertyName, scope = {}, db = BaseDb.getAppDb()) {
|
|
2054
|
-
if (!db)
|
|
2055
|
-
return null;
|
|
2056
|
-
if (!modelNameOrType || !propertyName)
|
|
2057
|
-
return null;
|
|
2058
|
-
const modelRecord = await resolveModelRecordByNameOrType(modelNameOrType, scope, db);
|
|
2059
|
-
if (!modelRecord)
|
|
2060
|
-
return null;
|
|
2061
|
-
const propertyNamesToTry = [propertyName];
|
|
2062
|
-
if (propertyName.endsWith('Ids')) {
|
|
2063
|
-
propertyNamesToTry.push(propertyName.slice(0, -3));
|
|
2064
|
-
}
|
|
2065
|
-
if (propertyName.endsWith('Id')) {
|
|
2066
|
-
propertyNamesToTry.push(propertyName.slice(0, -2));
|
|
2067
|
-
}
|
|
2068
|
-
for (const pName of propertyNamesToTry) {
|
|
2069
|
-
const rows = await db
|
|
2070
|
-
.select({ id: properties.id })
|
|
2071
|
-
.from(properties)
|
|
2072
|
-
.where(and(eq(properties.modelId, modelRecord.id), eq(properties.name, pName)))
|
|
2073
|
-
.limit(1);
|
|
2074
|
-
if (rows.length > 0 && rows[0].id != null) {
|
|
2075
|
-
return rows[0].id;
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
return null;
|
|
2079
|
-
}
|
|
2080
|
-
/**
|
|
2081
|
-
* A model's ItemStorage properties as stored in `properties` (storage_type 'ItemStorage'), with
|
|
2082
|
-
* their storage path and extension. Reads the table, not ModelProperty instances, so it doesn't
|
|
2083
|
-
* depend on whether (or from where) the model's property instances have loaded.
|
|
2084
|
-
*/
|
|
2085
|
-
async function getItemStoragePropertiesForModel(modelNameOrType, scope = {}) {
|
|
2086
|
-
const db = BaseDb.getAppDb();
|
|
2087
|
-
if (!db || !modelNameOrType)
|
|
2088
|
-
return [];
|
|
2089
|
-
const modelRecord = await resolveModelRecordByNameOrType(modelNameOrType, scope, db);
|
|
2090
|
-
if (!modelRecord)
|
|
2091
|
-
return [];
|
|
2092
|
-
return db
|
|
2093
|
-
.select({
|
|
2094
|
-
id: properties.id,
|
|
2095
|
-
name: properties.name,
|
|
2096
|
-
localStorageDir: properties.localStorageDir,
|
|
2097
|
-
filenameSuffix: properties.filenameSuffix,
|
|
2098
|
-
})
|
|
2099
|
-
.from(properties)
|
|
2100
|
-
.where(and(eq(properties.modelId, modelRecord.id), eq(properties.storageType, 'ItemStorage')));
|
|
2101
|
-
}
|
|
2102
|
-
/**
|
|
2103
|
-
* Migrate metadata rows when a property is renamed.
|
|
2104
|
-
* Updates metadata.propertyName from old to new for all rows that reference the renamed property.
|
|
2105
|
-
* Called from renameModelProperty to prevent duplicate ItemProperties.
|
|
2106
|
-
* @param modelName - Model name (PascalCase)
|
|
2107
|
-
* @param oldPropertyName - Current property name before rename
|
|
2108
|
-
* @param newPropertyName - New property name after rename
|
|
2109
|
-
* @param propertySchemaFileId - Optional schema file ID for the property (most reliable lookup)
|
|
2110
|
-
* @returns Count of metadata rows updated
|
|
2111
|
-
*/
|
|
2112
|
-
async function migrateMetadataForPropertyRename(modelName, oldPropertyName, newPropertyName, propertySchemaFileId, scope = {}) {
|
|
2113
|
-
const db = BaseDb.getAppDb();
|
|
2114
|
-
if (!db) {
|
|
2115
|
-
logger$3('[migrateMetadataForPropertyRename] Database not available, skipping');
|
|
2116
|
-
return 0;
|
|
2117
|
-
}
|
|
2118
|
-
// Find the property row by schemaFileId (preferred) or by name
|
|
2119
|
-
let propertyId = null;
|
|
2120
|
-
if (propertySchemaFileId) {
|
|
2121
|
-
propertyId = await getPropertyIdForSchemaFileId(modelName, propertySchemaFileId, scope);
|
|
2122
|
-
}
|
|
2123
|
-
if (propertyId == null) {
|
|
2124
|
-
propertyId = await getPropertyIdForModelAndName(modelName, oldPropertyName, scope);
|
|
2125
|
-
}
|
|
2126
|
-
if (propertyId == null) {
|
|
2127
|
-
logger$3(`[migrateMetadataForPropertyRename] Property not found: ${modelName}.${oldPropertyName}, skipping`);
|
|
2128
|
-
return 0;
|
|
2129
|
-
}
|
|
2130
|
-
// Primary path: update metadata by propertyId
|
|
2131
|
-
const toUpdateByPropertyId = await db
|
|
2132
|
-
.select({ localId: metadata.localId })
|
|
2133
|
-
.from(metadata)
|
|
2134
|
-
.where(eq(metadata.propertyId, propertyId));
|
|
2135
|
-
const updatedByPropertyId = toUpdateByPropertyId.length;
|
|
2136
|
-
if (updatedByPropertyId > 0) {
|
|
2137
|
-
await db
|
|
2138
|
-
.update(metadata)
|
|
2139
|
-
.set({ propertyName: newPropertyName })
|
|
2140
|
-
.where(eq(metadata.propertyId, propertyId));
|
|
2141
|
-
}
|
|
2142
|
-
// Fallback: update metadata with null propertyId (legacy rows) by propertyName + modelType
|
|
2143
|
-
const modelType = easName(modelName);
|
|
2144
|
-
const toUpdateByFallback = await db
|
|
2145
|
-
.select({ localId: metadata.localId })
|
|
2146
|
-
.from(metadata)
|
|
2147
|
-
.where(and(eq(metadata.propertyName, oldPropertyName), eq(metadata.modelType, modelType), isNull(metadata.propertyId)));
|
|
2148
|
-
const updatedByFallback = toUpdateByFallback.length;
|
|
2149
|
-
if (updatedByFallback > 0) {
|
|
2150
|
-
await db
|
|
2151
|
-
.update(metadata)
|
|
2152
|
-
.set({ propertyName: newPropertyName })
|
|
2153
|
-
.where(and(eq(metadata.propertyName, oldPropertyName), eq(metadata.modelType, modelType), isNull(metadata.propertyId)));
|
|
2154
|
-
}
|
|
2155
|
-
if (updatedByFallback > 0) {
|
|
2156
|
-
logger$3(`[migrateMetadataForPropertyRename] Fallback updated ${updatedByFallback} rows (propertyId was null)`);
|
|
2157
|
-
}
|
|
2158
|
-
// Edits of the property still in the outbox (ADR 0019) apply under its new name
|
|
2159
|
-
await db
|
|
2160
|
-
.update(pendingWrites)
|
|
2161
|
-
.set({ propertyName: newPropertyName })
|
|
2162
|
-
.where(and(eq(pendingWrites.propertyName, oldPropertyName), eq(pendingWrites.status, 'pending'), inArray(pendingWrites.seedLocalId, db.select({ localId: seeds.localId }).from(seeds).where(eq(seeds.type, modelType)))));
|
|
2163
|
-
const total = updatedByPropertyId + updatedByFallback;
|
|
2164
|
-
if (total > 0) {
|
|
2165
|
-
logger$3(`[migrateMetadataForPropertyRename] Migrated ${total} metadata rows for ${modelName}.${oldPropertyName} -> ${newPropertyName}`);
|
|
2166
|
-
}
|
|
2167
|
-
return total;
|
|
2168
|
-
}
|
|
2169
|
-
/**
|
|
2170
|
-
* Moves a renamed property's metadata rows to its new name: rows linked by propertyId, and legacy
|
|
2171
|
-
* rows without one by name and model type. Each stored form of the name moves to the same form of
|
|
2172
|
-
* the new one: the key (`author` → `writer`), a single relation's `<key>Id` (`authorId` →
|
|
2173
|
-
* `writerId`), a list relation's storage name (`authorIdentityIds` → `writerIdentityIds`).
|
|
2174
|
-
* Returns the number of rows moved.
|
|
2175
|
-
*/
|
|
2176
|
-
async function moveMetadataToRenamedProperty(db, { propertyId, modelType, oldName, newName, propertyDef, }) {
|
|
2177
|
-
const forms = (name) => [
|
|
2178
|
-
name,
|
|
2179
|
-
toMetadataPropertyName(name, propertyDef?.dataType, propertyDef?.refValueType),
|
|
2180
|
-
listRelationEasPropertyName(name, propertyDef) ?? name,
|
|
2181
|
-
];
|
|
2182
|
-
const oldForms = forms(oldName);
|
|
2183
|
-
const newForms = forms(newName);
|
|
2184
|
-
let moved = 0;
|
|
2185
|
-
const seen = new Set();
|
|
2186
|
-
for (let i = 0; i < oldForms.length; i++) {
|
|
2187
|
-
const from = oldForms[i];
|
|
2188
|
-
const to = newForms[i];
|
|
2189
|
-
if (from === to || seen.has(from))
|
|
2190
|
-
continue;
|
|
2191
|
-
seen.add(from);
|
|
2192
|
-
const byId = await db
|
|
2193
|
-
.update(metadata)
|
|
2194
|
-
.set({ propertyName: to })
|
|
2195
|
-
.where(and(eq(metadata.propertyId, propertyId), eq(metadata.propertyName, from)))
|
|
2196
|
-
.returning({ localId: metadata.localId });
|
|
2197
|
-
const legacy = await db
|
|
2198
|
-
.update(metadata)
|
|
2199
|
-
.set({ propertyName: to })
|
|
2200
|
-
.where(and(isNull(metadata.propertyId), eq(metadata.propertyName, from), eq(metadata.modelType, modelType)))
|
|
2201
|
-
.returning({ localId: metadata.localId });
|
|
2202
|
-
moved += byId.length + legacy.length;
|
|
2203
|
-
}
|
|
2204
|
-
if (moved > 0)
|
|
2205
|
-
logger$3(`[moveMetadataToRenamedProperty] moved ${moved} metadata rows ${oldName} -> ${newName}`);
|
|
2206
|
-
return moved;
|
|
2207
|
-
}
|
|
2208
|
-
/**
|
|
2209
|
-
* True when the models row is linked (model_schemas) to any schema other than schemaId.
|
|
2210
|
-
*/
|
|
2211
|
-
const isModelLinkedToOtherSchema = async (db, modelId, schemaId) => {
|
|
2212
|
-
const links = await db
|
|
2213
|
-
.select({ schemaId: modelSchemas.schemaId })
|
|
2214
|
-
.from(modelSchemas)
|
|
2215
|
-
.where(eq(modelSchemas.modelId, modelId));
|
|
2216
|
-
return links.some((l) => l.schemaId !== schemaId);
|
|
2217
|
-
};
|
|
2218
|
-
const isModelLinkedToAnySchema = async (db, modelId) => {
|
|
2219
|
-
const links = await db
|
|
2220
|
-
.select({ id: modelSchemas.id })
|
|
2221
|
-
.from(modelSchemas)
|
|
2222
|
-
.where(eq(modelSchemas.modelId, modelId))
|
|
2223
|
-
.limit(1);
|
|
2224
|
-
return links.length > 0;
|
|
2225
|
-
};
|
|
2226
|
-
/**
|
|
2227
|
-
* Find or create a models row by schemaFileId (preferred) or name.
|
|
2228
|
-
* Reuses an existing same-name row (especially schemaFileId=null stubs created via
|
|
2229
|
-
* ref resolution) instead of inserting a second row — models.name is not unique,
|
|
2230
|
-
* and duplicate names break name lookups (e.g. Resource.tags → Tag).
|
|
2231
|
-
*
|
|
2232
|
-
* When modelFileId is given, a same-name row that already carries a *different*
|
|
2233
|
-
* non-null schemaFileId is only adopted if no other schema is linked to it via
|
|
2234
|
-
* model_schemas. Otherwise it belongs to another schema (e.g. two schemas that each
|
|
2235
|
-
* define "Post"), and re-id'ing it would strand that schema's cached Model instance,
|
|
2236
|
-
* so a new row is inserted instead.
|
|
2237
|
-
*/
|
|
2238
|
-
const findOrCreateModelRecord = async (db, modelName, modelFileId, schemaId) => {
|
|
2239
|
-
if (modelFileId) {
|
|
2240
|
-
const byFileId = await db
|
|
2241
|
-
.select()
|
|
2242
|
-
.from(models)
|
|
2243
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
2244
|
-
.limit(1);
|
|
2245
|
-
if (byFileId.length > 0) {
|
|
2246
|
-
const record = byFileId[0];
|
|
2247
|
-
if (record.name !== modelName) {
|
|
2248
|
-
await db
|
|
2249
|
-
.update(models)
|
|
2250
|
-
.set({ name: modelName })
|
|
2251
|
-
.where(eq(models.id, record.id));
|
|
2252
|
-
return { ...record, name: modelName };
|
|
2253
|
-
}
|
|
2254
|
-
return record;
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
const byName = (await db
|
|
2258
|
-
.select()
|
|
2259
|
-
.from(models)
|
|
2260
|
-
.where(eq(models.name, modelName)));
|
|
2261
|
-
if (byName.length > 0) {
|
|
2262
|
-
// Prefer exact file-id match, then (for refs without a file id) this schema's row, then an
|
|
2263
|
-
// adoptable null schemaFileId stub, then any single row.
|
|
2264
|
-
let chosen = modelFileId
|
|
2265
|
-
? byName.find((r) => r.schemaFileId === modelFileId)
|
|
2266
|
-
: undefined;
|
|
2267
|
-
if (!chosen && !modelFileId && schemaId && byName.length > 1) {
|
|
2268
|
-
for (const candidate of byName) {
|
|
2269
|
-
if (!(await isModelLinkedToOtherSchema(db, candidate.id, schemaId))) {
|
|
2270
|
-
const links = await db
|
|
2271
|
-
.select({ schemaId: modelSchemas.schemaId })
|
|
2272
|
-
.from(modelSchemas)
|
|
2273
|
-
.where(eq(modelSchemas.modelId, candidate.id));
|
|
2274
|
-
if (links.length > 0) {
|
|
2275
|
-
chosen = candidate;
|
|
2276
|
-
break;
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
chosen ?? (chosen = byName.find((r) => !r.schemaFileId));
|
|
2282
|
-
if (!chosen && modelFileId) {
|
|
2283
|
-
// Every same-name row has a different file id. Only adopt one that no other
|
|
2284
|
-
// schema claims; otherwise fall through and insert a row for this file id.
|
|
2285
|
-
// A row with a file id that no schema links is a deleted schema's leftover (its old
|
|
2286
|
-
// properties are still attached), not an unclaimed row: don't adopt it either.
|
|
2287
|
-
for (const candidate of byName) {
|
|
2288
|
-
if (candidate.schemaFileId && !(await isModelLinkedToAnySchema(db, candidate.id)))
|
|
2289
|
-
continue;
|
|
2290
|
-
if (!(await isModelLinkedToOtherSchema(db, candidate.id, schemaId))) {
|
|
2291
|
-
chosen = candidate;
|
|
2292
|
-
break;
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
else if (!chosen) {
|
|
2297
|
-
chosen =
|
|
2298
|
-
(byName.length === 1 ? byName[0] : undefined) ||
|
|
2299
|
-
byName.find((r) => !!r.schemaFileId) ||
|
|
2300
|
-
byName[0];
|
|
2301
|
-
}
|
|
2302
|
-
if (chosen && modelFileId && chosen.schemaFileId !== modelFileId) {
|
|
2303
|
-
const conflict = await db
|
|
2304
|
-
.select()
|
|
2305
|
-
.from(models)
|
|
2306
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
2307
|
-
.limit(1);
|
|
2308
|
-
if (conflict.length > 0) {
|
|
2309
|
-
return conflict[0];
|
|
2310
|
-
}
|
|
2311
|
-
// Adopt stub (null schemaFileId) or an unclaimed same-name row by attaching this file id.
|
|
2312
|
-
await db
|
|
2313
|
-
.update(models)
|
|
2314
|
-
.set({ schemaFileId: modelFileId, isEdited: false })
|
|
2315
|
-
.where(eq(models.id, chosen.id));
|
|
2316
|
-
chosen = { ...chosen, schemaFileId: modelFileId, isEdited: false };
|
|
2317
|
-
logger$3(`Adopted existing model "${modelName}" (id: ${chosen.id}) with schemaFileId "${modelFileId}"`);
|
|
2318
|
-
}
|
|
2319
|
-
if (chosen) {
|
|
2320
|
-
return chosen;
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
try {
|
|
2324
|
-
// A Model instance's own write can insert this file id between the lookups above and here
|
|
2325
|
-
// (schema import writes each model twice at once). DO NOTHING instead of a failed statement,
|
|
2326
|
-
// which sqlite-wasm logs as a warning even though the error is caught.
|
|
2327
|
-
const inserted = await db
|
|
2328
|
-
.insert(models)
|
|
2329
|
-
.values({
|
|
2330
|
-
name: modelName,
|
|
2331
|
-
schemaFileId: modelFileId || null,
|
|
2332
|
-
isEdited: false,
|
|
2333
|
-
})
|
|
2334
|
-
.onConflictDoNothing()
|
|
2335
|
-
.returning();
|
|
2336
|
-
if (inserted.length > 0) {
|
|
2337
|
-
return inserted[0];
|
|
2338
|
-
}
|
|
2339
|
-
if (modelFileId) {
|
|
2340
|
-
const existing = await db
|
|
2341
|
-
.select()
|
|
2342
|
-
.from(models)
|
|
2343
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
2344
|
-
.limit(1);
|
|
2345
|
-
if (existing.length > 0) {
|
|
2346
|
-
return existing[0];
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
throw new Error(`Model "${modelName}" was not inserted and no row has schemaFileId "${modelFileId}"`);
|
|
2350
|
-
}
|
|
2351
|
-
catch (error) {
|
|
2352
|
-
if (isSqliteUniqueConstraintError(error) && modelFileId) {
|
|
2353
|
-
const existing = await db
|
|
2354
|
-
.select()
|
|
2355
|
-
.from(models)
|
|
2356
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
2357
|
-
.limit(1);
|
|
2358
|
-
if (existing.length > 0) {
|
|
2359
|
-
return existing[0];
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
// Race: another writer inserted by name — reuse rather than throw on duplicates.
|
|
2363
|
-
const raced = (await db
|
|
2364
|
-
.select()
|
|
2365
|
-
.from(models)
|
|
2366
|
-
.where(eq(models.name, modelName)));
|
|
2367
|
-
if (raced.length > 0) {
|
|
2368
|
-
return ((modelFileId
|
|
2369
|
-
? raced.find((r) => r.schemaFileId === modelFileId)
|
|
2370
|
-
: undefined) ||
|
|
2371
|
-
raced.find((r) => !r.schemaFileId) ||
|
|
2372
|
-
raced[0]);
|
|
2373
|
-
}
|
|
2374
|
-
throw error;
|
|
2375
|
-
}
|
|
2376
|
-
};
|
|
2377
|
-
/**
|
|
2378
|
-
* Find or create the models row a Relation property on ownerModelId points to. Model names are only
|
|
2379
|
-
* unique per schema, so the ref is looked up in the owner's schema(s) first; a global name lookup
|
|
2380
|
-
* by name threw "Multiple records found" once two schemas defined the ref model.
|
|
2381
|
-
*/
|
|
2382
|
-
const findOrCreateRefModelRecord = async (db, refModelName, ownerModelId) => {
|
|
2383
|
-
try {
|
|
2384
|
-
const ref = await resolveRefModelRecord(refModelName, { modelId: ownerModelId }, db);
|
|
2385
|
-
if (ref)
|
|
2386
|
-
return ref;
|
|
2387
|
-
}
|
|
2388
|
-
catch (error) {
|
|
2389
|
-
// Not in the owner's schema and defined by several others: pick/create like an import does
|
|
2390
|
-
if (!(error instanceof AmbiguousModelError))
|
|
2391
|
-
throw error;
|
|
2392
|
-
}
|
|
2393
|
-
const [ownerLink] = await db
|
|
2394
|
-
.select({ schemaId: modelSchemas.schemaId })
|
|
2395
|
-
.from(modelSchemas)
|
|
2396
|
-
.where(eq(modelSchemas.modelId, ownerModelId))
|
|
2397
|
-
.limit(1);
|
|
2398
|
-
return findOrCreateModelRecord(db, refModelName, undefined, ownerLink?.schemaId ?? undefined);
|
|
2399
|
-
};
|
|
2400
|
-
/**
|
|
2401
|
-
* Searches the database for an existing schema by schemaFileId (preferred) or name and creates it if it doesn't exist
|
|
2402
|
-
* @param schema - The schema to add to the database
|
|
2403
|
-
* @param schemaFileId - Optional schemaFileId from JSON file for change tracking
|
|
2404
|
-
* @param schemaData - Optional full schema content as JSON string (SchemaFileFormat)
|
|
2405
|
-
* @param isDraft - Optional flag indicating if schema is in draft state (default: false)
|
|
2406
|
-
* @param isEdited - Optional flag indicating if schema has been edited locally (default: false)
|
|
2407
|
-
* @returns The schema record (either existing or newly created)
|
|
2408
|
-
*/
|
|
2409
|
-
const addSchemaToDb = async (schema, schemaFileId, schemaData, isDraft, isEdited) => {
|
|
2410
|
-
const db = BaseDb.getAppDb();
|
|
2411
|
-
if (!db) {
|
|
2412
|
-
throw new Error('Database not found');
|
|
2413
|
-
}
|
|
2414
|
-
// The stored schema file may change its nameMatch levels (ADR 0009).
|
|
2415
|
-
clearNameMatchLevels();
|
|
2416
|
-
// First, try to find existing schema by schemaFileId (preferred for change tracking)
|
|
2417
|
-
if (schemaFileId) {
|
|
2418
|
-
const existingByFileId = await db
|
|
2419
|
-
.select()
|
|
2420
|
-
.from(schemas)
|
|
2421
|
-
.where(eq(schemas.schemaFileId, schemaFileId))
|
|
2422
|
-
.limit(1);
|
|
2423
|
-
if (existingByFileId.length > 0) {
|
|
2424
|
-
// Update fields if provided
|
|
2425
|
-
const updates = {};
|
|
2426
|
-
if (schemaFileId && !existingByFileId[0].schemaFileId) {
|
|
2427
|
-
updates.schemaFileId = schemaFileId;
|
|
2428
|
-
}
|
|
2429
|
-
// Always update name if it's different (handles name changes)
|
|
2430
|
-
if (schema.name && existingByFileId[0].name !== schema.name) {
|
|
2431
|
-
updates.name = schema.name;
|
|
2432
|
-
}
|
|
2433
|
-
if (schemaData !== undefined) {
|
|
2434
|
-
updates.schemaData = schemaData;
|
|
2435
|
-
}
|
|
2436
|
-
if (schema.version !== undefined && existingByFileId[0].version !== schema.version) {
|
|
2437
|
-
updates.version = schema.version;
|
|
2438
|
-
}
|
|
2439
|
-
if (isDraft !== undefined && existingByFileId[0].isDraft !== isDraft) {
|
|
2440
|
-
updates.isDraft = isDraft;
|
|
2441
|
-
}
|
|
2442
|
-
if (isEdited !== undefined && existingByFileId[0].isEdited !== isEdited) {
|
|
2443
|
-
updates.isEdited = isEdited;
|
|
2444
|
-
}
|
|
2445
|
-
if (schema.updatedAt && existingByFileId[0].updatedAt !== schema.updatedAt) {
|
|
2446
|
-
updates.updatedAt = schema.updatedAt;
|
|
2447
|
-
}
|
|
2448
|
-
if (Object.keys(updates).length > 0) {
|
|
2449
|
-
await db
|
|
2450
|
-
.update(schemas)
|
|
2451
|
-
.set(updates)
|
|
2452
|
-
.where(eq(schemas.id, existingByFileId[0].id));
|
|
2453
|
-
return { ...existingByFileId[0], ...updates };
|
|
2454
|
-
}
|
|
2455
|
-
return existingByFileId[0];
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
// For drafts, check if there's an existing draft by name (not by schemaFileId)
|
|
2459
|
-
if (isDraft === true) {
|
|
2460
|
-
const existingDrafts = await db
|
|
2461
|
-
.select()
|
|
2462
|
-
.from(schemas)
|
|
2463
|
-
.where(eq(schemas.name, schema.name))
|
|
2464
|
-
.limit(1);
|
|
2465
|
-
if (existingDrafts.length > 0 && existingDrafts[0].isDraft === true) {
|
|
2466
|
-
// Update existing draft
|
|
2467
|
-
const updates = {};
|
|
2468
|
-
// Update schemaFileId if provided and different from existing
|
|
2469
|
-
if (schemaFileId && existingDrafts[0].schemaFileId !== schemaFileId) {
|
|
2470
|
-
updates.schemaFileId = schemaFileId;
|
|
2471
|
-
}
|
|
2472
|
-
if (schemaData !== undefined) {
|
|
2473
|
-
updates.schemaData = schemaData;
|
|
2474
|
-
}
|
|
2475
|
-
if (schema.version !== undefined) {
|
|
2476
|
-
updates.version = schema.version;
|
|
2477
|
-
}
|
|
2478
|
-
updates.updatedAt = schema.updatedAt || Date.now();
|
|
2479
|
-
if (Object.keys(updates).length > 0) {
|
|
2480
|
-
await db
|
|
2481
|
-
.update(schemas)
|
|
2482
|
-
.set(updates)
|
|
2483
|
-
.where(eq(schemas.id, existingDrafts[0].id));
|
|
2484
|
-
return { ...existingDrafts[0], ...updates };
|
|
2485
|
-
}
|
|
2486
|
-
return existingDrafts[0];
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
// Fallback: Search for existing schema by name
|
|
2490
|
-
// BUT: If we have a schemaFileId, also check if any record with that schemaFileId exists
|
|
2491
|
-
// (in case the name changed but we're looking up by the new name)
|
|
2492
|
-
if (schemaFileId) {
|
|
2493
|
-
// Double-check by schemaFileId - maybe the name changed and we're looking up by new name
|
|
2494
|
-
const doubleCheckByFileId = await db
|
|
2495
|
-
.select()
|
|
2496
|
-
.from(schemas)
|
|
2497
|
-
.where(eq(schemas.schemaFileId, schemaFileId))
|
|
2498
|
-
.limit(1);
|
|
2499
|
-
if (doubleCheckByFileId.length > 0) {
|
|
2500
|
-
// Found by schemaFileId - update it (name might have changed)
|
|
2501
|
-
const updates = {};
|
|
2502
|
-
// Always update name if it's different (handles name changes)
|
|
2503
|
-
if (schema.name && doubleCheckByFileId[0].name !== schema.name) {
|
|
2504
|
-
updates.name = schema.name;
|
|
2505
|
-
}
|
|
2506
|
-
if (schemaData !== undefined) {
|
|
2507
|
-
updates.schemaData = schemaData;
|
|
2508
|
-
}
|
|
2509
|
-
if (schema.version !== undefined && doubleCheckByFileId[0].version !== schema.version) {
|
|
2510
|
-
updates.version = schema.version;
|
|
2511
|
-
}
|
|
2512
|
-
if (isDraft !== undefined && doubleCheckByFileId[0].isDraft !== isDraft) {
|
|
2513
|
-
updates.isDraft = isDraft;
|
|
2514
|
-
}
|
|
2515
|
-
if (isEdited !== undefined && doubleCheckByFileId[0].isEdited !== isEdited) {
|
|
2516
|
-
updates.isEdited = isEdited;
|
|
2517
|
-
}
|
|
2518
|
-
if (schema.updatedAt && doubleCheckByFileId[0].updatedAt !== schema.updatedAt) {
|
|
2519
|
-
updates.updatedAt = schema.updatedAt;
|
|
2520
|
-
}
|
|
2521
|
-
if (Object.keys(updates).length > 0) {
|
|
2522
|
-
await db
|
|
2523
|
-
.update(schemas)
|
|
2524
|
-
.set(updates)
|
|
2525
|
-
.where(eq(schemas.id, doubleCheckByFileId[0].id));
|
|
2526
|
-
return { ...doubleCheckByFileId[0], ...updates };
|
|
2527
|
-
}
|
|
2528
|
-
return doubleCheckByFileId[0];
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
// Final fallback: Search by name (only if no schemaFileId or schemaFileId lookup failed)
|
|
2532
|
-
// If isDraft is explicitly false, first try to find non-draft, but also check for drafts
|
|
2533
|
-
// to handle the case where a draft exists and we're loading a non-draft version
|
|
2534
|
-
let existingSchemas = await db
|
|
2535
|
-
.select()
|
|
2536
|
-
.from(schemas)
|
|
2537
|
-
.where(isDraft === false
|
|
2538
|
-
? and(eq(schemas.name, schema.name), eq(schemas.isDraft, false))
|
|
2539
|
-
: eq(schemas.name, schema.name))
|
|
2540
|
-
.limit(1);
|
|
2541
|
-
// If isDraft is false and we didn't find a non-draft, check for a draft with the same name
|
|
2542
|
-
// This handles the case where a draft was created first and we're now loading the non-draft version
|
|
2543
|
-
if (isDraft === false && existingSchemas.length === 0) {
|
|
2544
|
-
const draftSchemas = await db
|
|
2545
|
-
.select()
|
|
2546
|
-
.from(schemas)
|
|
2547
|
-
.where(and(eq(schemas.name, schema.name), eq(schemas.isDraft, true)))
|
|
2548
|
-
.limit(1);
|
|
2549
|
-
if (draftSchemas.length > 0) {
|
|
2550
|
-
// Found a draft - we'll update it to non-draft below
|
|
2551
|
-
logger$3(`Found draft schema "${schema.name}" with schemaFileId "${draftSchemas[0].schemaFileId}", will update to non-draft with schemaFileId "${schemaFileId}"`);
|
|
2552
|
-
existingSchemas = draftSchemas;
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
if (existingSchemas.length > 0) {
|
|
2556
|
-
// The row is per schema name and holds its latest version. A complete schema file can bring
|
|
2557
|
-
// a version under its own id (SDK version bumps keep the id; imported files may not).
|
|
2558
|
-
const isOtherVersionFile = !!schemaFileId &&
|
|
2559
|
-
!!existingSchemas[0].schemaFileId &&
|
|
2560
|
-
existingSchemas[0].schemaFileId !== schemaFileId &&
|
|
2561
|
-
existingSchemas[0].isDraft !== true &&
|
|
2562
|
-
schema.version !== undefined &&
|
|
2563
|
-
schema.version !== existingSchemas[0].version;
|
|
2564
|
-
if (isOtherVersionFile && schema.version < existingSchemas[0].version) {
|
|
2565
|
-
// An older version's file: rolling the row back to it would leave the newer version's id
|
|
2566
|
-
// pointing at the older version's data.
|
|
2567
|
-
logger$3(`Keeping schema "${schema.name}" at v${existingSchemas[0].version}; not replacing it with older v${schema.version}`);
|
|
2568
|
-
return existingSchemas[0];
|
|
2569
|
-
}
|
|
2570
|
-
// Update fields if provided
|
|
2571
|
-
const updates = {};
|
|
2572
|
-
// Update schemaFileId if provided and different from existing (or if existing doesn't have one or is null)
|
|
2573
|
-
// This is important for preserving IDs from schema files when drafts exist
|
|
2574
|
-
// BUT: Don't overwrite a non-draft schema's schemaFileId with a new generated one
|
|
2575
|
-
// This prevents loadOrCreateSchema from overwriting correct IDs from fixture files
|
|
2576
|
-
if (schemaFileId && existingSchemas[0].schemaFileId !== schemaFileId) {
|
|
2577
|
-
// Only update if:
|
|
2578
|
-
// 1. Existing schema is a draft (can be updated)
|
|
2579
|
-
// 2. Existing schema has no schemaFileId (should be set)
|
|
2580
|
-
// 3. We're explicitly setting isDraft to false (updating from draft to non-draft)
|
|
2581
|
-
// 4. It's a newer version's file: the row takes that version's data, so it takes its id too
|
|
2582
|
-
const shouldUpdate = existingSchemas[0].isDraft === true ||
|
|
2583
|
-
!existingSchemas[0].schemaFileId ||
|
|
2584
|
-
(isDraft === false && existingSchemas[0].isDraft === true) ||
|
|
2585
|
-
isOtherVersionFile;
|
|
2586
|
-
if (shouldUpdate) {
|
|
2587
|
-
logger$3(`Updating schemaFileId from "${existingSchemas[0].schemaFileId}" to "${schemaFileId}" for schema "${schema.name}"`);
|
|
2588
|
-
updates.schemaFileId = schemaFileId;
|
|
2589
|
-
}
|
|
2590
|
-
else {
|
|
2591
|
-
logger$3(`Preserving existing schemaFileId "${existingSchemas[0].schemaFileId}" for non-draft schema "${schema.name}" (new schemaFileId "${schemaFileId}" would overwrite it)`);
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
// Always update name if it's different (handles name changes)
|
|
2595
|
-
if (schema.name && existingSchemas[0].name !== schema.name) {
|
|
2596
|
-
updates.name = schema.name;
|
|
2597
|
-
}
|
|
2598
|
-
if (schemaData !== undefined) {
|
|
2599
|
-
updates.schemaData = schemaData;
|
|
2600
|
-
}
|
|
2601
|
-
if (schema.version !== undefined && existingSchemas[0].version !== schema.version) {
|
|
2602
|
-
updates.version = schema.version;
|
|
2603
|
-
}
|
|
2604
|
-
// If isDraft is explicitly false and we found a draft, update it to non-draft
|
|
2605
|
-
if (isDraft === false && existingSchemas[0].isDraft === true) {
|
|
2606
|
-
updates.isDraft = false;
|
|
2607
|
-
}
|
|
2608
|
-
else if (isDraft !== undefined && existingSchemas[0].isDraft !== isDraft) {
|
|
2609
|
-
updates.isDraft = isDraft;
|
|
2610
|
-
}
|
|
2611
|
-
if (isEdited !== undefined && existingSchemas[0].isEdited !== isEdited) {
|
|
2612
|
-
updates.isEdited = isEdited;
|
|
2613
|
-
}
|
|
2614
|
-
if (schema.updatedAt && existingSchemas[0].updatedAt !== schema.updatedAt) {
|
|
2615
|
-
updates.updatedAt = schema.updatedAt;
|
|
2616
|
-
}
|
|
2617
|
-
if (Object.keys(updates).length > 0) {
|
|
2618
|
-
logger$3(`Applying updates to schema "${schema.name}" (id: ${existingSchemas[0].id}):`, Object.keys(updates));
|
|
2619
|
-
await db
|
|
2620
|
-
.update(schemas)
|
|
2621
|
-
.set(updates)
|
|
2622
|
-
.where(eq(schemas.id, existingSchemas[0].id));
|
|
2623
|
-
const updated = { ...existingSchemas[0], ...updates };
|
|
2624
|
-
logger$3(`Schema "${schema.name}" updated. New schemaFileId: "${updated.schemaFileId}", isDraft: ${updated.isDraft}`);
|
|
2625
|
-
return updated;
|
|
2626
|
-
}
|
|
2627
|
-
logger$3(`No updates needed for schema "${schema.name}" (id: ${existingSchemas[0].id})`);
|
|
2628
|
-
return existingSchemas[0];
|
|
2629
|
-
}
|
|
2630
|
-
const newSchema = await db.insert(schemas).values({
|
|
2631
|
-
name: schema.name,
|
|
2632
|
-
version: schema.version,
|
|
2633
|
-
schemaFileId: schemaFileId || null,
|
|
2634
|
-
schemaData: schemaData || null,
|
|
2635
|
-
isDraft: isDraft ?? false,
|
|
2636
|
-
isEdited: isEdited ?? false,
|
|
2637
|
-
createdAt: schema.createdAt,
|
|
2638
|
-
updatedAt: schema.updatedAt,
|
|
2639
|
-
}).returning();
|
|
2640
|
-
// Notify React useSchemas so it can invalidate; live query often doesn't re-run when schemas table is inserted.
|
|
2641
|
-
if (typeof BroadcastChannel !== 'undefined') {
|
|
2642
|
-
try {
|
|
2643
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
2644
|
-
postBroadcast('seed-schemas-invalidate', {});
|
|
2645
|
-
}
|
|
2646
|
-
catch (_) { }
|
|
2647
|
-
}
|
|
2648
|
-
return newSchema[0];
|
|
2649
|
-
};
|
|
2650
|
-
/**
|
|
2651
|
-
* Rename a model in the database
|
|
2652
|
-
* Updates the model name and all properties that reference it
|
|
2653
|
-
* @param oldName - The current model name
|
|
2654
|
-
* @param newName - The new model name
|
|
2655
|
-
* @param schemaNameOrId - Schema name or schema ID to scope the rename (required when multiple schemas have models with the same name)
|
|
2656
|
-
* @returns The updated model record
|
|
2657
|
-
*/
|
|
2658
|
-
const renameModelInDb = async (oldName, newName, schemaNameOrId) => {
|
|
2659
|
-
const db = BaseDb.getAppDb();
|
|
2660
|
-
if (!db) {
|
|
2661
|
-
throw new Error('Database not found');
|
|
2662
|
-
}
|
|
2663
|
-
const { schemas: schemasTable } = await Promise.resolve().then(function () { return SchemaSchema; });
|
|
2664
|
-
// Find the model by old name, optionally scoped by schema
|
|
2665
|
-
let existingModels;
|
|
2666
|
-
if (schemaNameOrId !== undefined) {
|
|
2667
|
-
if (typeof schemaNameOrId === 'number') {
|
|
2668
|
-
const rows = await db
|
|
2669
|
-
.select({
|
|
2670
|
-
id: models.id,
|
|
2671
|
-
name: models.name,
|
|
2672
|
-
schemaFileId: models.schemaFileId,
|
|
2673
|
-
isEdited: models.isEdited,
|
|
2674
|
-
})
|
|
2675
|
-
.from(models)
|
|
2676
|
-
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
2677
|
-
.where(and(eq(models.name, oldName), eq(modelSchemas.schemaId, schemaNameOrId)))
|
|
2678
|
-
.limit(1);
|
|
2679
|
-
existingModels = rows;
|
|
2680
|
-
}
|
|
2681
|
-
else {
|
|
2682
|
-
const rows = await db
|
|
2683
|
-
.select({
|
|
2684
|
-
id: models.id,
|
|
2685
|
-
name: models.name,
|
|
2686
|
-
schemaFileId: models.schemaFileId,
|
|
2687
|
-
isEdited: models.isEdited,
|
|
2688
|
-
})
|
|
2689
|
-
.from(models)
|
|
2690
|
-
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
2691
|
-
.innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
|
|
2692
|
-
.where(and(eq(models.name, oldName), eq(schemasTable.name, schemaNameOrId)))
|
|
2693
|
-
.limit(1);
|
|
2694
|
-
existingModels = rows;
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
else {
|
|
2698
|
-
existingModels = await db
|
|
2699
|
-
.select({
|
|
2700
|
-
id: models.id,
|
|
2701
|
-
name: models.name,
|
|
2702
|
-
schemaFileId: models.schemaFileId,
|
|
2703
|
-
isEdited: models.isEdited,
|
|
2704
|
-
})
|
|
2705
|
-
.from(models)
|
|
2706
|
-
.where(eq(models.name, oldName));
|
|
2707
|
-
// Model names are only unique per schema: an unscoped rename must not pick one arbitrarily.
|
|
2708
|
-
if (existingModels.length > 1) {
|
|
2709
|
-
throw new Error(`Model "${oldName}" exists in more than one schema; pass the schema name or id to rename it`);
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
if (existingModels.length === 0) {
|
|
2713
|
-
if (schemaNameOrId !== undefined) {
|
|
2714
|
-
throw new Error(`Model "${oldName}" not found in schema "${schemaNameOrId}"`);
|
|
2715
|
-
}
|
|
2716
|
-
// Backward compat: model doesn't exist, create it with new name
|
|
2717
|
-
const newModel = await db.insert(models).values({ name: newName }).returning();
|
|
2718
|
-
return newModel[0];
|
|
2719
|
-
}
|
|
2720
|
-
const modelToRename = existingModels[0];
|
|
2721
|
-
// Check if a model with the new name already exists in the same schema
|
|
2722
|
-
let existingWithNewName;
|
|
2723
|
-
if (schemaNameOrId !== undefined) {
|
|
2724
|
-
if (typeof schemaNameOrId === 'number') {
|
|
2725
|
-
existingWithNewName = await db
|
|
2726
|
-
.select({
|
|
2727
|
-
id: models.id,
|
|
2728
|
-
name: models.name,
|
|
2729
|
-
schemaFileId: models.schemaFileId,
|
|
2730
|
-
isEdited: models.isEdited,
|
|
2731
|
-
})
|
|
2732
|
-
.from(models)
|
|
2733
|
-
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
2734
|
-
.where(and(eq(models.name, newName), eq(modelSchemas.schemaId, schemaNameOrId)))
|
|
2735
|
-
.limit(1);
|
|
2736
|
-
}
|
|
2737
|
-
else {
|
|
2738
|
-
existingWithNewName = await db
|
|
2739
|
-
.select({
|
|
2740
|
-
id: models.id,
|
|
2741
|
-
name: models.name,
|
|
2742
|
-
schemaFileId: models.schemaFileId,
|
|
2743
|
-
isEdited: models.isEdited,
|
|
2744
|
-
})
|
|
2745
|
-
.from(models)
|
|
2746
|
-
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
2747
|
-
.innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
|
|
2748
|
-
.where(and(eq(models.name, newName), eq(schemasTable.name, schemaNameOrId)))
|
|
2749
|
-
.limit(1);
|
|
2750
|
-
}
|
|
2751
|
-
}
|
|
2752
|
-
else {
|
|
2753
|
-
existingWithNewName = await db
|
|
2754
|
-
.select({
|
|
2755
|
-
id: models.id,
|
|
2756
|
-
name: models.name,
|
|
2757
|
-
schemaFileId: models.schemaFileId,
|
|
2758
|
-
isEdited: models.isEdited,
|
|
2759
|
-
})
|
|
2760
|
-
.from(models)
|
|
2761
|
-
.where(eq(models.name, newName))
|
|
2762
|
-
.limit(1);
|
|
2763
|
-
}
|
|
2764
|
-
if (existingWithNewName.length > 0) {
|
|
2765
|
-
// New name already exists in this schema, return it
|
|
2766
|
-
return existingWithNewName[0];
|
|
2767
|
-
}
|
|
2768
|
-
// Update the model name
|
|
2769
|
-
await db
|
|
2770
|
-
.update(models)
|
|
2771
|
-
.set({ name: newName })
|
|
2772
|
-
.where(eq(models.id, modelToRename.id));
|
|
2773
|
-
return { ...modelToRename, name: newName };
|
|
2774
|
-
};
|
|
2775
|
-
/**
|
|
2776
|
-
* Adds models and their properties to the database.
|
|
2777
|
-
* Optionally connects models to a schema via join records.
|
|
2778
|
-
* @param models - The model definitions to add
|
|
2779
|
-
* @param schema - Optional schema to connect models to
|
|
2780
|
-
* @param modelRenames - Optional map of old model names to new model names for handling renames
|
|
2781
|
-
* @param schemaFileData - Optional object containing schemaFileId mappings from JSON file: { schemaFileId, modelFileIds: Map<modelName, id>, propertyFileIds: Map<modelName, Map<propertyName, id>> }
|
|
2782
|
-
*/
|
|
2783
|
-
const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) => {
|
|
2784
|
-
const db = BaseDb.getAppDb();
|
|
2785
|
-
if (!db) {
|
|
2786
|
-
throw new Error('Database not found');
|
|
2787
|
-
}
|
|
2788
|
-
// Model rows and their schema links decide which schema file's nameMatch applies (ADR 0009).
|
|
2789
|
-
clearNameMatchLevels();
|
|
2790
|
-
// If schema is provided, add it to the database
|
|
2791
|
-
// Note: schema might already be a database record (with id) if passed from importJsonSchema
|
|
2792
|
-
let schemaRecord;
|
|
2793
|
-
if (schema) {
|
|
2794
|
-
// Check if schema already has an id (it's already a database record)
|
|
2795
|
-
// SchemaType includes id, so check if it's set
|
|
2796
|
-
if (schema.id !== null && schema.id !== undefined) {
|
|
2797
|
-
schemaRecord = schema;
|
|
2798
|
-
logger$3(`Using existing schema record with id: ${schemaRecord.id}`);
|
|
2799
|
-
}
|
|
2800
|
-
else {
|
|
2801
|
-
schemaRecord = await addSchemaToDb(schema, schemaFileData?.schemaFileId);
|
|
2802
|
-
logger$3(`Created/found schema record with id: ${schemaRecord.id}`);
|
|
2803
|
-
}
|
|
2804
|
-
}
|
|
2805
|
-
// Handle model renames first if provided
|
|
2806
|
-
if (modelRenames && schemaRecord) {
|
|
2807
|
-
const schemaNameOrId = schemaRecord.name ?? schemaRecord.id;
|
|
2808
|
-
for (const [oldName, newName] of modelRenames.entries()) {
|
|
2809
|
-
await renameModelInDb(oldName, newName, schemaNameOrId);
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
// Search for all models by schemaFileId (preferred) or name and create them if they don't exist
|
|
2813
|
-
const modelRecords = new Map();
|
|
2814
|
-
const modelProcessingErrors = [];
|
|
2815
|
-
// Property rows this call changed, so cached ModelProperty instances can be given the new values
|
|
2816
|
-
const updatedPropertyFileIds = [];
|
|
2817
|
-
for (const [modelName, modelClass] of Object.entries(models$1)) {
|
|
2818
|
-
try {
|
|
2819
|
-
const modelFileId = schemaFileData?.modelFileIds?.get(modelName);
|
|
2820
|
-
let modelRecord = await findOrCreateModelRecord(db, modelName, modelFileId, schemaRecord?.id);
|
|
2821
|
-
// Keep schemaFileId aligned when findOrCreate returned a row that still needs adoption
|
|
2822
|
-
// (rare: findOrCreate handles stubs and unclaimed same-name rows itself).
|
|
2823
|
-
if (modelFileId && modelRecord.schemaFileId !== modelFileId) {
|
|
2824
|
-
const existingWithFileId = await db
|
|
2825
|
-
.select()
|
|
2826
|
-
.from(models)
|
|
2827
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
2828
|
-
.limit(1);
|
|
2829
|
-
if (existingWithFileId.length > 0 && existingWithFileId[0].id !== modelRecord.id) {
|
|
2830
|
-
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}"`);
|
|
2831
|
-
modelRecord = existingWithFileId[0];
|
|
2832
|
-
}
|
|
2833
|
-
else if (!modelRecord.schemaFileId) {
|
|
2834
|
-
await db
|
|
2835
|
-
.update(models)
|
|
2836
|
-
.set({ schemaFileId: modelFileId })
|
|
2837
|
-
.where(eq(models.id, modelRecord.id));
|
|
2838
|
-
modelRecord = { ...modelRecord, schemaFileId: modelFileId };
|
|
2839
|
-
}
|
|
2840
|
-
}
|
|
2841
|
-
modelRecords.set(modelName, modelRecord);
|
|
2842
|
-
// Get all existing properties for this model upfront to handle renamed properties
|
|
2843
|
-
const allDbProperties = await db
|
|
2844
|
-
.select()
|
|
2845
|
-
.from(properties)
|
|
2846
|
-
.where(eq(properties.modelId, modelRecord.id));
|
|
2847
|
-
// Track which DB properties have been matched to schema properties
|
|
2848
|
-
const matchedDbPropertyIds = new Set();
|
|
2849
|
-
// Get properties from Model context (where they're stored as an object when Model is created)
|
|
2850
|
-
// modelClass.properties returns ModelProperty instances from DB, but we need the context properties
|
|
2851
|
-
// which are stored as an object when Model.create() is called with properties
|
|
2852
|
-
const modelContext = modelClass._getSnapshotContext?.() || {};
|
|
2853
|
-
const contextProperties = modelContext.properties || {};
|
|
2854
|
-
// When models are created from JSON, properties are stored as _pendingPropertyDefinitions
|
|
2855
|
-
const pendingProperties = modelContext._pendingPropertyDefinitions || {};
|
|
2856
|
-
// If context properties are empty, try modelClass.properties (for backward compatibility)
|
|
2857
|
-
// but convert ModelProperty instances to object format
|
|
2858
|
-
// Property definitions read from the schema file win: a cached Model keeps the properties it
|
|
2859
|
-
// was created with, so after a rename its context still has the old name.
|
|
2860
|
-
const filePropertyDefinitions = schemaFileData?.propertyDefinitions?.get(modelName);
|
|
2861
|
-
let schema = {};
|
|
2862
|
-
if (filePropertyDefinitions) {
|
|
2863
|
-
schema = filePropertyDefinitions;
|
|
2864
|
-
}
|
|
2865
|
-
else if (Object.keys(contextProperties).length > 0) {
|
|
2866
|
-
schema = contextProperties;
|
|
2867
|
-
}
|
|
2868
|
-
else if (Object.keys(pendingProperties).length > 0) {
|
|
2869
|
-
// Use pending property definitions (from JSON import)
|
|
2870
|
-
schema = pendingProperties;
|
|
2871
|
-
}
|
|
2872
|
-
else {
|
|
2873
|
-
const modelHelpersMod = await import('./model-oHA3YhOd.js');
|
|
2874
|
-
const { modelPropertiesToObject } = modelHelpersMod;
|
|
2875
|
-
const modelProperties = modelClass.properties || [];
|
|
2876
|
-
schema = modelPropertiesToObject(modelProperties);
|
|
2877
|
-
}
|
|
2878
|
-
// Search for all properties and create them if they don't exist
|
|
2879
|
-
// Properties are unique by name + modelId, so we search using both
|
|
2880
|
-
const schemaEntries = Object.entries(schema);
|
|
2881
|
-
for (let index = 0; index < schemaEntries.length; index++) {
|
|
2882
|
-
const [propertyName, propertyValues] = schemaEntries[index];
|
|
2883
|
-
if (!propertyValues) {
|
|
2884
|
-
throw new Error(`Property values not found for ${propertyName}`);
|
|
2885
|
-
}
|
|
2886
|
-
// Get property schemaFileId if available
|
|
2887
|
-
let propertyFileId = schemaFileData?.propertyFileIds?.get(modelName)?.get(propertyName);
|
|
2888
|
-
const propertyFileIdGenerated = !propertyFileId;
|
|
2889
|
-
// If no propertyFileId from map, generate a random ID
|
|
2890
|
-
// IDs should be generated in the import process before calling addModelsToDb
|
|
2891
|
-
if (!propertyFileId) {
|
|
2892
|
-
const { generateId } = await Promise.resolve().then(function () { return generateId$1; });
|
|
2893
|
-
propertyFileId = generateId();
|
|
2894
|
-
logger$3(`Generated propertyFileId "${propertyFileId}" for property "${modelName}:${propertyName}" (not found in propertyFileIds map)`);
|
|
2895
|
-
}
|
|
2896
|
-
// First, try to find by schemaFileId (preferred for change tracking)
|
|
2897
|
-
let existingProperty;
|
|
2898
|
-
if (propertyFileId) {
|
|
2899
|
-
existingProperty = allDbProperties.find((p) => p.schemaFileId === propertyFileId && !matchedDbPropertyIds.has(p.id));
|
|
2900
|
-
}
|
|
2901
|
-
// Fallback: check if property exists by name + modelId
|
|
2902
|
-
if (!existingProperty) {
|
|
2903
|
-
existingProperty = allDbProperties.find((p) => p.name === propertyName && !matchedDbPropertyIds.has(p.id));
|
|
2904
|
-
}
|
|
2905
|
-
// Prepare property values
|
|
2906
|
-
const propertyData = {
|
|
2907
|
-
name: propertyName,
|
|
2908
|
-
modelId: modelRecord.id,
|
|
2909
|
-
dataType: normalizeDataType(propertyValues.dataType),
|
|
2910
|
-
schemaFileId: propertyFileId || null,
|
|
2911
|
-
required: propertyValues.required ?? false,
|
|
2912
|
-
// The schema file is the source of truth here: no storage config means none.
|
|
2913
|
-
...propertyStorageColumns(propertyValues),
|
|
2914
|
-
};
|
|
2915
|
-
// Handle ref property - create ref model if needed
|
|
2916
|
-
// Check both ref and refModelName (refModelName is set by createModelFromJson)
|
|
2917
|
-
const refModelName = propertyValues.ref || propertyValues.refModelName;
|
|
2918
|
-
if (refModelName) {
|
|
2919
|
-
// When ref model was already processed in this batch, use it (avoids db round-trip)
|
|
2920
|
-
const cachedRef = modelRecords.get(refModelName);
|
|
2921
|
-
const refModelFileId = schemaFileData?.modelFileIds?.get(refModelName);
|
|
2922
|
-
const refModel = cachedRef
|
|
2923
|
-
? cachedRef
|
|
2924
|
-
: await findOrCreateModelRecord(db, refModelName, refModelFileId, schemaRecord?.id);
|
|
2925
|
-
if (!cachedRef) {
|
|
2926
|
-
modelRecords.set(refModelName, refModel);
|
|
2927
|
-
}
|
|
2928
|
-
propertyData.refModelId = refModel.id ?? null;
|
|
2929
|
-
}
|
|
2930
|
-
else {
|
|
2931
|
-
// If it's not a Relation type, ensure refModelId is null
|
|
2932
|
-
propertyData.refModelId = null;
|
|
2933
|
-
}
|
|
2934
|
-
if (propertyValues.refValueType) {
|
|
2935
|
-
propertyData.refValueType = normalizeDataType(propertyValues.refValueType);
|
|
2936
|
-
}
|
|
2937
|
-
else {
|
|
2938
|
-
// If refValueType is not set, ensure it's null
|
|
2939
|
-
propertyData.refValueType = null;
|
|
2940
|
-
}
|
|
2941
|
-
// ADR 0008: a rename needs the property's id. A name that matches no stored property by id
|
|
2942
|
-
// or name is a new property, even if one of the same type was removed in the same edit.
|
|
2943
|
-
if (existingProperty) {
|
|
2944
|
-
// Mark this DB property as matched
|
|
2945
|
-
matchedDbPropertyIds.add(existingProperty.id);
|
|
2946
|
-
// A property edited locally keeps its row.
|
|
2947
|
-
const isPropertyEdited = existingProperty.isEdited === true;
|
|
2948
|
-
if (isPropertyEdited) {
|
|
2949
|
-
// Property has been locally edited - skip update to preserve local changes
|
|
2950
|
-
logger$3(`Skipping update for property ${modelName}:${existingProperty.name} -> ${propertyName} - it has been locally edited and will be preserved`);
|
|
2951
|
-
continue;
|
|
2952
|
-
}
|
|
2953
|
-
// Property exists (possibly with different name) - update it with new values from schema file
|
|
2954
|
-
// This handles both regular updates and renames
|
|
2955
|
-
// Ensure schemaFileId is updated if we have it
|
|
2956
|
-
// Preserve existing isEdited value if it was true (local edits take precedence)
|
|
2957
|
-
const updateData = { ...propertyData };
|
|
2958
|
-
// A generated id must not replace the one the row already has from its schema file
|
|
2959
|
-
if (propertyFileIdGenerated && existingProperty.schemaFileId) {
|
|
2960
|
-
updateData.schemaFileId = existingProperty.schemaFileId;
|
|
2961
|
-
}
|
|
2962
|
-
if (existingProperty.isEdited === true) {
|
|
2963
|
-
// Preserve isEdited = true if property was locally edited
|
|
2964
|
-
delete updateData.isEdited;
|
|
2965
|
-
}
|
|
2966
|
-
else {
|
|
2967
|
-
updateData.isEdited = false;
|
|
2968
|
-
}
|
|
2969
|
-
// Skip db.update when data unchanged (avoids slow OPFS writes in browser)
|
|
2970
|
-
const unchanged = existingProperty.name === updateData.name &&
|
|
2971
|
-
existingProperty.dataType === updateData.dataType &&
|
|
2972
|
-
existingProperty.schemaFileId === (updateData.schemaFileId ?? null) &&
|
|
2973
|
-
existingProperty.required === (updateData.required ?? false) &&
|
|
2974
|
-
existingProperty.refModelId === (updateData.refModelId ?? null) &&
|
|
2975
|
-
existingProperty.refValueType === (updateData.refValueType ?? null) &&
|
|
2976
|
-
(existingProperty.storageType ?? null) === (updateData.storageType ?? null) &&
|
|
2977
|
-
(existingProperty.localStorageDir ?? null) === (updateData.localStorageDir ?? null) &&
|
|
2978
|
-
(existingProperty.filenameSuffix ?? null) === (updateData.filenameSuffix ?? null) &&
|
|
2979
|
-
existingProperty.isEdited === (updateData.isEdited ?? false);
|
|
2980
|
-
if (!unchanged) {
|
|
2981
|
-
await db
|
|
2982
|
-
.update(properties)
|
|
2983
|
-
.set(updateData)
|
|
2984
|
-
.where(eq(properties.id, existingProperty.id));
|
|
2985
|
-
const updatedFileId = updateData.schemaFileId ?? existingProperty.schemaFileId;
|
|
2986
|
-
if (updatedFileId)
|
|
2987
|
-
updatedPropertyFileIds.push(updatedFileId);
|
|
2988
|
-
}
|
|
2989
|
-
if (existingProperty.name !== propertyName) {
|
|
2990
|
-
// Renamed (matched by id): its values move to the new name.
|
|
2991
|
-
await moveMetadataToRenamedProperty(db, {
|
|
2992
|
-
propertyId: existingProperty.id,
|
|
2993
|
-
modelType: easName(modelName),
|
|
2994
|
-
oldName: existingProperty.name,
|
|
2995
|
-
newName: propertyName,
|
|
2996
|
-
propertyDef: propertyValues,
|
|
2997
|
-
});
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
else {
|
|
3001
|
-
// Property doesn't exist, create it with schemaFileId
|
|
3002
|
-
// Set isEdited = false when loading from schema file
|
|
3003
|
-
const propertyDataWithIsEdited = {
|
|
3004
|
-
...propertyData,
|
|
3005
|
-
isEdited: false,
|
|
3006
|
-
};
|
|
3007
|
-
// Global schemaFileId lookup (unique across all models) before insert — avoids
|
|
3008
|
-
// UNIQUE races when the property already exists on another model row or from a prior pass.
|
|
3009
|
-
if (propertyFileId) {
|
|
3010
|
-
const existingByFileId = await db
|
|
3011
|
-
.select()
|
|
3012
|
-
.from(properties)
|
|
3013
|
-
.where(eq(properties.schemaFileId, propertyFileId))
|
|
3014
|
-
.limit(1);
|
|
3015
|
-
if (existingByFileId.length > 0) {
|
|
3016
|
-
const found = existingByFileId[0];
|
|
3017
|
-
matchedDbPropertyIds.add(found.id);
|
|
3018
|
-
logger$3(`Property ${modelName}:${propertyName} already exists by schemaFileId "${propertyFileId}" (id: ${found.id}), skipping insert`);
|
|
3019
|
-
continue;
|
|
3020
|
-
}
|
|
3021
|
-
}
|
|
3022
|
-
logger$3(`Creating new property ${modelName}:${propertyName} with schemaFileId: ${propertyData.schemaFileId}`);
|
|
3023
|
-
// A conflict means the row already exists: the schema import's Model instances write their
|
|
3024
|
-
// properties at the same time as this. DO NOTHING keeps sqlite-wasm from logging a failed insert.
|
|
3025
|
-
const inserted = await db
|
|
3026
|
-
.insert(properties)
|
|
3027
|
-
.values(propertyDataWithIsEdited)
|
|
3028
|
-
.onConflictDoNothing()
|
|
3029
|
-
.returning({ id: properties.id });
|
|
3030
|
-
if (inserted.length === 0) {
|
|
3031
|
-
logger$3(`Property ${modelName}:${propertyName} already exists (UNIQUE constraint), treating as success`);
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
|
-
catch (error) {
|
|
3037
|
-
const errorMessage = error?.message || String(error);
|
|
3038
|
-
logger$3(`Error processing model "${modelName}": ${errorMessage}`);
|
|
3039
|
-
modelProcessingErrors.push({ modelName, error: errorMessage });
|
|
3040
|
-
// Continue processing other models even if one fails
|
|
3041
|
-
}
|
|
3042
|
-
}
|
|
3043
|
-
if (modelProcessingErrors.length > 0) {
|
|
3044
|
-
console.warn(`[addModelsToDb] Errors occurred while processing ${modelProcessingErrors.length} model(s):`, modelProcessingErrors.map(e => `${e.modelName}: ${e.error}`).join('; '));
|
|
3045
|
-
logger$3(`Errors processing models: ${modelProcessingErrors.map(e => e.modelName).join(', ')}`);
|
|
3046
|
-
}
|
|
3047
|
-
// If schema was provided, create modelSchema join records to connect models to the schema
|
|
3048
|
-
if (schemaRecord && schemaRecord.id) {
|
|
3049
|
-
const modelIds = [...modelRecords.entries()]
|
|
3050
|
-
.filter(([, r]) => r.id)
|
|
3051
|
-
.map(([name, r]) => ({ modelName: name, modelId: r.id }));
|
|
3052
|
-
if (modelIds.length === 0) {
|
|
3053
|
-
logger$3('No model IDs to link, skipping schema linking');
|
|
3054
|
-
}
|
|
3055
|
-
else {
|
|
3056
|
-
// Single batch query: get all existing joins for this schema
|
|
3057
|
-
const existingJoins = await db
|
|
3058
|
-
.select({ modelId: modelSchemas.modelId })
|
|
3059
|
-
.from(modelSchemas)
|
|
3060
|
-
.where(eq(modelSchemas.schemaId, schemaRecord.id));
|
|
3061
|
-
const existingSet = new Set(existingJoins.map((r) => r.modelId));
|
|
3062
|
-
const toInsert = modelIds.filter(({ modelId }) => !existingSet.has(modelId));
|
|
3063
|
-
if (toInsert.length > 0) {
|
|
3064
|
-
for (const { modelName, modelId } of toInsert) {
|
|
3065
|
-
if (!(await linkModelToSchema(db, modelId, schemaRecord.id)))
|
|
3066
|
-
continue;
|
|
3067
|
-
logger$3(`Created join table entry for model ${modelName} (id: ${modelId}) to schema (id: ${schemaRecord.id})`);
|
|
3068
|
-
}
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
else if (schemaRecord && !schemaRecord.id) {
|
|
3073
|
-
logger$3(`Warning: schemaRecord provided but has no id, cannot create join table entries`);
|
|
3074
|
-
}
|
|
3075
|
-
// A cached instance is otherwise never re-read from its row (createById returns it as is)
|
|
3076
|
-
await refreshCachedProperties(updatedPropertyFileIds);
|
|
3077
|
-
};
|
|
3078
|
-
/**
|
|
3079
|
-
* Loads models from the database for a given schema by querying the model_schemas join table.
|
|
3080
|
-
* This ensures that models added to the database (via model_schemas) are included even if
|
|
3081
|
-
* they're not in the schemaData JSON.
|
|
3082
|
-
* @param schemaId - The ID of the schema record in the database
|
|
3083
|
-
* @returns A map of model names to model data (compatible with SchemaFileFormat.models)
|
|
3084
|
-
*/
|
|
3085
|
-
const loadModelsFromDbForSchema = async (schemaId) => {
|
|
3086
|
-
const db = BaseDb.getAppDb();
|
|
3087
|
-
if (!db) {
|
|
3088
|
-
logger$3('Database not found, cannot load models from DB');
|
|
3089
|
-
return {};
|
|
3090
|
-
}
|
|
3091
|
-
try {
|
|
3092
|
-
// Query model_schemas join table to find all models linked to this schema
|
|
3093
|
-
const modelSchemaRecords = await db
|
|
3094
|
-
.select({
|
|
3095
|
-
modelId: modelSchemas.modelId,
|
|
3096
|
-
modelName: models.name,
|
|
3097
|
-
schemaFileId: models.schemaFileId,
|
|
3098
|
-
})
|
|
3099
|
-
.from(modelSchemas)
|
|
3100
|
-
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
3101
|
-
.where(eq(modelSchemas.schemaId, schemaId));
|
|
3102
|
-
const models$1 = {};
|
|
3103
|
-
// For each model, load its properties
|
|
3104
|
-
for (const { modelId, modelName, schemaFileId } of modelSchemaRecords) {
|
|
3105
|
-
if (!modelId || !modelName)
|
|
3106
|
-
continue;
|
|
3107
|
-
// Get all properties for this model
|
|
3108
|
-
const propertyRecords = await db
|
|
3109
|
-
.select()
|
|
3110
|
-
.from(properties)
|
|
3111
|
-
.where(eq(properties.modelId, modelId));
|
|
3112
|
-
// Reconstruct properties object
|
|
3113
|
-
const modelProperties = {};
|
|
3114
|
-
for (const prop of propertyRecords) {
|
|
3115
|
-
// Build a basic property structure from database fields
|
|
3116
|
-
// Note: This is a simplified reconstruction - full property schemas
|
|
3117
|
-
// (like Relation details, List configs, etc.) should come from schemaData
|
|
3118
|
-
const propertyData = {
|
|
3119
|
-
dataType: normalizeDataType(prop.dataType ?? undefined),
|
|
3120
|
-
};
|
|
3121
|
-
if (prop.schemaFileId) {
|
|
3122
|
-
propertyData.schemaFileId = prop.schemaFileId;
|
|
3123
|
-
}
|
|
3124
|
-
// Add ref information if it's a relation
|
|
3125
|
-
if (prop.refModelId) {
|
|
3126
|
-
// Get the referenced model name
|
|
3127
|
-
const refModelRecords = await db
|
|
3128
|
-
.select({ name: models.name })
|
|
3129
|
-
.from(models)
|
|
3130
|
-
.where(eq(models.id, prop.refModelId))
|
|
3131
|
-
.limit(1);
|
|
3132
|
-
if (refModelRecords.length > 0) {
|
|
3133
|
-
propertyData.ref = refModelRecords[0].name;
|
|
3134
|
-
}
|
|
3135
|
-
}
|
|
3136
|
-
if (prop.refValueType) {
|
|
3137
|
-
propertyData.refValueType = normalizeDataType(prop.refValueType ?? undefined);
|
|
3138
|
-
}
|
|
3139
|
-
if (prop.storageType)
|
|
3140
|
-
propertyData.storageType = prop.storageType;
|
|
3141
|
-
if (prop.localStorageDir)
|
|
3142
|
-
propertyData.localStorageDir = prop.localStorageDir;
|
|
3143
|
-
if (prop.filenameSuffix)
|
|
3144
|
-
propertyData.filenameSuffix = prop.filenameSuffix;
|
|
3145
|
-
modelProperties[prop.name] = propertyData;
|
|
3146
|
-
}
|
|
3147
|
-
// Create model structure. Include id (schemaFileId) so processSchemaFiles can populate
|
|
3148
|
-
// modelFileIds and Model.create can skip duplicate-name logic, preventing model
|
|
3149
|
-
// duplication on reload (Post 1 1, Post 2 1, etc.).
|
|
3150
|
-
models$1[modelName] = {
|
|
3151
|
-
properties: modelProperties,
|
|
3152
|
-
...(schemaFileId ? { id: schemaFileId } : {}),
|
|
3153
|
-
// Note: description would need to be stored separately
|
|
3154
|
-
// or reconstructed from schemaData if available
|
|
3155
|
-
};
|
|
3156
|
-
}
|
|
3157
|
-
logger$3(`Loaded ${Object.keys(models$1).length} models from database for schema ${schemaId}`);
|
|
3158
|
-
return models$1;
|
|
3159
|
-
}
|
|
3160
|
-
catch (error) {
|
|
3161
|
-
logger$3(`Error loading models from database for schema ${schemaId}:`, error);
|
|
3162
|
-
return {};
|
|
3163
|
-
}
|
|
3164
|
-
};
|
|
3165
|
-
/**
|
|
3166
|
-
* Merges the models {@link loadModelsFromDbForSchema} read from the database into a schema's
|
|
3167
|
-
* models. The database decides which models and properties exist and their stored fields; the
|
|
3168
|
-
* schema keeps what only it holds (nameMatch, description, validation, ...), found by id first so
|
|
3169
|
-
* a property renamed in the database keeps them. A schema property whose id a database property
|
|
3170
|
-
* has is dropped under its old name.
|
|
3171
|
-
*/
|
|
3172
|
-
const mergeDbModelsIntoSchemaModels = (schemaModels, dbModels) => {
|
|
3173
|
-
const merged = { ...(schemaModels ?? {}) };
|
|
3174
|
-
for (const [modelName, dbModel] of Object.entries(dbModels)) {
|
|
3175
|
-
const schemaModel = schemaModels?.[modelName];
|
|
3176
|
-
if (!schemaModel) {
|
|
3177
|
-
merged[modelName] = dbModel;
|
|
3178
|
-
continue;
|
|
3179
|
-
}
|
|
3180
|
-
const schemaProps = schemaModel.properties ?? {};
|
|
3181
|
-
const dbProps = dbModel.properties ?? {};
|
|
3182
|
-
const schemaNameById = new Map();
|
|
3183
|
-
for (const [name, prop] of Object.entries(schemaProps)) {
|
|
3184
|
-
const id = prop?.id ?? prop?.schemaFileId;
|
|
3185
|
-
if (id)
|
|
3186
|
-
schemaNameById.set(id, name);
|
|
3187
|
-
}
|
|
3188
|
-
const properties = {};
|
|
3189
|
-
const matched = new Set();
|
|
3190
|
-
for (const [name, dbProp] of Object.entries(dbProps)) {
|
|
3191
|
-
const byId = dbProp?.schemaFileId ? schemaNameById.get(dbProp.schemaFileId) : undefined;
|
|
3192
|
-
const byName = schemaProps[name];
|
|
3193
|
-
const byNameId = byName?.id ?? byName?.schemaFileId;
|
|
3194
|
-
// By name only when the ids don't say it's another property.
|
|
3195
|
-
const schemaName = byId ?? (byName && !(byNameId && dbProp?.schemaFileId && byNameId !== dbProp.schemaFileId) ? name : undefined);
|
|
3196
|
-
const schemaProp = schemaName ? schemaProps[schemaName] : undefined;
|
|
3197
|
-
if (schemaName && schemaProp)
|
|
3198
|
-
matched.add(schemaName);
|
|
3199
|
-
// The database's dataType and ref replace the file's type and model.
|
|
3200
|
-
const { type, model, ...fromSchema } = schemaProp ?? {};
|
|
3201
|
-
properties[name] = {
|
|
3202
|
-
...fromSchema,
|
|
3203
|
-
...(dbProp?.dataType === undefined && type !== undefined ? { type } : {}),
|
|
3204
|
-
...(dbProp?.ref === undefined && model !== undefined ? { model } : {}),
|
|
3205
|
-
...dbProp,
|
|
3206
|
-
};
|
|
3207
|
-
}
|
|
3208
|
-
const unmatched = Object.fromEntries(Object.entries(schemaProps).filter(([name]) => !matched.has(name)));
|
|
3209
|
-
merged[modelName] = { ...schemaModel, ...dbModel, properties: { ...unmatched, ...properties } };
|
|
3210
|
-
}
|
|
3211
|
-
return merged;
|
|
3212
|
-
};
|
|
3213
|
-
/**
|
|
3214
|
-
* Returns model name by database model ID.
|
|
3215
|
-
*/
|
|
3216
|
-
async function getModelNameByModelId(modelId) {
|
|
3217
|
-
const db = BaseDb.getAppDb();
|
|
3218
|
-
if (!db)
|
|
3219
|
-
return undefined;
|
|
3220
|
-
try {
|
|
3221
|
-
const rows = await db
|
|
3222
|
-
.select({ name: models.name })
|
|
3223
|
-
.from(models)
|
|
3224
|
-
.where(eq(models.id, modelId))
|
|
3225
|
-
.limit(1);
|
|
3226
|
-
return rows.length > 0 ? rows[0].name : undefined;
|
|
3227
|
-
}
|
|
3228
|
-
catch {
|
|
3229
|
-
return undefined;
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
|
-
/**
|
|
3233
|
-
* Resolves modelName and dataType for a property by its schemaFileId (e.g. context.id).
|
|
3234
|
-
* Used when machine context lacks these (e.g. just-created property renamed before full context is set).
|
|
3235
|
-
*/
|
|
3236
|
-
async function getPropertyModelNameAndDataType(schemaFileId) {
|
|
3237
|
-
const db = BaseDb.getAppDb();
|
|
3238
|
-
if (!db || !schemaFileId)
|
|
3239
|
-
return undefined;
|
|
3240
|
-
try {
|
|
3241
|
-
const rows = await db
|
|
3242
|
-
.select({
|
|
3243
|
-
dataType: properties.dataType,
|
|
3244
|
-
modelId: properties.modelId,
|
|
3245
|
-
})
|
|
3246
|
-
.from(properties)
|
|
3247
|
-
.where(eq(properties.schemaFileId, schemaFileId))
|
|
3248
|
-
.limit(1);
|
|
3249
|
-
if (rows.length === 0)
|
|
3250
|
-
return undefined;
|
|
3251
|
-
const modelRows = await db
|
|
3252
|
-
.select({ name: models.name })
|
|
3253
|
-
.from(models)
|
|
3254
|
-
.where(eq(models.id, rows[0].modelId))
|
|
3255
|
-
.limit(1);
|
|
3256
|
-
if (modelRows.length === 0)
|
|
3257
|
-
return undefined;
|
|
3258
|
-
return {
|
|
3259
|
-
modelName: modelRows[0].name,
|
|
3260
|
-
dataType: rows[0].dataType ?? '',
|
|
3261
|
-
};
|
|
3262
|
-
}
|
|
3263
|
-
catch {
|
|
3264
|
-
return undefined;
|
|
3265
|
-
}
|
|
3266
|
-
}
|
|
3267
|
-
/**
|
|
3268
|
-
* Saves a property's changes to the database without updating the JSON schema file.
|
|
3269
|
-
* This is used when properties are edited but the schema hasn't been saved as a new version yet.
|
|
3270
|
-
* @param property - The ModelPropertyMachineContext with updated values
|
|
3271
|
-
*/
|
|
3272
|
-
const savePropertyToDb = async (property) => {
|
|
3273
|
-
const db = BaseDb.getAppDb();
|
|
3274
|
-
if (!db) {
|
|
3275
|
-
throw new Error('Database not found');
|
|
3276
|
-
}
|
|
3277
|
-
if (!property.name) {
|
|
3278
|
-
throw new Error('Property name is required');
|
|
3279
|
-
}
|
|
3280
|
-
// Find the model: prefer modelId (reliable), then modelName as name, then modelName as schemaFileId
|
|
3281
|
-
// (context.modelName can be a model schemaFileId when schema uses that as identifier)
|
|
3282
|
-
let modelRecord;
|
|
3283
|
-
if (property.modelId != null && typeof property.modelId === 'number') {
|
|
3284
|
-
const byId = await db
|
|
3285
|
-
.select()
|
|
3286
|
-
.from(models)
|
|
3287
|
-
.where(eq(models.id, property.modelId))
|
|
3288
|
-
.limit(1);
|
|
3289
|
-
modelRecord = byId[0];
|
|
3290
|
-
}
|
|
3291
|
-
if (!modelRecord && property.modelName) {
|
|
3292
|
-
// Model names are only unique per schema: scope by the property's schema when known
|
|
3293
|
-
modelRecord = await resolveModelRecord(property.modelName, { schemaName: property._schemaName }, db);
|
|
3294
|
-
}
|
|
3295
|
-
if (!modelRecord && property.modelName) {
|
|
3296
|
-
const bySchemaFileId = await db
|
|
3297
|
-
.select()
|
|
3298
|
-
.from(models)
|
|
3299
|
-
.where(eq(models.schemaFileId, property.modelName))
|
|
3300
|
-
.limit(1);
|
|
3301
|
-
modelRecord = bySchemaFileId[0];
|
|
3302
|
-
}
|
|
3303
|
-
// Fallback: resolve model from existing property row (by schemaFileId) when modelName is wrong
|
|
3304
|
-
const schemaFileId = property._propertyFileId || (typeof property.id === 'string' ? property.id : undefined);
|
|
3305
|
-
if (!modelRecord && schemaFileId) {
|
|
3306
|
-
const propRows = await db
|
|
3307
|
-
.select({ modelId: properties.modelId })
|
|
3308
|
-
.from(properties)
|
|
3309
|
-
.where(eq(properties.schemaFileId, schemaFileId))
|
|
3310
|
-
.limit(1);
|
|
3311
|
-
if (propRows.length > 0 && propRows[0].modelId) {
|
|
3312
|
-
const byPropModelId = await db
|
|
3313
|
-
.select()
|
|
3314
|
-
.from(models)
|
|
3315
|
-
.where(eq(models.id, propRows[0].modelId))
|
|
3316
|
-
.limit(1);
|
|
3317
|
-
modelRecord = byPropModelId[0];
|
|
3318
|
-
}
|
|
3319
|
-
}
|
|
3320
|
-
if (!modelRecord) {
|
|
3321
|
-
throw new Error(`Model not found in database (modelId=${property.modelId}, modelName=${property.modelName})`);
|
|
3322
|
-
}
|
|
3323
|
-
// Find existing property - try multiple strategies to handle name changes
|
|
3324
|
-
// 1. First try by schemaFileId (most reliable - doesn't change when name changes)
|
|
3325
|
-
let existingProperties = [];
|
|
3326
|
-
logger$3(`[savePropertyToDb] Looking for property ${property.modelName}:${property.name} (schemaFileId: ${schemaFileId}, originalName: ${property._originalValues?.name})`);
|
|
3327
|
-
if (schemaFileId) {
|
|
3328
|
-
existingProperties = await db
|
|
3329
|
-
.select()
|
|
3330
|
-
.from(properties)
|
|
3331
|
-
.where(and(eq(properties.schemaFileId, schemaFileId), eq(properties.modelId, modelRecord.id)))
|
|
3332
|
-
.limit(1);
|
|
3333
|
-
logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by schemaFileId`);
|
|
3334
|
-
}
|
|
3335
|
-
// 2. If not found by schemaFileId, try by original name (if name was changed)
|
|
3336
|
-
if (existingProperties.length === 0 && property._originalValues?.name && property._originalValues.name !== property.name) {
|
|
3337
|
-
logger$3(`[savePropertyToDb] Trying to find by original name: ${property._originalValues.name}`);
|
|
3338
|
-
existingProperties = await db
|
|
3339
|
-
.select()
|
|
3340
|
-
.from(properties)
|
|
3341
|
-
.where(and(eq(properties.name, property._originalValues.name), eq(properties.modelId, modelRecord.id)))
|
|
3342
|
-
.limit(1);
|
|
3343
|
-
logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by original name`);
|
|
3344
|
-
}
|
|
3345
|
-
// 3. Fallback: try by current name
|
|
3346
|
-
if (existingProperties.length === 0) {
|
|
3347
|
-
logger$3(`[savePropertyToDb] Trying to find by current name: ${property.name}`);
|
|
3348
|
-
existingProperties = await db
|
|
3349
|
-
.select()
|
|
3350
|
-
.from(properties)
|
|
3351
|
-
.where(and(eq(properties.name, property.name), eq(properties.modelId, modelRecord.id)))
|
|
3352
|
-
.limit(1);
|
|
3353
|
-
logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by current name`);
|
|
3354
|
-
}
|
|
3355
|
-
// Prepare property data
|
|
3356
|
-
const propertyData = {
|
|
3357
|
-
name: property.name,
|
|
3358
|
-
modelId: modelRecord.id,
|
|
3359
|
-
dataType: normalizeDataType(property.dataType),
|
|
3360
|
-
};
|
|
3361
|
-
// Preserve schemaFileId if we have it
|
|
3362
|
-
if (schemaFileId) {
|
|
3363
|
-
propertyData.schemaFileId = schemaFileId;
|
|
3364
|
-
}
|
|
3365
|
-
// Handle ref property - create ref model if needed
|
|
3366
|
-
if (property.refModelName) {
|
|
3367
|
-
const refModel = await findOrCreateRefModelRecord(db, property.refModelName, modelRecord.id);
|
|
3368
|
-
propertyData.refModelId = refModel.id;
|
|
3369
|
-
}
|
|
3370
|
-
else if (property.refModelId) {
|
|
3371
|
-
propertyData.refModelId = property.refModelId;
|
|
3372
|
-
}
|
|
3373
|
-
else {
|
|
3374
|
-
// If it's not a Relation type, ensure refModelId is null
|
|
3375
|
-
propertyData.refModelId = null;
|
|
3376
|
-
}
|
|
3377
|
-
if (property.refValueType) {
|
|
3378
|
-
propertyData.refValueType = normalizeDataType(property.refValueType);
|
|
3379
|
-
}
|
|
3380
|
-
else {
|
|
3381
|
-
// If refValueType is not set, ensure it's null
|
|
3382
|
-
propertyData.refValueType = null;
|
|
3383
|
-
}
|
|
3384
|
-
propertyData.required = property.required ?? false;
|
|
3385
|
-
// Only when the context has storage settings: a context built without them (e.g. loaded before
|
|
3386
|
-
// they were stored) must not clear the row's.
|
|
3387
|
-
const storage = propertyStorageColumns(property);
|
|
3388
|
-
if (hasStorageSettings(storage)) {
|
|
3389
|
-
Object.assign(propertyData, storage);
|
|
3390
|
-
}
|
|
3391
|
-
if (existingProperties.length > 0) {
|
|
3392
|
-
// Property exists, update it with new values (including new name)
|
|
3393
|
-
// Set isEdited = true when property is edited
|
|
3394
|
-
const existingProperty = existingProperties[0];
|
|
3395
|
-
await db
|
|
3396
|
-
.update(properties)
|
|
3397
|
-
.set({
|
|
3398
|
-
...propertyData,
|
|
3399
|
-
isEdited: true, // Mark as edited when saving changes
|
|
3400
|
-
})
|
|
3401
|
-
.where(eq(properties.id, existingProperty.id));
|
|
3402
|
-
logger$3(`Updated property ${property.modelName}:${property._originalValues?.name || 'unknown'} -> ${property.name} in database`);
|
|
3403
|
-
}
|
|
3404
|
-
else {
|
|
3405
|
-
// Property doesn't exist, create it
|
|
3406
|
-
// Set isEdited = true for runtime-created properties
|
|
3407
|
-
// A conflict means the row already exists (concurrent addModelsToDb or another write): treat as success
|
|
3408
|
-
const inserted = await db
|
|
3409
|
-
.insert(properties)
|
|
3410
|
-
.values({
|
|
3411
|
-
...propertyData,
|
|
3412
|
-
isEdited: true, // Runtime-created properties are edited
|
|
3413
|
-
})
|
|
3414
|
-
.onConflictDoNothing()
|
|
3415
|
-
.returning({ id: properties.id });
|
|
3416
|
-
if (inserted.length > 0) {
|
|
3417
|
-
logger$3(`Created property ${property.modelName}:${property.name} in database`);
|
|
3418
|
-
}
|
|
3419
|
-
else {
|
|
3420
|
-
logger$3(`Property ${property.modelName}:${property.name} already exists (UNIQUE constraint), treating as success`);
|
|
3421
|
-
}
|
|
3422
|
-
}
|
|
3423
|
-
};
|
|
3424
|
-
function parseAddressConfig(value) {
|
|
3425
|
-
if (!value)
|
|
3426
|
-
return null;
|
|
3427
|
-
try {
|
|
3428
|
-
const parsed = JSON.parse(value);
|
|
3429
|
-
if (Array.isArray(parsed)) {
|
|
3430
|
-
return normalizeAddressConfig(parsed);
|
|
3431
|
-
}
|
|
3432
|
-
if (parsed && typeof parsed === 'object' && Array.isArray(parsed.owned)) {
|
|
3433
|
-
return normalizeAddressConfig({
|
|
3434
|
-
owned: parsed.owned,
|
|
3435
|
-
watched: Array.isArray(parsed.watched) ? parsed.watched : [],
|
|
3436
|
-
});
|
|
3437
|
-
}
|
|
3438
|
-
return null;
|
|
3439
|
-
}
|
|
3440
|
-
catch {
|
|
3441
|
-
return null;
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3444
|
-
const getOwnedAddressesFromDb = async () => {
|
|
3445
|
-
const config = await getAddressConfigFromDb();
|
|
3446
|
-
return config?.owned ?? [];
|
|
3447
|
-
};
|
|
3448
|
-
const getWatchedAddressesFromDb = async () => {
|
|
3449
|
-
const config = await getAddressConfigFromDb();
|
|
3450
|
-
return config?.watched ?? [];
|
|
3451
|
-
};
|
|
3452
|
-
/**
|
|
3453
|
-
* Returns persisted owned or watched addresses for list filters.
|
|
3454
|
-
* Does not include `getAdditionalSyncAddresses` (those are extra EAS indexers).
|
|
3455
|
-
*/
|
|
3456
|
-
const getAddressesForItemsFilter = async (addressFilter) => {
|
|
3457
|
-
if (addressFilter === 'owned') {
|
|
3458
|
-
return getOwnedAddressesFromDb();
|
|
3459
|
-
}
|
|
3460
|
-
return getWatchedAddressesFromDb();
|
|
3461
|
-
};
|
|
3462
|
-
/**
|
|
3463
|
-
* Returns owned + watched addresses. Use for EAS sync and file download.
|
|
3464
|
-
*/
|
|
3465
|
-
const getAllAddressesFromDb = async () => {
|
|
3466
|
-
const config = await getAddressConfigFromDb();
|
|
3467
|
-
if (!config)
|
|
3468
|
-
return [];
|
|
3469
|
-
return [...config.owned, ...config.watched];
|
|
3470
|
-
};
|
|
3471
|
-
async function getAddressConfigFromDb() {
|
|
3472
|
-
const appDb = BaseDb.getAppDb();
|
|
3473
|
-
if (!appDb) {
|
|
3474
|
-
return new Promise((resolve) => {
|
|
3475
|
-
setTimeout(async () => {
|
|
3476
|
-
resolve(await getAddressConfigFromDb());
|
|
3477
|
-
}, 500);
|
|
3478
|
-
});
|
|
3479
|
-
}
|
|
3480
|
-
const appStatesRecords = await appDb
|
|
3481
|
-
.select()
|
|
3482
|
-
.from(appState)
|
|
3483
|
-
.where(eq(appState.key, 'addresses'))
|
|
3484
|
-
.limit(1);
|
|
3485
|
-
if (!appStatesRecords || appStatesRecords.length === 0) {
|
|
3486
|
-
return null;
|
|
3487
|
-
}
|
|
3488
|
-
const addressArrayString = appStatesRecords[0].value;
|
|
3489
|
-
return parseAddressConfig(addressArrayString);
|
|
3490
|
-
}
|
|
3491
|
-
/**
|
|
3492
|
-
* Write model to database and create model_schemas join entry
|
|
3493
|
-
* @param modelFileId - The model file ID (schema_file_id)
|
|
3494
|
-
* @param data - Model data including modelName, schemaId, and optional properties
|
|
3495
|
-
*/
|
|
3496
|
-
async function writeModelToDb(modelFileId, data) {
|
|
3497
|
-
const db = BaseDb.getAppDb();
|
|
3498
|
-
if (!db)
|
|
3499
|
-
throw new Error('Database not available');
|
|
3500
|
-
// Find or create model record
|
|
3501
|
-
const modelRecords = await db
|
|
3502
|
-
.select()
|
|
3503
|
-
.from(models)
|
|
3504
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
3505
|
-
.limit(1);
|
|
3506
|
-
let modelId;
|
|
3507
|
-
if (modelRecords.length === 0) {
|
|
3508
|
-
// Create new model record
|
|
3509
|
-
// Check again right before insert to avoid race conditions
|
|
3510
|
-
const doubleCheck = await db
|
|
3511
|
-
.select()
|
|
3512
|
-
.from(models)
|
|
3513
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
3514
|
-
.limit(1);
|
|
3515
|
-
if (doubleCheck.length > 0) {
|
|
3516
|
-
// Another process created it, use existing
|
|
3517
|
-
modelId = doubleCheck[0].id;
|
|
3518
|
-
logger$3(`Model with schemaFileId "${modelFileId}" was created by another process, using existing record (id: ${modelId})`);
|
|
3519
|
-
}
|
|
3520
|
-
else {
|
|
3521
|
-
// DO NOTHING on conflict: addModelsToDb writes the same row during schema import, and a failed
|
|
3522
|
-
// insert is logged by sqlite-wasm even when caught.
|
|
3523
|
-
const newModel = await db
|
|
3524
|
-
.insert(models)
|
|
3525
|
-
.values({
|
|
3526
|
-
name: data.modelName,
|
|
3527
|
-
schemaFileId: modelFileId,
|
|
3528
|
-
isEdited: true, // Runtime-created models are edited
|
|
3529
|
-
})
|
|
3530
|
-
.onConflictDoNothing()
|
|
3531
|
-
.returning();
|
|
3532
|
-
if (newModel.length > 0) {
|
|
3533
|
-
modelId = newModel[0].id;
|
|
3534
|
-
}
|
|
3535
|
-
else {
|
|
3536
|
-
const existing = await db
|
|
3537
|
-
.select()
|
|
3538
|
-
.from(models)
|
|
3539
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
3540
|
-
.limit(1);
|
|
3541
|
-
if (existing.length === 0) {
|
|
3542
|
-
throw new Error(`Failed to create or find model "${data.modelName}" with schemaFileId "${modelFileId}"`);
|
|
3543
|
-
}
|
|
3544
|
-
modelId = existing[0].id;
|
|
3545
|
-
logger$3(`Found existing model with schemaFileId "${modelFileId}" (id: ${modelId}) after insert conflict`);
|
|
3546
|
-
}
|
|
3547
|
-
}
|
|
3548
|
-
}
|
|
3549
|
-
else {
|
|
3550
|
-
// Update existing model record
|
|
3551
|
-
modelId = modelRecords[0].id;
|
|
3552
|
-
const updates = {};
|
|
3553
|
-
if (data.modelName !== modelRecords[0].name) {
|
|
3554
|
-
updates.name = data.modelName;
|
|
3555
|
-
}
|
|
3556
|
-
if (Object.keys(updates).length > 0) {
|
|
3557
|
-
await db
|
|
3558
|
-
.update(models)
|
|
3559
|
-
.set(updates)
|
|
3560
|
-
.where(eq(models.id, modelId));
|
|
3561
|
-
}
|
|
3562
|
-
}
|
|
3563
|
-
// Validate that modelId and schemaId are valid before creating join entry
|
|
3564
|
-
if (!modelId || !Number.isInteger(modelId) || modelId <= 0) {
|
|
3565
|
-
throw new Error(`Invalid modelId: ${modelId}. Model record must be created successfully before creating join entry.`);
|
|
3566
|
-
}
|
|
3567
|
-
if (!data.schemaId || !Number.isInteger(data.schemaId) || data.schemaId <= 0) {
|
|
3568
|
-
throw new Error(`Invalid schemaId: ${data.schemaId}. Schema record must exist before creating join entry.`);
|
|
3569
|
-
}
|
|
3570
|
-
// Create model_schemas join entry
|
|
3571
|
-
// CRITICAL: This must happen AFTER the model record is fully saved and committed
|
|
3572
|
-
// The modelId comes from .returning() which ensures the record is committed
|
|
3573
|
-
const existingJoin = await db
|
|
3574
|
-
.select()
|
|
3575
|
-
.from(modelSchemas)
|
|
3576
|
-
.where(and(eq(modelSchemas.modelId, modelId), eq(modelSchemas.schemaId, data.schemaId)))
|
|
3577
|
-
.limit(1);
|
|
3578
|
-
if (existingJoin.length === 0) {
|
|
3579
|
-
// Only provide modelId and schemaId - id is auto-increment and should not be included
|
|
3580
|
-
// Don't use type cast - let Drizzle infer the correct type without id
|
|
3581
|
-
// Verify both modelId and schemaId exist before inserting join record
|
|
3582
|
-
try {
|
|
3583
|
-
// Verify modelId exists
|
|
3584
|
-
const modelCheck = await db
|
|
3585
|
-
.select({ id: models.id })
|
|
3586
|
-
.from(models)
|
|
3587
|
-
.where(eq(models.id, modelId))
|
|
3588
|
-
.limit(1);
|
|
3589
|
-
if (modelCheck.length === 0) {
|
|
3590
|
-
throw new Error(`Model with id ${modelId} does not exist in database. Cannot create join record.`);
|
|
3591
|
-
}
|
|
3592
|
-
// Verify schemaId exists and get name/fileId for invalidation broadcast
|
|
3593
|
-
const schemaSchemaMod = await Promise.resolve().then(function () { return SchemaSchema; });
|
|
3594
|
-
const { schemas: schemasTable } = schemaSchemaMod;
|
|
3595
|
-
const schemaCheck = await db
|
|
3596
|
-
.select({
|
|
3597
|
-
id: schemasTable.id,
|
|
3598
|
-
name: schemasTable.name,
|
|
3599
|
-
schemaFileId: schemasTable.schemaFileId,
|
|
3600
|
-
})
|
|
3601
|
-
.from(schemasTable)
|
|
3602
|
-
.where(eq(schemasTable.id, data.schemaId))
|
|
3603
|
-
.limit(1);
|
|
3604
|
-
if (schemaCheck.length === 0) {
|
|
3605
|
-
throw new Error(`Schema with id ${data.schemaId} does not exist in database. Cannot create join record.`);
|
|
3606
|
-
}
|
|
3607
|
-
logger$3(`Creating join record: modelId=${modelId}, schemaId=${data.schemaId} (both verified to exist)`);
|
|
3608
|
-
await linkModelToSchema(db, modelId, data.schemaId);
|
|
3609
|
-
// Notify React useModels so it can invalidate; live query over join often doesn't re-run when model_schemas is inserted.
|
|
3610
|
-
// Yield so the insert is visible to the refetch that will run when the broadcast is received.
|
|
3611
|
-
if (typeof BroadcastChannel !== 'undefined') {
|
|
3612
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
3613
|
-
const row = schemaCheck[0];
|
|
3614
|
-
try {
|
|
3615
|
-
postBroadcast('seed-models-invalidate', {
|
|
3616
|
-
schemaName: row.name ?? undefined,
|
|
3617
|
-
schemaFileId: row.schemaFileId ?? undefined,
|
|
3618
|
-
});
|
|
3619
|
-
}
|
|
3620
|
-
catch (_) { }
|
|
3621
|
-
}
|
|
3622
|
-
logger$3(`Successfully created join record for model ${data.modelName} (id: ${modelId}) to schema (id: ${data.schemaId})`);
|
|
3623
|
-
}
|
|
3624
|
-
catch (error) {
|
|
3625
|
-
if (error?.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
|
|
3626
|
-
logger$3(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}"`);
|
|
3627
|
-
logger$3(`modelId: ${modelId}, schemaId: ${data.schemaId}`);
|
|
3628
|
-
logger$3(`Error details:`, error);
|
|
3629
|
-
// Re-throw with more context
|
|
3630
|
-
throw new Error(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}" (modelId: ${modelId}, schemaId: ${data.schemaId}). ${error.message}`);
|
|
3631
|
-
}
|
|
3632
|
-
throw error;
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
// Write properties if provided
|
|
3636
|
-
if (data.properties) {
|
|
3637
|
-
for (const [propName, propData] of Object.entries(data.properties)) {
|
|
3638
|
-
// Check if property already exists in database
|
|
3639
|
-
const existingProps = await db
|
|
3640
|
-
.select()
|
|
3641
|
-
.from(properties)
|
|
3642
|
-
.where(and(eq(properties.name, propName), eq(properties.modelId, modelId)))
|
|
3643
|
-
.limit(1);
|
|
3644
|
-
let propertyFileId;
|
|
3645
|
-
if (existingProps.length > 0 && existingProps[0].schemaFileId) {
|
|
3646
|
-
// Use existing property's schemaFileId
|
|
3647
|
-
propertyFileId = existingProps[0].schemaFileId;
|
|
3648
|
-
logger$3(`Using existing propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
|
|
3649
|
-
}
|
|
3650
|
-
else if (propData.id) {
|
|
3651
|
-
// Use provided ID
|
|
3652
|
-
propertyFileId = propData.id;
|
|
3653
|
-
}
|
|
3654
|
-
else {
|
|
3655
|
-
// Generate random propertyFileId
|
|
3656
|
-
// IDs should be generated in the import process before calling writeModelToDb
|
|
3657
|
-
const { generateId } = await Promise.resolve().then(function () { return generateId$1; });
|
|
3658
|
-
propertyFileId = generateId();
|
|
3659
|
-
logger$3(`Generated propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
|
|
3660
|
-
}
|
|
3661
|
-
await writePropertyToDb(propertyFileId, {
|
|
3662
|
-
modelId,
|
|
3663
|
-
name: propName,
|
|
3664
|
-
...propData,
|
|
3665
|
-
});
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
logger$3(`Wrote model ${data.modelName} (${modelFileId}) to database`);
|
|
3669
|
-
}
|
|
3670
|
-
/**
|
|
3671
|
-
* Write property to database
|
|
3672
|
-
* @param propertyFileId - The property file ID (schema_file_id)
|
|
3673
|
-
* @param data - Property data including modelId, name, dataType, and other property fields
|
|
3674
|
-
* @param isEdited - Optional flag indicating if property has been edited locally. Left unset, an update
|
|
3675
|
-
* keeps the row's flag (a load or creation write must not clear an unsaved edit) and an insert stores false.
|
|
3676
|
-
*/
|
|
3677
|
-
async function writePropertyToDb(propertyFileId, data, isEdited) {
|
|
3678
|
-
const db = BaseDb.getAppDb();
|
|
3679
|
-
if (!db)
|
|
3680
|
-
throw new Error('Database not available');
|
|
3681
|
-
// Find existing property by schemaFileId first (preferred for uniqueness)
|
|
3682
|
-
let existingProperties = await db
|
|
3683
|
-
.select()
|
|
3684
|
-
.from(properties)
|
|
3685
|
-
.where(eq(properties.schemaFileId, propertyFileId))
|
|
3686
|
-
.limit(1);
|
|
3687
|
-
// Fallback: find by modelId and name if not found by schemaFileId
|
|
3688
|
-
if (existingProperties.length === 0) {
|
|
3689
|
-
existingProperties = await db
|
|
3690
|
-
.select()
|
|
3691
|
-
.from(properties)
|
|
3692
|
-
.where(and(eq(properties.name, data.name), eq(properties.modelId, data.modelId)))
|
|
3693
|
-
.limit(1);
|
|
3694
|
-
}
|
|
3695
|
-
// Prepare property data
|
|
3696
|
-
const propertyData = {
|
|
3697
|
-
name: data.name,
|
|
3698
|
-
modelId: data.modelId,
|
|
3699
|
-
dataType: normalizeDataType(data.dataType),
|
|
3700
|
-
schemaFileId: propertyFileId,
|
|
3701
|
-
...(isEdited !== undefined && { isEdited }),
|
|
3702
|
-
};
|
|
3703
|
-
// Handle ref property - create ref model if needed
|
|
3704
|
-
// Check refModelName first, then ref (for backwards compatibility with schema files)
|
|
3705
|
-
const refModelName = data.refModelName || data.ref;
|
|
3706
|
-
if (refModelName) {
|
|
3707
|
-
const refModel = await findOrCreateRefModelRecord(db, refModelName, data.modelId);
|
|
3708
|
-
propertyData.refModelId = refModel.id;
|
|
3709
|
-
}
|
|
3710
|
-
else if (data.refModelId) {
|
|
3711
|
-
propertyData.refModelId = data.refModelId;
|
|
3712
|
-
}
|
|
3713
|
-
else {
|
|
3714
|
-
// If it's not a Relation type, ensure refModelId is null
|
|
3715
|
-
propertyData.refModelId = null;
|
|
3716
|
-
}
|
|
3717
|
-
if (data.refValueType) {
|
|
3718
|
-
propertyData.refValueType = normalizeDataType(data.refValueType);
|
|
3719
|
-
}
|
|
3720
|
-
else {
|
|
3721
|
-
// If refValueType is not set, ensure it's null
|
|
3722
|
-
propertyData.refValueType = null;
|
|
3723
|
-
}
|
|
3724
|
-
// Only overwrite `required` when the caller knows it. The creation write's payload can lack it, and
|
|
3725
|
-
// defaulting to false here cleared the flag the schema import set (required relations became optional).
|
|
3726
|
-
if (data.required !== undefined) {
|
|
3727
|
-
propertyData.required = data.required;
|
|
3728
|
-
}
|
|
3729
|
-
// Storage settings, like `required`, only when the caller has them: the creation write's payload
|
|
3730
|
-
// can lack them, and writing nulls would clear what the schema import stored.
|
|
3731
|
-
const storage = propertyStorageColumns(data);
|
|
3732
|
-
if (hasStorageSettings(storage)) {
|
|
3733
|
-
Object.assign(propertyData, storage);
|
|
3734
|
-
}
|
|
3735
|
-
if (existingProperties.length > 0) {
|
|
3736
|
-
// Property exists, update it with new values.
|
|
3737
|
-
// Use existing row id for WHERE (not name+modelId) so renames work: the row
|
|
3738
|
-
// may have the old name but we're updating to the new one.
|
|
3739
|
-
const existing = existingProperties[0];
|
|
3740
|
-
await db
|
|
3741
|
-
.update(properties)
|
|
3742
|
-
.set(propertyData)
|
|
3743
|
-
.where(eq(properties.id, existing.id));
|
|
3744
|
-
logger$3(`Updated property ${data.name} (${propertyFileId}) in database`);
|
|
3745
|
-
}
|
|
3746
|
-
else {
|
|
3747
|
-
// Property doesn't exist, create it
|
|
3748
|
-
// Double-check before insert to avoid race conditions
|
|
3749
|
-
const doubleCheck = await db
|
|
3750
|
-
.select()
|
|
3751
|
-
.from(properties)
|
|
3752
|
-
.where(eq(properties.schemaFileId, propertyFileId))
|
|
3753
|
-
.limit(1);
|
|
3754
|
-
if (doubleCheck.length > 0) {
|
|
3755
|
-
// Another process created it, update it instead
|
|
3756
|
-
await db
|
|
3757
|
-
.update(properties)
|
|
3758
|
-
.set(propertyData)
|
|
3759
|
-
.where(eq(properties.id, doubleCheck[0].id));
|
|
3760
|
-
logger$3(`Property with schemaFileId "${propertyFileId}" was created by another process, updated existing record`);
|
|
3761
|
-
}
|
|
3762
|
-
else {
|
|
3763
|
-
// DO NOTHING on conflict: addModelsToDb writes the same row during schema import, and a failed
|
|
3764
|
-
// insert is logged by sqlite-wasm even when caught.
|
|
3765
|
-
const inserted = await db
|
|
3766
|
-
.insert(properties)
|
|
3767
|
-
.values({ required: false, isEdited: false, ...propertyData })
|
|
3768
|
-
.onConflictDoNothing()
|
|
3769
|
-
.returning({ id: properties.id });
|
|
3770
|
-
if (inserted.length > 0) {
|
|
3771
|
-
logger$3(`Created property ${data.name} (${propertyFileId}) in database`);
|
|
3772
|
-
}
|
|
3773
|
-
else {
|
|
3774
|
-
const existing = await db
|
|
3775
|
-
.select()
|
|
3776
|
-
.from(properties)
|
|
3777
|
-
.where(eq(properties.schemaFileId, propertyFileId))
|
|
3778
|
-
.limit(1);
|
|
3779
|
-
if (existing.length === 0) {
|
|
3780
|
-
throw new Error(`Failed to create or find property "${data.name}" with schemaFileId "${propertyFileId}"`);
|
|
3781
|
-
}
|
|
3782
|
-
await db
|
|
3783
|
-
.update(properties)
|
|
3784
|
-
.set(propertyData)
|
|
3785
|
-
.where(eq(properties.id, existing[0].id));
|
|
3786
|
-
logger$3(`Found existing property with schemaFileId "${propertyFileId}" (id: ${existing[0].id}) after insert conflict, updated it`);
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
}
|
|
3790
|
-
}
|
|
3791
|
-
/**
|
|
3792
|
-
* Get model database ID from model name or modelFileId
|
|
3793
|
-
* @param modelNameOrFileId - Model name (string) or modelFileId (string)
|
|
3794
|
-
* @param schemaNameOrId - Optional schema name or ID to narrow search
|
|
3795
|
-
* @returns Model database ID
|
|
3796
|
-
* @throws Error if model not found
|
|
3797
|
-
*/
|
|
3798
|
-
async function getModelId(modelNameOrFileId, schemaNameOrId) {
|
|
3799
|
-
const db = BaseDb.getAppDb();
|
|
3800
|
-
if (!db) {
|
|
3801
|
-
throw new Error('Database not available');
|
|
3802
|
-
}
|
|
3803
|
-
const modelSchemaMod = await Promise.resolve().then(function () { return ModelSchema; });
|
|
3804
|
-
const { models: modelsTable } = modelSchemaMod;
|
|
3805
|
-
const drizzleMod = await import('drizzle-orm');
|
|
3806
|
-
const { eq, and } = drizzleMod;
|
|
3807
|
-
// Try to find by modelFileId first (more reliable)
|
|
3808
|
-
let records = await db
|
|
3809
|
-
.select()
|
|
3810
|
-
.from(modelsTable)
|
|
3811
|
-
.where(eq(modelsTable.schemaFileId, modelNameOrFileId))
|
|
3812
|
-
.limit(1);
|
|
3813
|
-
// If not found by modelFileId, try by name
|
|
3814
|
-
if (records.length === 0) {
|
|
3815
|
-
// If schema is provided, narrow the search
|
|
3816
|
-
if (schemaNameOrId !== undefined) {
|
|
3817
|
-
const modelSchemaSchemaMod = await Promise.resolve().then(function () { return ModelSchemaSchema; });
|
|
3818
|
-
const { modelSchemas } = modelSchemaSchemaMod;
|
|
3819
|
-
const schemaSchemaMod = await Promise.resolve().then(function () { return SchemaSchema; });
|
|
3820
|
-
const { schemas: schemasTable } = schemaSchemaMod;
|
|
3821
|
-
if (typeof schemaNameOrId === 'number') {
|
|
3822
|
-
// schemaNameOrId is schemaId
|
|
3823
|
-
records = await db
|
|
3824
|
-
.select({ id: modelsTable.id })
|
|
3825
|
-
.from(modelsTable)
|
|
3826
|
-
.innerJoin(modelSchemas, eq(modelsTable.id, modelSchemas.modelId))
|
|
3827
|
-
.where(and(eq(modelsTable.name, modelNameOrFileId), eq(modelSchemas.schemaId, schemaNameOrId)))
|
|
3828
|
-
.limit(1);
|
|
3829
|
-
}
|
|
3830
|
-
else {
|
|
3831
|
-
// schemaNameOrId is schemaName
|
|
3832
|
-
records = await db
|
|
3833
|
-
.select({ id: modelsTable.id })
|
|
3834
|
-
.from(modelsTable)
|
|
3835
|
-
.innerJoin(modelSchemas, eq(modelsTable.id, modelSchemas.modelId))
|
|
3836
|
-
.innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
|
|
3837
|
-
.where(and(eq(modelsTable.name, modelNameOrFileId), eq(schemasTable.name, schemaNameOrId)))
|
|
3838
|
-
.limit(1);
|
|
3839
|
-
}
|
|
3840
|
-
}
|
|
3841
|
-
else {
|
|
3842
|
-
// No schema filter: name-only (newest row, warns when the name is ambiguous)
|
|
3843
|
-
const byName = await resolveModelRecord(modelNameOrFileId, {}, db);
|
|
3844
|
-
records = byName ? [byName] : [];
|
|
3845
|
-
}
|
|
3846
|
-
}
|
|
3847
|
-
if (records.length === 0) {
|
|
3848
|
-
const schemaInfo = schemaNameOrId ? ` in schema "${schemaNameOrId}"` : '';
|
|
3849
|
-
throw new Error(`Model "${modelNameOrFileId}"${schemaInfo} not found in database`);
|
|
3850
|
-
}
|
|
3851
|
-
return records[0].id;
|
|
3852
|
-
}
|
|
3853
|
-
/**
|
|
3854
|
-
* Get model database ID from modelFileId
|
|
3855
|
-
* @param modelFileId - The model file ID (schema_file_id)
|
|
3856
|
-
* @returns Model database ID
|
|
3857
|
-
* @throws Error if model not found
|
|
3858
|
-
*/
|
|
3859
|
-
async function getModelIdByFileId(modelFileId) {
|
|
3860
|
-
const db = BaseDb.getAppDb();
|
|
3861
|
-
if (!db) {
|
|
3862
|
-
throw new Error('Database not available');
|
|
3863
|
-
}
|
|
3864
|
-
const modelSchemaMod = await Promise.resolve().then(function () { return ModelSchema; });
|
|
3865
|
-
const { models: modelsTable } = modelSchemaMod;
|
|
3866
|
-
const drizzleMod = await import('drizzle-orm');
|
|
3867
|
-
const { eq } = drizzleMod;
|
|
3868
|
-
const records = await db
|
|
3869
|
-
.select()
|
|
3870
|
-
.from(modelsTable)
|
|
3871
|
-
.where(eq(modelsTable.schemaFileId, modelFileId))
|
|
3872
|
-
.limit(1);
|
|
3873
|
-
if (records.length === 0) {
|
|
3874
|
-
throw new Error(`Model with file ID "${modelFileId}" not found in database`);
|
|
3875
|
-
}
|
|
3876
|
-
return records[0].id;
|
|
3877
|
-
}
|
|
3878
|
-
|
|
3879
|
-
var db = /*#__PURE__*/Object.freeze({
|
|
3880
|
-
__proto__: null,
|
|
3881
|
-
addModelsToDb: addModelsToDb,
|
|
3882
|
-
addSchemaToDb: addSchemaToDb,
|
|
3883
|
-
getAddressesForItemsFilter: getAddressesForItemsFilter,
|
|
3884
|
-
getAllAddressesFromDb: getAllAddressesFromDb,
|
|
3885
|
-
getItemStoragePropertiesForModel: getItemStoragePropertiesForModel,
|
|
3886
|
-
getModelId: getModelId,
|
|
3887
|
-
getModelIdByFileId: getModelIdByFileId,
|
|
3888
|
-
getModelNameByModelId: getModelNameByModelId,
|
|
3889
|
-
getOwnedAddressesFromDb: getOwnedAddressesFromDb,
|
|
3890
|
-
getPropertyIdForModelAndName: getPropertyIdForModelAndName,
|
|
3891
|
-
getPropertyIdForSchemaFileId: getPropertyIdForSchemaFileId,
|
|
3892
|
-
getPropertyModelNameAndDataType: getPropertyModelNameAndDataType,
|
|
3893
|
-
getWatchedAddressesFromDb: getWatchedAddressesFromDb,
|
|
3894
|
-
loadModelsFromDbForSchema: loadModelsFromDbForSchema,
|
|
3895
|
-
mergeDbModelsIntoSchemaModels: mergeDbModelsIntoSchemaModels,
|
|
3896
|
-
migrateMetadataForPropertyRename: migrateMetadataForPropertyRename,
|
|
3897
|
-
moveMetadataToRenamedProperty: moveMetadataToRenamedProperty,
|
|
3898
|
-
propertyStorageColumns: propertyStorageColumns,
|
|
3899
|
-
renameModelInDb: renameModelInDb,
|
|
3900
|
-
savePropertyToDb: savePropertyToDb,
|
|
3901
|
-
writeModelToDb: writeModelToDb,
|
|
3902
|
-
writePropertyToDb: writePropertyToDb
|
|
3903
|
-
});
|
|
3904
|
-
|
|
3905
|
-
const isNode = () => {
|
|
3906
|
-
return (typeof process !== 'undefined' &&
|
|
3907
|
-
process.versions != null &&
|
|
3908
|
-
process.versions.node != null);
|
|
3909
|
-
};
|
|
3910
|
-
const isBrowser = () => {
|
|
3911
|
-
return !isElectron() && typeof document !== 'undefined' && typeof window !== 'undefined';
|
|
3912
|
-
};
|
|
3913
|
-
const isElectron = () => {
|
|
3914
|
-
return typeof process !== 'undefined' && process.versions != null && process.versions.electron != null;
|
|
3915
|
-
};
|
|
3916
|
-
/**
|
|
3917
|
-
* Detects if code is running in an Electron renderer process.
|
|
3918
|
-
*
|
|
3919
|
-
* Uses multiple detection methods for robustness:
|
|
3920
|
-
* 1. Checks process.type === 'renderer' (most reliable)
|
|
3921
|
-
* 2. Falls back to checking if Electron is present and window/document exist
|
|
3922
|
-
*
|
|
3923
|
-
* @returns true if running in Electron renderer process, false otherwise
|
|
3924
|
-
*/
|
|
3925
|
-
const isElectronRenderer = () => {
|
|
3926
|
-
// Method 1: Check process.type (most reliable - official Electron API)
|
|
3927
|
-
if (typeof process !== 'undefined' &&
|
|
3928
|
-
process.type === 'renderer') {
|
|
3929
|
-
return true;
|
|
3930
|
-
}
|
|
3931
|
-
// Method 2: Fallback - Electron + browser-like environment
|
|
3932
|
-
// Renderer processes have window/document, main process doesn't (in modern Electron)
|
|
3933
|
-
if (isElectron() &&
|
|
3934
|
-
typeof window !== 'undefined' &&
|
|
3935
|
-
typeof document !== 'undefined') {
|
|
3936
|
-
return true;
|
|
3937
|
-
}
|
|
3938
|
-
return false;
|
|
3939
|
-
};
|
|
3940
|
-
/**
|
|
3941
|
-
* True where OPFS-backed Arweave file downloads should run: normal browsers and
|
|
3942
|
-
* Electron renderer. {@link isBrowser} is false for every Electron build (including renderer).
|
|
3943
|
-
*/
|
|
3944
|
-
const supportsOpfsFileDownloads = () => {
|
|
3945
|
-
return isBrowser() || isElectronRenderer();
|
|
3946
|
-
};
|
|
3947
|
-
/**
|
|
3948
|
-
* NODE_ENV is 'test' or IS_SEED_DEV is set. Bundlers compile `process.env.NODE_ENV` in, but
|
|
3949
|
-
* not IS_SEED_DEV, so that is only read where a `process` global exists: browsers have none.
|
|
3950
|
-
*/
|
|
3951
|
-
const isTestOrSeedDev = () => process.env.NODE_ENV === 'test' || (typeof process !== 'undefined' && !!process.env?.IS_SEED_DEV);
|
|
3952
|
-
|
|
3953
|
-
/**
|
|
3954
|
-
* Tag every Seed upload carries with the hex digest of its data. Producers: the SDK's
|
|
3955
|
-
* `getPublishUploadData` / `getPublishUploads` (and through them both the L1 transaction path and
|
|
3956
|
-
* the bundler / DataItem path in `@seedprotocol/publish`). Consumer: the files download
|
|
3957
|
-
* (`events/files/download.ts` and the download worker), which verifies downloaded bytes against it.
|
|
3958
|
-
*/
|
|
3959
|
-
const CONTENT_SHA256_TAG = 'Content-SHA-256';
|
|
3960
|
-
const CONTENT_TYPE_TAG = 'Content-Type';
|
|
3961
|
-
/**
|
|
3962
|
-
* Lowercase hex SHA-256 of `data`: the value of the {@link CONTENT_SHA256_TAG} tag.
|
|
3963
|
-
*
|
|
3964
|
-
* Uses @noble/hashes rather than WebCrypto so it also works outside secure contexts (WebCrypto's
|
|
3965
|
-
* `crypto.subtle` is missing on plain-http origins); the digest is the same.
|
|
3966
|
-
*
|
|
3967
|
-
* In SDK 0.7.2 and earlier the L1 transaction path (`getPublishUploads`) wrote SHA3-256 under this tag name
|
|
3968
|
-
* while the bundler path wrote SHA-256. Uploads from that path still carry SHA3-256 digests; use
|
|
3969
|
-
* {@link contentSha256TagMatches} to check a tag value against bytes.
|
|
3970
|
-
*/
|
|
3971
|
-
const getContentSha256 = (data) => bytesToHex(sha256(data));
|
|
3972
|
-
/**
|
|
3973
|
-
* True when `tagValue` (a {@link CONTENT_SHA256_TAG} value read from Arweave) is the digest of
|
|
3974
|
-
* `data`. Accepts SHA-256 and, for uploads made by SDK 0.7.2 and earlier through the L1 transaction path,
|
|
3975
|
-
* SHA3-256.
|
|
3976
|
-
*/
|
|
3977
|
-
const contentSha256TagMatches = (data, tagValue) => {
|
|
3978
|
-
const expected = tagValue.trim().toLowerCase();
|
|
3979
|
-
if (!expected)
|
|
3980
|
-
return false;
|
|
3981
|
-
return getContentSha256(data) === expected || bytesToHex(sha3_256(data)) === expected;
|
|
3982
|
-
};
|
|
3983
|
-
/**
|
|
3984
|
-
* Tags for one upload, in the order every upload uses: content hash, content type, then any
|
|
3985
|
-
* configured extra tags (e.g. App-Name from `arweaveUploadTags`).
|
|
3986
|
-
*/
|
|
3987
|
-
const buildArweaveContentTags = (contentHash, contentType, extra) => {
|
|
3988
|
-
const tags = [];
|
|
3989
|
-
if (contentHash)
|
|
3990
|
-
tags.push({ name: CONTENT_SHA256_TAG, value: contentHash });
|
|
3991
|
-
if (contentType)
|
|
3992
|
-
tags.push({ name: CONTENT_TYPE_TAG, value: contentType });
|
|
3993
|
-
if (extra?.length)
|
|
3994
|
-
tags.push(...extra);
|
|
3995
|
-
return tags;
|
|
3996
|
-
};
|
|
3997
|
-
|
|
3998
|
-
/** Subdirectory of the files root that holds a storage seed's file, by its data type. */
|
|
3999
|
-
const getStorageSubdirForDataType = (dataType) => {
|
|
4000
|
-
switch (normalizeDataType(dataType ?? undefined)) {
|
|
4001
|
-
case 'Image':
|
|
4002
|
-
return 'images';
|
|
4003
|
-
case 'Html':
|
|
4004
|
-
return 'html';
|
|
4005
|
-
case 'Json':
|
|
4006
|
-
return 'json';
|
|
4007
|
-
case 'File':
|
|
4008
|
-
default:
|
|
4009
|
-
// Unknown types are generic files; `images/` is only for what the SDK knows is an image.
|
|
4010
|
-
return 'files';
|
|
4011
|
-
}
|
|
4012
|
-
};
|
|
4013
|
-
/** Absolute directory for a storage seed's file (`images/`, `files/`, `html/` or `json/` under the files root). */
|
|
4014
|
-
const getStorageDirForDataType = (dataType) => BaseFileManager.getFilesPath(getStorageSubdirForDataType(dataType));
|
|
4015
|
-
/**
|
|
4016
|
-
* Subdirectory (relative to the files root, no leading slash) for an ItemStorage property's file:
|
|
4017
|
-
* its model's `storage.path` (`localStorageDir`), else `files`.
|
|
4018
|
-
*/
|
|
4019
|
-
const getItemStorageSubdir = (localStorageDir) => (localStorageDir ?? '').replace(/^\/+/, '').replace(/\/+$/, '') || 'files';
|
|
4020
|
-
/**
|
|
4021
|
-
* File name a local save gives an ItemStorage property: `<seedUid or seedLocalId><extension>`
|
|
4022
|
-
* (see saveItemStorage). Downloaded content is named after its transaction instead
|
|
4023
|
-
* (`<txId><extension>`, see syncDbWithEas), which is what the metadata row's refResolvedValue says.
|
|
4024
|
-
*/
|
|
4025
|
-
const getItemStorageLocalFileName = (seedId, filenameSuffix) => `${seedId}${filenameSuffix ?? ''}`;
|
|
4026
|
-
|
|
4027
|
-
/** Rows per query: well under SQLite's bound-parameter limit. */
|
|
4028
|
-
const CHUNK_SIZE = 400;
|
|
4029
|
-
/**
|
|
4030
|
-
* Where the download worker saves each child of a composite upload, by transaction id then child
|
|
4031
|
-
* (property) name: a path relative to the files root.
|
|
4032
|
-
*
|
|
4033
|
-
* The paths come from the ItemStorage rows sync derives from a `storage_transaction_id`
|
|
4034
|
-
* attestation (see `createMetadataRecordsForStorageTransactionId`): property value = the
|
|
4035
|
-
* transaction id, `refResolvedValue` = `<txId><extension>`, `localStorageDir` = the model's storage
|
|
4036
|
-
* directory. Those rows are what hydration reads, so a child saved here is found as that property's
|
|
4037
|
-
* file. Transactions with no such rows are left out; the worker then falls back to the legacy
|
|
4038
|
-
* html / json layout.
|
|
4039
|
-
*/
|
|
4040
|
-
const getCompositeChildPaths = async (transactionIds) => {
|
|
4041
|
-
const out = {};
|
|
4042
|
-
const appDb = BaseDb.getAppDb();
|
|
4043
|
-
if (!appDb || transactionIds.length === 0)
|
|
4044
|
-
return out;
|
|
4045
|
-
for (let i = 0; i < transactionIds.length; i += CHUNK_SIZE) {
|
|
4046
|
-
const chunk = transactionIds.slice(i, i + CHUNK_SIZE);
|
|
4047
|
-
const rows = await appDb
|
|
4048
|
-
.select({
|
|
4049
|
-
transactionId: metadata.propertyValue,
|
|
4050
|
-
propertyName: metadata.propertyName,
|
|
4051
|
-
refResolvedValue: metadata.refResolvedValue,
|
|
4052
|
-
localStorageDir: metadata.localStorageDir,
|
|
4053
|
-
})
|
|
4054
|
-
.from(metadata)
|
|
4055
|
-
.where(and(isNotNull(metadata.derivedFromUid), isNotNull(metadata.refResolvedValue), inArray(metadata.propertyValue, chunk)));
|
|
4056
|
-
for (const row of rows) {
|
|
4057
|
-
const { transactionId, propertyName, refResolvedValue } = row;
|
|
4058
|
-
if (!transactionId || !propertyName || !refResolvedValue)
|
|
4059
|
-
continue;
|
|
4060
|
-
// A file name, never a path: anything else is not where the SDK would look.
|
|
4061
|
-
if (refResolvedValue.includes('/') || refResolvedValue.includes('\\'))
|
|
4062
|
-
continue;
|
|
4063
|
-
const byName = (out[transactionId] ?? (out[transactionId] = {}));
|
|
4064
|
-
byName[propertyName] ?? (byName[propertyName] = `${getItemStorageSubdir(row.localStorageDir)}/${refResolvedValue}`);
|
|
4065
|
-
}
|
|
4066
|
-
}
|
|
4067
|
-
return out;
|
|
4068
|
-
};
|
|
4069
|
-
|
|
4070
|
-
const logger$2 = debug('seedSdk:files:download');
|
|
4071
|
-
const AUTO_BULK_DOWNLOAD_THROTTLE_MS = 10000;
|
|
4072
|
-
/** Avoid refetch-on-every-call: transient empty indexer responses were overwriting good cache (see trailing throttle ~10s after success). */
|
|
4073
|
-
const GET_FILES_METADATA_STALE_MS = 120000;
|
|
4074
|
-
let bulkDownloadInFlight = null;
|
|
4075
|
-
const lazyDownloadInFlightByTransactionId = new Map();
|
|
4076
|
-
const getAppDbReady = async () => {
|
|
4077
|
-
if (!BaseDb.isAppDbReady()) {
|
|
4078
|
-
// Wait for ClientManager to be ready (DB_INIT state or later)
|
|
4079
|
-
const _mod_0 = await import('./ClientManager-GZfXErDX.js');
|
|
4080
|
-
const _ns_0 = _mod_0.W;
|
|
4081
|
-
const clientManagerMod = _ns_0;
|
|
4082
|
-
const { getClient } = clientManagerMod;
|
|
4083
|
-
const clientManager = getClient();
|
|
4084
|
-
const clientService = clientManager.getService();
|
|
4085
|
-
await waitFor(clientService, (snapshot) => {
|
|
4086
|
-
const state = snapshot.value;
|
|
4087
|
-
return state === 'dbInit' ||
|
|
4088
|
-
state === 'saveConfig' ||
|
|
4089
|
-
state === 'processSchemaFiles' ||
|
|
4090
|
-
state === 'addModelsToStore' ||
|
|
4091
|
-
state === 'addModelsToDb' ||
|
|
4092
|
-
state === 'idle';
|
|
4093
|
-
}, { timeout: 30000 });
|
|
4094
|
-
}
|
|
4095
|
-
return BaseDb.getAppDb();
|
|
4096
|
-
};
|
|
4097
|
-
const getExcludedTransactions = async () => {
|
|
4098
|
-
const appDb = await getAppDbReady();
|
|
4099
|
-
if (!appDb) {
|
|
4100
|
-
return new Set();
|
|
4101
|
-
}
|
|
4102
|
-
const excludedTransactionsQuery = await appDb
|
|
4103
|
-
.select()
|
|
4104
|
-
.from(appState)
|
|
4105
|
-
.where(eq(appState.key, 'excludedTransactions'));
|
|
4106
|
-
const excludedTransactions = new Set();
|
|
4107
|
-
if (excludedTransactionsQuery && excludedTransactionsQuery.length === 1) {
|
|
4108
|
-
const valueString = excludedTransactionsQuery[0].value;
|
|
4109
|
-
if (valueString) {
|
|
4110
|
-
const excludedTransactionsArray = JSON.parse(valueString);
|
|
4111
|
-
for (const txId of excludedTransactionsArray) {
|
|
4112
|
-
excludedTransactions.add(txId);
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
4115
|
-
}
|
|
4116
|
-
return excludedTransactions;
|
|
4117
|
-
};
|
|
4118
|
-
/** Arweave transaction ids are 43 chars in the URL-safe base64 alphabet. */
|
|
4119
|
-
const ARWEAVE_TX_ID_RE$1 = /^[a-z0-9_-]{43}$/i;
|
|
4120
|
-
const normalizeArweaveTxId = (raw) => {
|
|
4121
|
-
if (raw == null || raw === '')
|
|
4122
|
-
return undefined;
|
|
4123
|
-
const t = String(raw).trim();
|
|
4124
|
-
if (ARWEAVE_TX_ID_RE$1.test(t))
|
|
4125
|
-
return t;
|
|
4126
|
-
const m = t.match(/^([a-z0-9_-]{43})(?:\.[^/]+)?$/i);
|
|
4127
|
-
if (m)
|
|
4128
|
-
return m[1];
|
|
4129
|
-
// Values may be full gateway URLs or paths; scan for an embedded tx id. It must be 43 characters
|
|
4130
|
-
// on its own, not the start of a longer run: the first 43 of a bytes32 value (`0x0000…0020`,
|
|
4131
|
-
// an EAS UID) are in the alphabet too.
|
|
4132
|
-
const embedded = t.match(/(?<![a-z0-9_-])[a-z0-9_-]{43}(?![a-z0-9_-])/gi);
|
|
4133
|
-
return embedded?.[0];
|
|
4134
|
-
};
|
|
4135
|
-
const mergeAddressLists = (a, b) => {
|
|
4136
|
-
const seen = new Set();
|
|
4137
|
-
const out = [];
|
|
4138
|
-
for (const addr of [...(a ?? []), ...(b ?? [])]) {
|
|
4139
|
-
if (!addr || !addr.trim())
|
|
4140
|
-
continue;
|
|
4141
|
-
const k = addr.toLowerCase();
|
|
4142
|
-
if (seen.has(k))
|
|
4143
|
-
continue;
|
|
4144
|
-
seen.add(k);
|
|
4145
|
-
out.push(addr);
|
|
4146
|
-
}
|
|
4147
|
-
return out;
|
|
4148
|
-
};
|
|
4149
|
-
const collectTransactionIdsFromLocalMetadata = async () => {
|
|
4150
|
-
const appDb = await getAppDbReady();
|
|
4151
|
-
if (!appDb) {
|
|
4152
|
-
return [];
|
|
4153
|
-
}
|
|
4154
|
-
const nameOr = or(eq(metadata.propertyName, 'storageTransactionId'), eq(metadata.propertyName, 'storage_transaction_id'), eq(metadata.propertyName, 'transactionId'));
|
|
4155
|
-
const propRows = await appDb
|
|
4156
|
-
.select({ propertyValue: metadata.propertyValue })
|
|
4157
|
-
.from(metadata)
|
|
4158
|
-
.where(and(nameOr, isNotNull(metadata.propertyValue)));
|
|
4159
|
-
const refRows = await appDb
|
|
4160
|
-
.select({ refResolvedValue: metadata.refResolvedValue })
|
|
4161
|
-
.from(metadata)
|
|
4162
|
-
.where(isNotNull(metadata.refResolvedValue));
|
|
4163
|
-
const ids = new Set();
|
|
4164
|
-
for (const row of propRows) {
|
|
4165
|
-
const id = normalizeArweaveTxId(row.propertyValue ?? undefined);
|
|
4166
|
-
if (id)
|
|
4167
|
-
ids.add(id);
|
|
4168
|
-
}
|
|
4169
|
-
for (const row of refRows) {
|
|
4170
|
-
const id = normalizeArweaveTxId(row.refResolvedValue ?? undefined);
|
|
4171
|
-
if (id)
|
|
4172
|
-
ids.add(id);
|
|
4173
|
-
}
|
|
4174
|
-
return Array.from(ids);
|
|
4175
|
-
};
|
|
4176
|
-
const parseTransactionIdsFromMetadata = (filesMetadata, excludedTransactions) => {
|
|
4177
|
-
const transactionIds = [];
|
|
4178
|
-
for (const fileMetadata of filesMetadata) {
|
|
4179
|
-
// Validate and parse decodedDataJson
|
|
4180
|
-
if (!fileMetadata.decodedDataJson || fileMetadata.decodedDataJson.trim() === '') {
|
|
4181
|
-
console.warn('[events/files] [download] empty decodedDataJson for fileMetadata: ', fileMetadata.id);
|
|
4182
|
-
continue;
|
|
4183
|
-
}
|
|
4184
|
-
let json;
|
|
4185
|
-
try {
|
|
4186
|
-
json = JSON.parse(fileMetadata.decodedDataJson);
|
|
4187
|
-
}
|
|
4188
|
-
catch (error) {
|
|
4189
|
-
console.warn('[events/files] [download] failed to parse decodedDataJson for fileMetadata: ', fileMetadata.id, error);
|
|
4190
|
-
continue;
|
|
4191
|
-
}
|
|
4192
|
-
if (!Array.isArray(json) || json.length === 0 || !json[0]?.value?.value) {
|
|
4193
|
-
console.warn('[events/files] [download] invalid decodedDataJson structure for fileMetadata: ', fileMetadata.id);
|
|
4194
|
-
continue;
|
|
4195
|
-
}
|
|
4196
|
-
const rawTx = json[0].value.value;
|
|
4197
|
-
if (typeof rawTx !== 'string' || rawTx.trim().length === 0) {
|
|
4198
|
-
continue;
|
|
4199
|
-
}
|
|
4200
|
-
const transactionId = normalizeArweaveTxId(rawTx.trim());
|
|
4201
|
-
if (!transactionId) {
|
|
4202
|
-
continue;
|
|
4203
|
-
}
|
|
4204
|
-
if (excludedTransactions.has(transactionId) || excludedTransactions.has(rawTx.trim())) {
|
|
4205
|
-
continue;
|
|
4206
|
-
}
|
|
4207
|
-
transactionIds.push(transactionId);
|
|
4208
|
-
}
|
|
4209
|
-
return transactionIds;
|
|
4210
|
-
};
|
|
4211
|
-
/** Content-SHA-256 tag values for the transactions that have one, from one batched tag lookup (cached). */
|
|
4212
|
-
const getExpectedContentHashes = async (transactionIds) => {
|
|
4213
|
-
const queryClient = BaseQueryClient.getQueryClient();
|
|
4214
|
-
const tagsById = new Map();
|
|
4215
|
-
const toFetch = [];
|
|
4216
|
-
for (const transactionId of transactionIds) {
|
|
4217
|
-
// Tags never change once a transaction is indexed, so a cached lookup stays good.
|
|
4218
|
-
const cached = queryClient.getQueryData?.(['getTransactionTags', transactionId]);
|
|
4219
|
-
if (cached)
|
|
4220
|
-
tagsById.set(transactionId, cached);
|
|
4221
|
-
else
|
|
4222
|
-
toFetch.push(transactionId);
|
|
4223
|
-
}
|
|
4224
|
-
if (toFetch.length > 0) {
|
|
4225
|
-
try {
|
|
4226
|
-
const fetched = await getTransactionTagsByIds(toFetch, {
|
|
4227
|
-
client: BaseArweaveClient.getArweaveClient(),
|
|
4228
|
-
onError: (error) => logger$2('[download] transaction tags lookup failed', error),
|
|
4229
|
-
});
|
|
4230
|
-
for (const [transactionId, tags] of fetched) {
|
|
4231
|
-
tagsById.set(transactionId, tags);
|
|
4232
|
-
// Seed the per-transaction cache entry (the client interface has no setQueryData).
|
|
4233
|
-
void queryClient
|
|
4234
|
-
.fetchQuery({ queryKey: ['getTransactionTags', transactionId], queryFn: async () => tags })
|
|
4235
|
-
.catch(() => { });
|
|
4236
|
-
}
|
|
4237
|
-
}
|
|
4238
|
-
catch (error) {
|
|
4239
|
-
logger$2('[download] transaction tags lookup failed', error);
|
|
4240
|
-
}
|
|
4241
|
-
}
|
|
4242
|
-
const out = {};
|
|
4243
|
-
for (const [transactionId, tags] of tagsById) {
|
|
4244
|
-
const value = tags.find((tag) => tag.name === CONTENT_SHA256_TAG)?.value?.trim();
|
|
4245
|
-
if (value)
|
|
4246
|
-
out[transactionId] = value;
|
|
4247
|
-
}
|
|
4248
|
-
return out;
|
|
4249
|
-
};
|
|
4250
|
-
const downloadTransactionIds = async (rawTransactionIds, options) => {
|
|
4251
|
-
if (!supportsOpfsFileDownloads()) {
|
|
4252
|
-
return false;
|
|
4253
|
-
}
|
|
4254
|
-
const transactionIds = Array.from(new Set(rawTransactionIds.filter((tx) => !!tx && tx.trim().length > 0)));
|
|
4255
|
-
if (transactionIds.length === 0) {
|
|
4256
|
-
return false;
|
|
4257
|
-
}
|
|
4258
|
-
const excludedTransactions = await getExcludedTransactions();
|
|
4259
|
-
await ensureReadGatewaySelected();
|
|
4260
|
-
// Browser: do not call getTransactionStatus() — many gateways (e.g. g8way.io) redirect or omit
|
|
4261
|
-
// CORS headers, so fetch() fails and we would wrongly exclude txs. The OPFS worker fetch decides success.
|
|
4262
|
-
const transactionIdsToDownload = transactionIds.filter((id) => !excludedTransactions.has(id));
|
|
4263
|
-
if (transactionIdsToDownload.length === 0) {
|
|
4264
|
-
return false;
|
|
4265
|
-
}
|
|
4266
|
-
// One batched lookup for every upload's Content-SHA-256 tag; downloads are verified against it.
|
|
4267
|
-
// Missing tags (old uploads, or a just-published one not indexed yet) just mean no verification.
|
|
4268
|
-
const expectedContentHashes = await getExpectedContentHashes(transactionIdsToDownload);
|
|
4269
|
-
// Preferred gateways first: a just-published item is only on the gateway it was uploaded through.
|
|
4270
|
-
const arweaveBaseUrls = getArweaveReadBaseUrls();
|
|
4271
|
-
// Where each ItemStorage child of a composite goes (the worker cannot read the DB or the schema).
|
|
4272
|
-
const compositeChildPaths = await getCompositeChildPaths(transactionIdsToDownload).catch((error) => {
|
|
4273
|
-
logger$2('[download] composite child paths lookup failed', error);
|
|
4274
|
-
return {};
|
|
4275
|
-
});
|
|
4276
|
-
let result;
|
|
4277
|
-
if (transactionIdsToDownload.length === 1) {
|
|
4278
|
-
const transactionId = transactionIdsToDownload[0];
|
|
4279
|
-
result = await BaseFileManager.downloadFileByTransactionId({
|
|
4280
|
-
transactionId,
|
|
4281
|
-
arweaveHost: BaseArweaveClient.getBaseUrl(),
|
|
4282
|
-
arweaveBaseUrls,
|
|
4283
|
-
excludedTransactions,
|
|
4284
|
-
expectedContentHash: expectedContentHashes[transactionId],
|
|
4285
|
-
compositeChildPaths: compositeChildPaths[transactionId],
|
|
4286
|
-
skipExisting: options?.skipExisting,
|
|
4287
|
-
});
|
|
4288
|
-
}
|
|
4289
|
-
else {
|
|
4290
|
-
result = await BaseFileManager.downloadAllFiles({
|
|
4291
|
-
transactionIds: transactionIdsToDownload,
|
|
4292
|
-
arweaveHost: BaseArweaveClient.getBaseUrl(),
|
|
4293
|
-
arweaveBaseUrls,
|
|
4294
|
-
excludedTransactions,
|
|
4295
|
-
expectedContentHashes,
|
|
4296
|
-
compositeChildPaths,
|
|
4297
|
-
skipExisting: options?.skipExisting,
|
|
4298
|
-
});
|
|
4299
|
-
}
|
|
4300
|
-
if (result) {
|
|
4301
|
-
for (const transactionId of result.hashMismatchTransactionIds) {
|
|
4302
|
-
// Not saved by the worker; left for a later run (a gateway may serve the right bytes then).
|
|
4303
|
-
logger$2(`[download] ${transactionId}: downloaded bytes do not match Content-SHA-256; skipped`);
|
|
4304
|
-
}
|
|
4305
|
-
}
|
|
4306
|
-
return true;
|
|
4307
|
-
};
|
|
4308
|
-
const runBulkDownloadWithDedupe = async (transactionIds) => {
|
|
4309
|
-
if (bulkDownloadInFlight) {
|
|
4310
|
-
return bulkDownloadInFlight;
|
|
4311
|
-
}
|
|
4312
|
-
bulkDownloadInFlight = (async () => {
|
|
4313
|
-
try {
|
|
4314
|
-
await downloadTransactionIds(transactionIds, { skipExisting: true });
|
|
4315
|
-
}
|
|
4316
|
-
finally {
|
|
4317
|
-
bulkDownloadInFlight = null;
|
|
4318
|
-
}
|
|
4319
|
-
})();
|
|
4320
|
-
return bulkDownloadInFlight;
|
|
4321
|
-
};
|
|
4322
|
-
const downloadAllFilesBinaryRequestHandler = async (addressesHint) => {
|
|
4323
|
-
if (!supportsOpfsFileDownloads()) {
|
|
4324
|
-
return;
|
|
4325
|
-
}
|
|
4326
|
-
let fromDb = [];
|
|
4327
|
-
if (BaseDb.isAppDbReady()) {
|
|
4328
|
-
fromDb = await getAllAddressesFromDb();
|
|
4329
|
-
}
|
|
4330
|
-
if (!BaseDb.isAppDbReady()) {
|
|
4331
|
-
await getAppDbReady();
|
|
4332
|
-
fromDb = await getAllAddressesFromDb();
|
|
4333
|
-
}
|
|
4334
|
-
const addresses = mergeAddressLists(addressesHint, fromDb);
|
|
4335
|
-
if (!addresses || addresses.length === 0) {
|
|
4336
|
-
return;
|
|
4337
|
-
}
|
|
4338
|
-
const queryClient = BaseQueryClient.getQueryClient();
|
|
4339
|
-
const easClient = BaseEasClient.getEasClient();
|
|
4340
|
-
const filesMetadataQueryKey = ['getFilesMetadata', ...addresses];
|
|
4341
|
-
const fetchFilesMetadata = () => easClient.request(GET_FILES_METADATA, {
|
|
4342
|
-
where: {
|
|
4343
|
-
attester: {
|
|
4344
|
-
in: addresses,
|
|
4345
|
-
},
|
|
4346
|
-
schema: {
|
|
4347
|
-
is: {
|
|
4348
|
-
id: {
|
|
4349
|
-
equals: '0x55fdefb36fcbbaebeb7d6b41dc3a1a9666e4e42154267c889de064faa7ede517',
|
|
4350
|
-
},
|
|
4351
|
-
},
|
|
4352
|
-
},
|
|
4353
|
-
},
|
|
4354
|
-
});
|
|
4355
|
-
let { filesMetadata } = await queryClient.fetchQuery({
|
|
4356
|
-
queryKey: filesMetadataQueryKey,
|
|
4357
|
-
queryFn: fetchFilesMetadata,
|
|
4358
|
-
staleTime: GET_FILES_METADATA_STALE_MS,
|
|
4359
|
-
});
|
|
4360
|
-
if ((!Array.isArray(filesMetadata) || filesMetadata.length === 0) && addresses.length > 0) {
|
|
4361
|
-
await queryClient.removeQueries({ queryKey: filesMetadataQueryKey });
|
|
4362
|
-
({ filesMetadata } = await queryClient.fetchQuery({
|
|
4363
|
-
queryKey: filesMetadataQueryKey,
|
|
4364
|
-
queryFn: fetchFilesMetadata,
|
|
4365
|
-
staleTime: GET_FILES_METADATA_STALE_MS,
|
|
4366
|
-
}));
|
|
4367
|
-
}
|
|
4368
|
-
const filesRoot = BaseFileManager.getWorkingDir();
|
|
4369
|
-
await BaseFileManager.createDirIfNotExists(filesRoot);
|
|
4370
|
-
await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('html'));
|
|
4371
|
-
await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('json'));
|
|
4372
|
-
await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('images'));
|
|
4373
|
-
await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('files'));
|
|
4374
|
-
const excludedTransactions = await getExcludedTransactions();
|
|
4375
|
-
const fromEas = parseTransactionIdsFromMetadata(filesMetadata ?? [], excludedTransactions);
|
|
4376
|
-
const fromLocal = await collectTransactionIdsFromLocalMetadata();
|
|
4377
|
-
const transactionIds = Array.from(new Set([...fromEas, ...fromLocal]));
|
|
4378
|
-
await runBulkDownloadWithDedupe(transactionIds);
|
|
4379
|
-
};
|
|
4380
|
-
const downloadTransactionIdWithDedupe = async (transactionId) => {
|
|
4381
|
-
if (!supportsOpfsFileDownloads() || !transactionId || transactionId.trim().length === 0) {
|
|
4382
|
-
return false;
|
|
4383
|
-
}
|
|
4384
|
-
const normalizedTxId = normalizeArweaveTxId(transactionId);
|
|
4385
|
-
if (!normalizedTxId) {
|
|
4386
|
-
logger$2(`[download] not an Arweave transaction id: ${transactionId}`);
|
|
4387
|
-
return false;
|
|
4388
|
-
}
|
|
4389
|
-
const existing = lazyDownloadInFlightByTransactionId.get(normalizedTxId);
|
|
4390
|
-
if (existing) {
|
|
4391
|
-
return existing;
|
|
4392
|
-
}
|
|
4393
|
-
const inFlight = (async () => {
|
|
4394
|
-
try {
|
|
4395
|
-
// No skipExisting: the caller found no local file, so a file on disk it could not see is
|
|
4396
|
-
// written again and announced.
|
|
4397
|
-
const ok = await downloadTransactionIds([normalizedTxId]);
|
|
4398
|
-
return ok;
|
|
4399
|
-
}
|
|
4400
|
-
finally {
|
|
4401
|
-
lazyDownloadInFlightByTransactionId.delete(normalizedTxId);
|
|
4402
|
-
}
|
|
4403
|
-
})();
|
|
4404
|
-
lazyDownloadInFlightByTransactionId.set(normalizedTxId, inFlight);
|
|
4405
|
-
return inFlight;
|
|
4406
|
-
};
|
|
4407
|
-
const scheduleBulkFilesDownloadFromEasSyncThrottled = throttle((addressesHint) => {
|
|
4408
|
-
void downloadAllFilesBinaryRequestHandler(addressesHint).catch((error) => {
|
|
4409
|
-
logger$2('[scheduleBulkFilesDownloadFromEasSync] failed', error);
|
|
4410
|
-
});
|
|
4411
|
-
}, AUTO_BULK_DOWNLOAD_THROTTLE_MS, { leading: true, trailing: false });
|
|
4412
|
-
const scheduleBulkFilesDownloadFromEasSync = (addressesHint) => {
|
|
4413
|
-
if (!supportsOpfsFileDownloads()) {
|
|
4414
|
-
return;
|
|
4415
|
-
}
|
|
4416
|
-
scheduleBulkFilesDownloadFromEasSyncThrottled(addressesHint);
|
|
4417
|
-
};
|
|
4418
|
-
|
|
4419
|
-
var download = /*#__PURE__*/Object.freeze({
|
|
4420
|
-
__proto__: null,
|
|
4421
|
-
downloadAllFilesBinaryRequestHandler: downloadAllFilesBinaryRequestHandler,
|
|
4422
|
-
downloadTransactionIdWithDedupe: downloadTransactionIdWithDedupe,
|
|
4423
|
-
scheduleBulkFilesDownloadFromEasSync: scheduleBulkFilesDownloadFromEasSync
|
|
4424
|
-
});
|
|
4425
|
-
|
|
4426
|
-
const logger$1 = debug('seedSdk:helpers:ensureImageLocal');
|
|
4427
|
-
/** Arweave transaction ids are 43 chars in the URL-safe base64 alphabet. */
|
|
4428
|
-
const ARWEAVE_TX_ID_RE = /^[a-z0-9_-]{43}$/i;
|
|
4429
|
-
const IMAGE_SIZES = [
|
|
4430
|
-
ImageSize.EXTRA_SMALL,
|
|
4431
|
-
ImageSize.SMALL,
|
|
4432
|
-
ImageSize.MEDIUM,
|
|
4433
|
-
ImageSize.LARGE,
|
|
4434
|
-
ImageSize.EXTRA_LARGE,
|
|
4435
|
-
];
|
|
4436
|
-
const inFlightByKey = new Map();
|
|
4437
|
-
const normalizeArweaveTxIdForEnsure = (raw) => {
|
|
4438
|
-
if (raw == null || raw === '')
|
|
4439
|
-
return undefined;
|
|
4440
|
-
const t = String(raw).trim();
|
|
4441
|
-
if (ARWEAVE_TX_ID_RE.test(t))
|
|
4442
|
-
return t;
|
|
4443
|
-
const m = t.match(/^([a-z0-9_-]{43})(?:\.[^/]+)?$/i);
|
|
4444
|
-
if (m)
|
|
4445
|
-
return m[1];
|
|
4446
|
-
const embedded = t.match(/[a-z0-9_-]{43}/gi);
|
|
4447
|
-
if (embedded) {
|
|
4448
|
-
for (const candidate of embedded) {
|
|
4449
|
-
if (ARWEAVE_TX_ID_RE.test(candidate))
|
|
4450
|
-
return candidate;
|
|
4451
|
-
}
|
|
4452
|
-
}
|
|
4453
|
-
return undefined;
|
|
4454
|
-
};
|
|
4455
|
-
const closestImageSize = (width) => {
|
|
4456
|
-
let best = ImageSize.EXTRA_SMALL;
|
|
4457
|
-
let bestDist = Math.abs(best - width);
|
|
4458
|
-
for (const size of IMAGE_SIZES) {
|
|
4459
|
-
const dist = Math.abs(size - width);
|
|
4460
|
-
if (dist < bestDist) {
|
|
4461
|
-
best = size;
|
|
4462
|
-
bestDist = dist;
|
|
4463
|
-
}
|
|
4464
|
-
}
|
|
4465
|
-
return best;
|
|
4466
|
-
};
|
|
4467
|
-
const mapRequestedWidthsToImageSizes = (widths) => {
|
|
4468
|
-
const requested = widths && widths.length > 0 ? widths : [ImageSize.EXTRA_SMALL];
|
|
4469
|
-
const unique = new Set();
|
|
4470
|
-
for (const w of requested) {
|
|
4471
|
-
if (typeof w !== 'number' || !Number.isFinite(w) || w <= 0)
|
|
4472
|
-
continue;
|
|
4473
|
-
unique.add(closestImageSize(w));
|
|
4474
|
-
}
|
|
4475
|
-
if (unique.size === 0) {
|
|
4476
|
-
unique.add(ImageSize.EXTRA_SMALL);
|
|
4477
|
-
}
|
|
4478
|
-
return Array.from(unique).sort((a, b) => a - b);
|
|
4479
|
-
};
|
|
4480
|
-
const getBasename = (filePath) => {
|
|
4481
|
-
const path = BaseFileManager.getPathModule();
|
|
4482
|
-
return path.basename(filePath);
|
|
4483
|
-
};
|
|
4484
|
-
const getBasenameWithoutExtension = (fileName) => {
|
|
4485
|
-
const match = fileName.match(/^(.*[\/\\])?([^\/\\]+?)(\.[^.\/\\]*)?$/);
|
|
4486
|
-
return match?.[2] ?? fileName;
|
|
4487
|
-
};
|
|
4488
|
-
const resolveMatchingImagePath = async (needle) => {
|
|
4489
|
-
const dirPath = BaseFileManager.getFilesPath('images');
|
|
4490
|
-
const dirExists = await BaseFileManager.pathExists(dirPath);
|
|
4491
|
-
if (!dirExists)
|
|
4492
|
-
return undefined;
|
|
4493
|
-
const fs = await BaseFileManager.getFs();
|
|
4494
|
-
const path = BaseFileManager.getPathModule();
|
|
4495
|
-
const files = await fs.promises.readdir(dirPath);
|
|
4496
|
-
// Prefer exact basename match (files only); skip size dirs.
|
|
4497
|
-
const exact = files.find((file) => path.basename(file) === needle);
|
|
4498
|
-
if (exact) {
|
|
4499
|
-
const candidate = BaseFileManager.getFilesPath('images', exact);
|
|
4500
|
-
try {
|
|
4501
|
-
const zenfs = await BaseFileManager.getFs();
|
|
4502
|
-
const stat = await zenfs.promises.stat(candidate);
|
|
4503
|
-
if (stat.isFile())
|
|
4504
|
-
return candidate;
|
|
4505
|
-
}
|
|
4506
|
-
catch {
|
|
4507
|
-
// fall through
|
|
4508
|
-
}
|
|
4509
|
-
}
|
|
4510
|
-
const matchingFiles = files.filter((file) => {
|
|
4511
|
-
const base = path.basename(file);
|
|
4512
|
-
return base.includes(needle);
|
|
4513
|
-
});
|
|
4514
|
-
for (const file of matchingFiles) {
|
|
4515
|
-
const candidate = BaseFileManager.getFilesPath('images', file);
|
|
4516
|
-
try {
|
|
4517
|
-
const zenfs = await BaseFileManager.getFs();
|
|
4518
|
-
const stat = await zenfs.promises.stat(candidate);
|
|
4519
|
-
if (stat.isFile())
|
|
4520
|
-
return candidate;
|
|
4521
|
-
}
|
|
4522
|
-
catch {
|
|
4523
|
-
continue;
|
|
4524
|
-
}
|
|
4525
|
-
}
|
|
4526
|
-
return undefined;
|
|
4527
|
-
};
|
|
4528
|
-
const sizedVariantPath = (originalFilePath, size) => {
|
|
4529
|
-
const path = BaseFileManager.getPathModule();
|
|
4530
|
-
const dir = path.dirname(originalFilePath);
|
|
4531
|
-
const base = getBasenameWithoutExtension(getBasename(originalFilePath));
|
|
4532
|
-
return path.join(dir, String(size), `${base}.webp`);
|
|
4533
|
-
};
|
|
4534
|
-
const ensureKey = (txId, fileName, sizes) => {
|
|
4535
|
-
const idPart = txId ?? fileName ?? '';
|
|
4536
|
-
return `${idPart}|${sizes.join(',')}`;
|
|
4537
|
-
};
|
|
4538
|
-
const runEnsureImageLocal = async (params) => {
|
|
4539
|
-
if (!supportsOpfsFileDownloads()) {
|
|
4540
|
-
return { status: 'unsupported' };
|
|
4541
|
-
}
|
|
4542
|
-
const fileName = params.fileName?.trim() || undefined;
|
|
4543
|
-
const txId = normalizeArweaveTxIdForEnsure(params.transactionId) ??
|
|
4544
|
-
normalizeArweaveTxIdForEnsure(fileName);
|
|
4545
|
-
if (!txId && !fileName) {
|
|
4546
|
-
return { status: 'missing-id' };
|
|
4547
|
-
}
|
|
4548
|
-
const targetSizes = mapRequestedWidthsToImageSizes(params.widths);
|
|
4549
|
-
const needles = [fileName, txId].filter((n) => typeof n === 'string' && n.length > 0);
|
|
4550
|
-
try {
|
|
4551
|
-
let filePath;
|
|
4552
|
-
for (const needle of needles) {
|
|
4553
|
-
filePath = await resolveMatchingImagePath(needle);
|
|
4554
|
-
if (filePath)
|
|
4555
|
-
break;
|
|
4556
|
-
}
|
|
4557
|
-
if (!filePath && txId) {
|
|
4558
|
-
await downloadTransactionIdWithDedupe(txId);
|
|
4559
|
-
for (const needle of needles) {
|
|
4560
|
-
filePath = await resolveMatchingImagePath(needle);
|
|
4561
|
-
if (filePath)
|
|
4562
|
-
break;
|
|
4563
|
-
}
|
|
4564
|
-
// Downloads store as bare tx id
|
|
4565
|
-
if (!filePath) {
|
|
4566
|
-
const direct = BaseFileManager.getFilesPath('images', txId);
|
|
4567
|
-
if (await BaseFileManager.pathExists(direct)) {
|
|
4568
|
-
filePath = direct;
|
|
4569
|
-
}
|
|
4570
|
-
}
|
|
4571
|
-
}
|
|
4572
|
-
if (!filePath) {
|
|
4573
|
-
return { status: 'failed' };
|
|
4574
|
-
}
|
|
4575
|
-
const createdWidths = [];
|
|
4576
|
-
for (const size of targetSizes) {
|
|
4577
|
-
const variantPath = sizedVariantPath(filePath, size);
|
|
4578
|
-
const exists = await BaseFileManager.pathExists(variantPath);
|
|
4579
|
-
if (exists)
|
|
4580
|
-
continue;
|
|
4581
|
-
await BaseFileManager.resizeImage({
|
|
4582
|
-
filePath,
|
|
4583
|
-
width: size,
|
|
4584
|
-
height: size,
|
|
4585
|
-
});
|
|
4586
|
-
createdWidths.push(size);
|
|
4587
|
-
}
|
|
4588
|
-
return {
|
|
4589
|
-
status: 'ready',
|
|
4590
|
-
filePath,
|
|
4591
|
-
createdWidths,
|
|
4592
|
-
};
|
|
4593
|
-
}
|
|
4594
|
-
catch (error) {
|
|
4595
|
-
logger$1('ensureImageLocal failed', error);
|
|
4596
|
-
return { status: 'failed' };
|
|
4597
|
-
}
|
|
4598
|
-
};
|
|
4599
|
-
/**
|
|
4600
|
-
* Ensure an image original exists in OPFS (downloading from Arweave if needed)
|
|
4601
|
-
* and create only the requested ImageSize variant(s) for that one file.
|
|
4602
|
-
*/
|
|
4603
|
-
const ensureImageLocal = async (params) => {
|
|
4604
|
-
if (!supportsOpfsFileDownloads()) {
|
|
4605
|
-
return { status: 'unsupported' };
|
|
4606
|
-
}
|
|
4607
|
-
const fileName = params.fileName?.trim() || undefined;
|
|
4608
|
-
const txId = normalizeArweaveTxIdForEnsure(params.transactionId) ??
|
|
4609
|
-
normalizeArweaveTxIdForEnsure(fileName);
|
|
4610
|
-
if (!txId && !fileName) {
|
|
4611
|
-
return { status: 'missing-id' };
|
|
4612
|
-
}
|
|
4613
|
-
const targetSizes = mapRequestedWidthsToImageSizes(params.widths);
|
|
4614
|
-
const key = ensureKey(txId, fileName, targetSizes);
|
|
4615
|
-
const existing = inFlightByKey.get(key);
|
|
4616
|
-
if (existing) {
|
|
4617
|
-
return existing;
|
|
4618
|
-
}
|
|
4619
|
-
const inFlight = (async () => {
|
|
4620
|
-
try {
|
|
4621
|
-
return await runEnsureImageLocal({
|
|
4622
|
-
transactionId: txId,
|
|
4623
|
-
fileName,
|
|
4624
|
-
widths: targetSizes,
|
|
4625
|
-
});
|
|
4626
|
-
}
|
|
4627
|
-
finally {
|
|
4628
|
-
inFlightByKey.delete(key);
|
|
4629
|
-
}
|
|
4630
|
-
})();
|
|
4631
|
-
inFlightByKey.set(key, inFlight);
|
|
4632
|
-
return inFlight;
|
|
4633
|
-
};
|
|
4634
|
-
|
|
4635
|
-
const randomId = customAlphabet(alphanumeric, 10);
|
|
4636
|
-
/**
|
|
4637
|
-
* A 10-character local id. Never starts with "0x" (about 1 in 3,844 random ids would), so code that
|
|
4638
|
-
* tells local ids from EAS uids by that prefix can't mistake one for the other.
|
|
4639
|
-
*/
|
|
4640
|
-
const generateId = () => {
|
|
4641
|
-
let id = randomId();
|
|
4642
|
-
while (id.startsWith('0x'))
|
|
4643
|
-
id = randomId();
|
|
4644
|
-
return id;
|
|
4645
|
-
};
|
|
4646
|
-
|
|
4647
|
-
var generateId$1 = /*#__PURE__*/Object.freeze({
|
|
4648
|
-
__proto__: null,
|
|
4649
|
-
generateId: generateId
|
|
4650
|
-
});
|
|
4651
|
-
|
|
4652
|
-
const logger = debug('seedSdk:shared:helpers');
|
|
4653
|
-
const getMimeType = (base64) => {
|
|
4654
|
-
if (!base64) {
|
|
4655
|
-
return null;
|
|
4656
|
-
}
|
|
4657
|
-
const result = base64.match(/^data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,/);
|
|
4658
|
-
if (result && result.length > 1) {
|
|
4659
|
-
return result[1];
|
|
4660
|
-
}
|
|
4661
|
-
else {
|
|
4662
|
-
return null; // MIME type could not be determined
|
|
4663
|
-
}
|
|
4664
|
-
};
|
|
4665
|
-
const getCorrectId = (localIdOrUid) => {
|
|
4666
|
-
const id = {
|
|
4667
|
-
localId: undefined,
|
|
4668
|
-
uid: undefined,
|
|
4669
|
-
};
|
|
4670
|
-
if (!localIdOrUid) {
|
|
4671
|
-
return id;
|
|
4672
|
-
}
|
|
4673
|
-
if (localIdOrUid.length === 10) {
|
|
4674
|
-
id.localId = localIdOrUid;
|
|
4675
|
-
}
|
|
4676
|
-
if (localIdOrUid.startsWith('0x') && localIdOrUid.length === 66) {
|
|
4677
|
-
id.uid = localIdOrUid;
|
|
4678
|
-
}
|
|
4679
|
-
return id;
|
|
4680
|
-
};
|
|
4681
|
-
const getDataTypeFromString = (data) => {
|
|
4682
|
-
const nonImageBase64Regex = /^(?!data:image\/(?:jpeg|png|gif|bmp|webp);base64,)[A-Za-z0-9+/=]+$/;
|
|
4683
|
-
if (nonImageBase64Regex.test(data)) {
|
|
4684
|
-
return 'base64';
|
|
4685
|
-
}
|
|
4686
|
-
// Regular expression for base64 (simple version, checking for base64 format)
|
|
4687
|
-
const imageBase64Regex = /^data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/]+={0,2}$/;
|
|
4688
|
-
if (imageBase64Regex.test(data)) {
|
|
4689
|
-
return 'imageBase64';
|
|
4690
|
-
}
|
|
4691
|
-
// Blob URLs are fetchable but session-scoped; recognize them as url type
|
|
4692
|
-
if (data.startsWith('blob:')) {
|
|
4693
|
-
return 'url';
|
|
4694
|
-
}
|
|
4695
|
-
// Regular expression for URL (simple version, checking for common URL format)
|
|
4696
|
-
const urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/;
|
|
4697
|
-
if (urlRegex.test(data)) {
|
|
4698
|
-
return 'url';
|
|
4699
|
-
}
|
|
4700
|
-
return null;
|
|
4701
|
-
};
|
|
4702
|
-
const convertTxIdToImage = async (txId) => {
|
|
4703
|
-
const imageFilePath = BaseFileManager.getFilesPath('images', txId);
|
|
4704
|
-
const fileExists = await BaseFileManager.pathExists(imageFilePath);
|
|
4705
|
-
if (!fileExists) {
|
|
4706
|
-
logger(`[ItemView] [updateImage] ${imageFilePath} does not exist`);
|
|
4707
|
-
return;
|
|
4708
|
-
}
|
|
4709
|
-
const buffer = await BaseFileManager.readFileAsBuffer(imageFilePath);
|
|
4710
|
-
// Convert Blob to ArrayBuffer if needed, or use Buffer directly
|
|
4711
|
-
let arrayBuffer;
|
|
4712
|
-
if (buffer instanceof Blob) {
|
|
4713
|
-
arrayBuffer = await buffer.arrayBuffer();
|
|
4714
|
-
}
|
|
4715
|
-
else if (buffer instanceof ArrayBuffer) {
|
|
4716
|
-
arrayBuffer = buffer;
|
|
4717
|
-
}
|
|
4718
|
-
else {
|
|
4719
|
-
// Node.js Buffer
|
|
4720
|
-
const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
4721
|
-
// Handle SharedArrayBuffer case by creating a new ArrayBuffer
|
|
4722
|
-
if (sliced instanceof SharedArrayBuffer) {
|
|
4723
|
-
const uint8Array = new Uint8Array(sliced);
|
|
4724
|
-
arrayBuffer = uint8Array.buffer.slice(0);
|
|
4725
|
-
}
|
|
4726
|
-
else {
|
|
4727
|
-
arrayBuffer = sliced;
|
|
4728
|
-
}
|
|
4729
|
-
}
|
|
4730
|
-
const uint = new Uint8Array(arrayBuffer);
|
|
4731
|
-
const imageBlob = new Blob([uint]);
|
|
4732
|
-
return URL.createObjectURL(imageBlob);
|
|
4733
|
-
};
|
|
4734
|
-
/**
|
|
4735
|
-
* Constructs an Arweave URL for a storage transaction ID
|
|
4736
|
-
* @param storageTransactionId - The Arweave transaction ID
|
|
4737
|
-
* @returns The full URL to access the transaction data on Arweave (e.g., https://arweave.net/raw/{transactionId})
|
|
4738
|
-
* @deprecated Use BaseArweaveClient.getRawUrl() instead for better consistency and testability
|
|
4739
|
-
*/
|
|
4740
|
-
const getArweaveUrlForTransaction = (storageTransactionId) => {
|
|
4741
|
-
return BaseArweaveClient.getRawUrl(storageTransactionId);
|
|
4742
|
-
};
|
|
4743
|
-
|
|
4744
|
-
export { isNode as $, AmbiguousModelError as A, modelsRelations as B, pendingWrites as C, DEFAULT_TEXT_MAX_LENGTH as D, properties as E, propertiesRelations as F, propertyUidRelations as G, propertyUids as H, publishProcesses as I, schemas as J, uploadProcesses as K, BaseDb as L, ModelPropertyDataTypes as M, resolveStorageNameToSchemaName as N, nameMatchLevels as O, Property as P, BaseFileManager as Q, resolveModelRecord as R, getPropertyIdForModelAndName as S, THtmlEmbeddedDataUriPolicy as T, resolveItemModelFileId as U, INTERNAL_STORAGE_MODEL_FILE_IDS as V, getStorageDirForDataType as W, getContentSha256 as X, buildArweaveContentTags as Y, getItemStorageSubdir as Z, getItemStorageLocalFileName as _, TProperty as a, convertTxIdToImage as a$, ZERO_BYTES32 as a0, getItemModelScope as a1, INTERNAL_PROPERTY_NAMES as a2, isStorageSeedRef as a3, defaultAttestationData as a4, INTERNAL_DATA_TYPES as a5, listRelationEasPropertyName as a6, easPropertyNameForPublish as a7, normalizePublisher as a8, getAllAddressesFromDb 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, renameModelInDb as aI, currentNameMatchLevels as aJ, withSqliteBusyRetry as aK, loadModelsFromDbForSchema as aL, mergeDbModelsIntoSchemaModels as aM, addSchemaToDb as aN, addModelsToDb as aO, clearNameMatchLevels as aP, isTestOrSeedDev as aQ, warnOnInvalidNameMatch as aR, INTERNAL_SCHEMA_IDS as aS, linkModelToSchema as aT, getMetadataPropertyNamesForQuery as aU, getWatchedAddressesFromDb as aV, getOwnedAddressesFromDb as aW, CLIENT_NOT_INITIALIZED as aX, toSchemaPropertyName as aY, easSchemaDefinitionForProperty as aZ, getStorageTransactionIdForSeedUid as a_, loadNameMatchLevels as aa, scheduleBulkFilesDownloadFromEasSync as ab, EAS_SEED_DATA_SYNCED_TO_DB_EVENT as ac, generateId as ad, normalizeHexAddress as ae, toMetadataPropertyName as af, getItemStoragePropertiesForModel as ag, getModelIdByFileId as ah, getModelId as ai, setCachedPropertyRefresher as aj, isSqliteBusyError as ak, SQLITE_BUSY_RETRY_DELAYS_MS as al, isBrowser 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_UID_OPTIMISM_SEPOLIA as as, classifyMediaRef as at, closestImageSize as au, contentSha256TagMatches as av, ensureImageLocal as aw, getAddressesForItemsFilter as ax, getArweaveUrlForTransaction as ay, getFeedItemStringField as az, TPropertyConstructor as b, getPropertyIdForSchemaFileId as b0, downloadTransactionIdWithDedupe as b1, resolveMetadataRecord as b2, getDataTypeFromString as b3, getMimeType as b4, listRelationStoragePropertyName as b5, PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP as b6, toItemPropertyKey as b7, getAlternatePropertyNameForInstanceLookup as b8, BaseDb$1 as b9, ModelSchema as ba, BaseFileManager$1 as bb, SchemaSchema as bc, ModelSchemaSchema as bd, resolveModelRecord$1 as be, db as bf, download as bg, generateId$1 as bh, TPropertyDataType as c, TPropertyDefs as d, TStorageType as e, TValidationRules as f, normalizePropertyRecordSchema as g, appState as h, isDataType as i, config as j, getCorrectId as k, isPublishedSeedRef as l, metadata as m, normalizeDataType as n, modelUids as o, models as p, normalizeRelationPropertyValue as q, resolveSeedIdsFromRefString as r, seeds as s, arweaveL1FinalizeJobs as t, easSyncProcesses as u, versions as v, entityChanges as w, htmlEmbeddedImageCoPublish as x, modelRelations as y, modelSchemas as z };
|
|
4745
|
-
//# sourceMappingURL=index-Dx_GTdyQ.js.map
|