@seedprotocol/sdk 0.7.9-next.1189.g5e63267 → 0.7.9
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-CG5IhPtL.js → Item-Bf4kWKpH.js} +5576 -6649
- package/dist/Item-Bf4kWKpH.js.map +1 -0
- package/dist/{ModelProperty-DDD47KVm.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-CZzKNL04.js → SchemaValidationService-CnmKyzR-.js} +6 -9
- package/dist/{SchemaValidationService-CZzKNL04.js.map → SchemaValidationService-CnmKyzR-.js.map} +1 -1
- package/dist/{configurePlatform-Dukm2QwO.js → configurePlatform-CiOJ1R3q.js} +3 -12
- package/dist/configurePlatform-CiOJ1R3q.js.map +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/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 +29 -1
- package/dist/{detachRowsBeforeDelete-Di1enVUF.js → detachRowsBeforeDelete-sEaUwlOU.js} +2 -13
- package/dist/{detachRowsBeforeDelete-Di1enVUF.js.map → detachRowsBeforeDelete-sEaUwlOU.js.map} +1 -1
- package/dist/dist/{index-rOTW-2QZ.d.ts → BaseFileManager-BAlySv8w.d.ts} +1407 -2700
- package/dist/dist/BaseFileManager-BAlySv8w.d.ts.map +1 -0
- package/dist/dist/{configurePlatform-Dukm2QwO.d.ts → configurePlatform-CiOJ1R3q.d.ts} +1 -1
- package/dist/dist/configurePlatform-CiOJ1R3q.d.ts.map +1 -0
- package/dist/dist/{index-DxHpPNfn.d.ts → index-D0V-mjCV.d.ts} +39 -1
- package/dist/dist/{index-DxHpPNfn.d.ts.map → index-D0V-mjCV.d.ts.map} +1 -1
- package/dist/dist/{index.node-WsEJCOwC.d.ts → index.node-CYPemH-t.d.ts} +157 -5
- package/dist/dist/{index.node-WsEJCOwC.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-DQoyhp2c.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-DxHpPNfn.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-QPWBkLJA.js → index.browser-CUmBVYen.js} +397 -356
- package/dist/index.browser-CUmBVYen.js.map +1 -0
- package/dist/{index.node-WsEJCOwC.js → index.node-CYPemH-t.js} +10 -10
- package/dist/index.node-CYPemH-t.js.map +1 -0
- package/dist/main.js +23 -1075
- 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/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 +77 -46
- 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 +4 -50
- 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 +6 -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 +5 -1
- package/dist/src/db/read/getPropertyClear.d.ts.map +1 -1
- 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 +2 -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 +13 -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/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/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 +18 -3
- 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 -26
- 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 +13 -13
- 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 +9 -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 +2 -1
- 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 +63 -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/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 -24
- 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-BMHoou6X.js → syncDbWithEas-CA7PvBgu.js} +112 -162
- 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-BHndShch.js → tolerantWebAccess-BJqViq08.js} +10 -15
- package/dist/{tolerantWebAccess-BHndShch.js.map → tolerantWebAccess-BJqViq08.js.map} +1 -1
- package/dist/{resolveModelForSyncedSeed-fIURvYRV.js → updateSeedRevokedAt-BL9Bg-G7.js} +30 -4
- package/dist/updateSeedRevokedAt-BL9Bg-G7.js.map +1 -0
- package/package.json +6 -6
- package/dist/ClientManager-DqL0q_eu.js +0 -5292
- package/dist/ClientManager-DqL0q_eu.js.map +0 -1
- package/dist/Item-CG5IhPtL.js.map +0 -1
- package/dist/ModelProperty-DDD47KVm.js.map +0 -1
- package/dist/Schema-BS8oR7QV.js +0 -4370
- package/dist/Schema-BS8oR7QV.js.map +0 -1
- package/dist/backfillMetadataPropertyIds-C7P7FL6F.js +0 -72
- package/dist/backfillMetadataPropertyIds-C7P7FL6F.js.map +0 -1
- package/dist/changeFeed-M8sGbOrW.js +0 -726
- package/dist/changeFeed-M8sGbOrW.js.map +0 -1
- package/dist/configurePlatform-Dukm2QwO.js.map +0 -1
- package/dist/dist/ClientManager-DqL0q_eu.d.ts +0 -952
- package/dist/dist/ClientManager-DqL0q_eu.d.ts.map +0 -1
- package/dist/dist/Item-CG5IhPtL.d.ts +0 -1115
- package/dist/dist/Item-CG5IhPtL.d.ts.map +0 -1
- package/dist/dist/ModelProperty-DDD47KVm.d.ts +0 -315
- package/dist/dist/ModelProperty-DDD47KVm.d.ts.map +0 -1
- package/dist/dist/Schema-BS8oR7QV.d.ts +0 -432
- package/dist/dist/Schema-BS8oR7QV.d.ts.map +0 -1
- package/dist/dist/SchemaValidationService-CZzKNL04.d.ts +0 -118
- package/dist/dist/SchemaValidationService-CZzKNL04.d.ts.map +0 -1
- package/dist/dist/backfillMetadataPropertyIds-C7P7FL6F.d.ts +0 -7
- package/dist/dist/backfillMetadataPropertyIds-C7P7FL6F.d.ts.map +0 -1
- package/dist/dist/changeFeed-M8sGbOrW.d.ts +0 -160
- package/dist/dist/changeFeed-M8sGbOrW.d.ts.map +0 -1
- package/dist/dist/configurePlatform-Dukm2QwO.d.ts.map +0 -1
- package/dist/dist/detachRowsBeforeDelete-Di1enVUF.d.ts +0 -13
- package/dist/dist/detachRowsBeforeDelete-Di1enVUF.d.ts.map +0 -1
- package/dist/dist/eas-B5VJolSj.d.ts +0 -12
- package/dist/dist/eas-B5VJolSj.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-CS3nDpUU.d.ts +0 -515
- package/dist/dist/errors-CS3nDpUU.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-CuQmK7An.d.ts +0 -13
- package/dist/dist/findSeedRowsByUid-CuQmK7An.d.ts.map +0 -1
- package/dist/dist/getItem-Uha3_H0t.d.ts +0 -6
- package/dist/dist/getItem-Uha3_H0t.d.ts.map +0 -1
- package/dist/dist/getPublishPayload-pbin4lk8.d.ts +0 -65
- package/dist/dist/getPublishPayload-pbin4lk8.d.ts.map +0 -1
- package/dist/dist/getPublishPendingDiff-ByIaPvCn.d.ts +0 -18
- package/dist/dist/getPublishPendingDiff-ByIaPvCn.d.ts.map +0 -1
- package/dist/dist/getPublishUploads-BBqiowVc.d.ts +0 -89
- package/dist/dist/getPublishUploads-BBqiowVc.d.ts.map +0 -1
- package/dist/dist/index-DQoyhp2c.d.ts +0 -6
- package/dist/dist/index-DQoyhp2c.d.ts.map +0 -1
- package/dist/dist/index-njjjo74v.d.ts +0 -14
- package/dist/dist/index-njjjo74v.d.ts.map +0 -1
- package/dist/dist/index-rOTW-2QZ.d.ts.map +0 -1
- package/dist/dist/index.browser-QPWBkLJA.d.ts +0 -9959
- package/dist/dist/index.browser-QPWBkLJA.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-onFOG2x6.d.ts +0 -7
- package/dist/dist/modelPropertyRecordSchemas-onFOG2x6.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-C85DCD7K.d.ts +0 -37
- package/dist/dist/property-C85DCD7K.d.ts.map +0 -1
- package/dist/dist/propertyValidationRules-CjUPOFeu.d.ts +0 -13
- package/dist/dist/propertyValidationRules-CjUPOFeu.d.ts.map +0 -1
- package/dist/dist/publishDraftGraph-DBWAN72w.d.ts +0 -93
- package/dist/dist/publishDraftGraph-DBWAN72w.d.ts.map +0 -1
- package/dist/dist/resolveModelForSyncedSeed-fIURvYRV.d.ts +0 -21
- package/dist/dist/resolveModelForSyncedSeed-fIURvYRV.d.ts.map +0 -1
- package/dist/dist/syncDbWithEas-BMHoou6X.d.ts +0 -13
- package/dist/dist/syncDbWithEas-BMHoou6X.d.ts.map +0 -1
- package/dist/dist/tolerantWebAccess-BHndShch.d.ts +0 -113
- package/dist/dist/tolerantWebAccess-BHndShch.d.ts.map +0 -1
- package/dist/dist/updateSeedRevokedAt-DpfG6ztE.d.ts +0 -11
- package/dist/dist/updateSeedRevokedAt-DpfG6ztE.d.ts.map +0 -1
- package/dist/eas-B5VJolSj.js +0 -67
- package/dist/eas-B5VJolSj.js.map +0 -1
- package/dist/errors-CS3nDpUU.js +0 -4006
- package/dist/errors-CS3nDpUU.js.map +0 -1
- package/dist/eventBus-7vxTCc3a.js +0 -64
- package/dist/eventBus-7vxTCc3a.js.map +0 -1
- package/dist/findSeedRowsByUid-CuQmK7An.js +0 -47
- package/dist/findSeedRowsByUid-CuQmK7An.js.map +0 -1
- package/dist/getItem-Uha3_H0t.js +0 -45
- package/dist/getItem-Uha3_H0t.js.map +0 -1
- package/dist/getPublishPayload-pbin4lk8.js +0 -1736
- package/dist/getPublishPayload-pbin4lk8.js.map +0 -1
- package/dist/getPublishPendingDiff-ByIaPvCn.js +0 -118
- package/dist/getPublishPendingDiff-ByIaPvCn.js.map +0 -1
- package/dist/getPublishUploads-BBqiowVc.js +0 -1080
- package/dist/getPublishUploads-BBqiowVc.js.map +0 -1
- package/dist/index-DQoyhp2c.js.map +0 -1
- package/dist/index-DxHpPNfn.js.map +0 -1
- package/dist/index-njjjo74v.js +0 -29
- package/dist/index-njjjo74v.js.map +0 -1
- package/dist/index-rOTW-2QZ.js +0 -4760
- package/dist/index-rOTW-2QZ.js.map +0 -1
- package/dist/index.browser-QPWBkLJA.js.map +0 -1
- package/dist/index.node-WsEJCOwC.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-onFOG2x6.js +0 -94
- package/dist/modelPropertyRecordSchemas-onFOG2x6.js.map +0 -1
- package/dist/property-C85DCD7K.js +0 -289
- package/dist/property-C85DCD7K.js.map +0 -1
- package/dist/propertyValidationRules-CjUPOFeu.js +0 -73
- package/dist/propertyValidationRules-CjUPOFeu.js.map +0 -1
- package/dist/publishDraftGraph-DBWAN72w.js +0 -505
- package/dist/publishDraftGraph-DBWAN72w.js.map +0 -1
- package/dist/resolveModelForSyncedSeed-fIURvYRV.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-BMHoou6X.js.map +0 -1
- package/dist/updateSeedRevokedAt-DpfG6ztE.js +0 -31
- package/dist/updateSeedRevokedAt-DpfG6ztE.js.map +0 -1
package/dist/Schema-BS8oR7QV.js
DELETED
|
@@ -1,4370 +0,0 @@
|
|
|
1
|
-
import { fromCallback, setup, assign, createActor } from 'xstate';
|
|
2
|
-
import { L as BaseDb, ar as SEED_PROTOCOL_SCHEMA_NAME, J as schemas, Q as BaseFileManager, aM as loadModelsFromDbForSchema, aN as mergeDbModelsIntoSchemaModels, aG as isInternalSchema, p as models, z as modelSchemas, aO as addSchemaToDb, a5 as generateId, i as isDataType, M as ModelPropertyDataTypes, aP as addModelsToDb, E as properties, aQ as clearNameMatchLevels } from './index-rOTW-2QZ.js';
|
|
3
|
-
import { SchemaValidationService } from './SchemaValidationService-CZzKNL04.js';
|
|
4
|
-
import { A as listCompleteSchemaFiles, N as getLatestSchemaVersion, O as createRowsWrittenSignal, P as createModelsFromJson, l as loadSchemaFromFile, Q as jsonPropertiesToSchemaProperties, B as loadAllSchemasFromDb, v as getClient } from './ClientManager-DqL0q_eu.js';
|
|
5
|
-
import { eq, desc, and } from 'drizzle-orm';
|
|
6
|
-
import debug from 'debug';
|
|
7
|
-
import { c as currentEvictionEpoch, M as Model, g as getDraftSaveError, C as ConflictError, r as recordSchemaDestroy, f as finishSchemaDestroy, e as setupEntityLiveQuery, t as trackDraftSave } from './errors-CS3nDpUU.js';
|
|
8
|
-
import 'pluralize';
|
|
9
|
-
import '@seedprotocol/eas';
|
|
10
|
-
import { ak as ActorKeyedState, Q as DEFAULT_READY_TIMEOUT_MS, F as waitForEntityIdle, R as createReactiveProxy, T as findEntity, X as ClientManagerState, al as clearDestroySubscriptions, U as forceRemoveFromCaches, am as unloadEntity, V as runDestroyLifecycle } from './Item-CG5IhPtL.js';
|
|
11
|
-
|
|
12
|
-
const validationService = new SchemaValidationService();
|
|
13
|
-
// Validation timeout in milliseconds (10 seconds)
|
|
14
|
-
const VALIDATION_TIMEOUT_MS = 10000;
|
|
15
|
-
const validateSchema = fromCallback(({ sendBack, input: { context } }) => {
|
|
16
|
-
let hasResponded = false;
|
|
17
|
-
// Timeout handler to ensure we always respond
|
|
18
|
-
const timeoutId = setTimeout(() => {
|
|
19
|
-
if (!hasResponded) {
|
|
20
|
-
hasResponded = true;
|
|
21
|
-
sendBack({
|
|
22
|
-
type: 'validationError',
|
|
23
|
-
errors: [{
|
|
24
|
-
field: 'schema',
|
|
25
|
-
message: `Schema validation timed out after ${VALIDATION_TIMEOUT_MS}ms`,
|
|
26
|
-
code: 'validation_timeout',
|
|
27
|
-
severity: 'error',
|
|
28
|
-
}],
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}, VALIDATION_TIMEOUT_MS);
|
|
32
|
-
// Run validation and ensure event is always sent back
|
|
33
|
-
// Use Promise.resolve().then() to ensure async event sending
|
|
34
|
-
// This prevents issues where synchronous validation might not trigger XState properly
|
|
35
|
-
Promise.resolve().then(() => {
|
|
36
|
-
try {
|
|
37
|
-
const result = validationService.validateSchema(context);
|
|
38
|
-
if (!hasResponded) {
|
|
39
|
-
hasResponded = true;
|
|
40
|
-
clearTimeout(timeoutId);
|
|
41
|
-
if (result.isValid) {
|
|
42
|
-
sendBack({ type: 'validationSuccess', errors: [] });
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
sendBack({ type: 'validationError', errors: result.errors });
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
if (!hasResponded) {
|
|
51
|
-
hasResponded = true;
|
|
52
|
-
clearTimeout(timeoutId);
|
|
53
|
-
// Ensure error is always sent back
|
|
54
|
-
sendBack({
|
|
55
|
-
type: 'validationError',
|
|
56
|
-
errors: [{
|
|
57
|
-
field: 'schema',
|
|
58
|
-
message: error instanceof Error ? error.message : 'Unknown validation error',
|
|
59
|
-
code: 'validation_exception',
|
|
60
|
-
severity: 'error',
|
|
61
|
-
}],
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
return () => {
|
|
67
|
-
// Cleanup function - clear timeout if actor is stopped
|
|
68
|
-
clearTimeout(timeoutId);
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const logger$c = debug('seedSdk:schema:actors:checkExistingSchema');
|
|
73
|
-
/**
|
|
74
|
-
* Query model IDs (schemaFileId) from database for a given schema
|
|
75
|
-
*/
|
|
76
|
-
const getModelIdsForSchema = async (schemaId) => {
|
|
77
|
-
const db = BaseDb.getAppDb();
|
|
78
|
-
if (!db) {
|
|
79
|
-
logger$c('Database not available, cannot query model IDs');
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
try {
|
|
83
|
-
const modelRecords = await db
|
|
84
|
-
.select({
|
|
85
|
-
modelFileId: models.schemaFileId,
|
|
86
|
-
})
|
|
87
|
-
.from(modelSchemas)
|
|
88
|
-
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
89
|
-
.where(eq(modelSchemas.schemaId, schemaId));
|
|
90
|
-
const modelIds = modelRecords
|
|
91
|
-
.map((row) => row.modelFileId)
|
|
92
|
-
.filter((id) => id !== null && id !== undefined);
|
|
93
|
-
logger$c(`Found ${modelIds.length} model IDs for schema (id: ${schemaId}): ${modelIds.join(', ')}`);
|
|
94
|
-
return modelIds;
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
logger$c(`Error querying model IDs for schema ${schemaId}:`, error);
|
|
98
|
-
return [];
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* Get the full file path for a schema
|
|
103
|
-
*/
|
|
104
|
-
const getSchemaFilePath$1 = (name, version, schemaFileId) => {
|
|
105
|
-
const path = BaseFileManager.getPathModule();
|
|
106
|
-
const sanitizeSchemaName = (name) => {
|
|
107
|
-
return name
|
|
108
|
-
.replace(/[^a-zA-Z0-9\s_-]/g, '_')
|
|
109
|
-
.replace(/\s+/g, '_')
|
|
110
|
-
.replace(/^_+|_+$/g, '')
|
|
111
|
-
.replace(/_+/g, '_');
|
|
112
|
-
};
|
|
113
|
-
const sanitizedName = sanitizeSchemaName(name);
|
|
114
|
-
const filename = `${schemaFileId}_${sanitizedName}_v${version}.json`;
|
|
115
|
-
const workingDir = BaseFileManager.getWorkingDir();
|
|
116
|
-
return path.join(workingDir, filename);
|
|
117
|
-
};
|
|
118
|
-
/**
|
|
119
|
-
* Create Model instances for all model IDs to ensure they're cached
|
|
120
|
-
*/
|
|
121
|
-
const createModelInstances$1 = async (modelIds, evictionEpoch) => {
|
|
122
|
-
if (modelIds.length === 0) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
const _mod_0 = await import('./errors-CS3nDpUU.js');
|
|
127
|
-
const _ns_0 = _mod_0.i;
|
|
128
|
-
const mod = _ns_0;
|
|
129
|
-
const Model = mod?.Model ?? mod?.default;
|
|
130
|
-
if (!Model) {
|
|
131
|
-
logger$c('Model not available from dynamic import');
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const createPromises = modelIds.map(async (modelFileId) => {
|
|
135
|
-
try {
|
|
136
|
-
// Not if the schema was evicted (Schema.destroy, test cleanup) since this check started
|
|
137
|
-
const schemaHold = Model.takeSchemaHold(modelFileId);
|
|
138
|
-
const model = await Model.createById(modelFileId, { evictionEpoch, ...(!schemaHold && { hold: false }) });
|
|
139
|
-
if (!model && schemaHold)
|
|
140
|
-
Model.dropSchemaHold(modelFileId);
|
|
141
|
-
if (model) {
|
|
142
|
-
logger$c(`Created/cached Model instance for modelFileId "${modelFileId}"`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
logger$c(`Error creating Model instance for modelFileId "${modelFileId}": ${error}`);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
await Promise.all(createPromises);
|
|
150
|
-
logger$c(`Finished creating/caching ${modelIds.length} Model instances`);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
logger$c(`Error in createModelInstances: ${error}`);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const checkExistingSchema = fromCallback(({ sendBack, input: { context } }) => {
|
|
157
|
-
const evictionEpoch = currentEvictionEpoch();
|
|
158
|
-
const _check = async () => {
|
|
159
|
-
const { schemaName } = context;
|
|
160
|
-
if (!schemaName) {
|
|
161
|
-
sendBack({
|
|
162
|
-
type: 'schemaNotFound',
|
|
163
|
-
});
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const db = BaseDb.getAppDb();
|
|
167
|
-
if (!db) {
|
|
168
|
-
sendBack({
|
|
169
|
-
type: 'schemaNotFound',
|
|
170
|
-
});
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
// Check if this is an internal SDK schema (Seed Protocol) — use static import so consumer bundles resolve correctly
|
|
174
|
-
const isInternal = isInternalSchema(schemaName);
|
|
175
|
-
if (isInternal && schemaName === SEED_PROTOCOL_SCHEMA_NAME) {
|
|
176
|
-
// For Seed Protocol, check if it exists in database
|
|
177
|
-
try {
|
|
178
|
-
const _mod_1 = await import('./ClientManager-DqL0q_eu.js');
|
|
179
|
-
const _ns_1 = _mod_1.T;
|
|
180
|
-
const internalSchema = _ns_1;
|
|
181
|
-
const schemaFile = internalSchema.default;
|
|
182
|
-
if (db && schemaFile.id) {
|
|
183
|
-
const existing = await db
|
|
184
|
-
.select()
|
|
185
|
-
.from(schemas)
|
|
186
|
-
.where(eq(schemas.schemaFileId, schemaFile.id))
|
|
187
|
-
.limit(1);
|
|
188
|
-
if (existing.length > 0) {
|
|
189
|
-
const schemaRecord = existing[0];
|
|
190
|
-
let modelIds = [];
|
|
191
|
-
if (schemaRecord.id) {
|
|
192
|
-
modelIds = await getModelIdsForSchema(schemaRecord.id);
|
|
193
|
-
await createModelInstances$1(modelIds, evictionEpoch);
|
|
194
|
-
}
|
|
195
|
-
sendBack({
|
|
196
|
-
type: 'schemaFound',
|
|
197
|
-
schema: schemaFile,
|
|
198
|
-
schemaRecord,
|
|
199
|
-
modelIds,
|
|
200
|
-
});
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
logger$c(`Error checking internal Seed Protocol schema: ${error}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
// STEP 1: Query database first for existing schema (prefer drafts)
|
|
210
|
-
let dbSchemas = await db
|
|
211
|
-
.select()
|
|
212
|
-
.from(schemas)
|
|
213
|
-
.where(eq(schemas.name, schemaName))
|
|
214
|
-
.orderBy(desc(schemas.isDraft), desc(schemas.version))
|
|
215
|
-
.limit(1);
|
|
216
|
-
// If not found by name, try querying by schemaFileId
|
|
217
|
-
if (dbSchemas.length === 0) {
|
|
218
|
-
logger$c(`No schema found by name "${schemaName}", trying to find by schemaFileId`);
|
|
219
|
-
const schemasByFileId = await db
|
|
220
|
-
.select()
|
|
221
|
-
.from(schemas)
|
|
222
|
-
.where(eq(schemas.schemaFileId, schemaName))
|
|
223
|
-
.orderBy(desc(schemas.isDraft), desc(schemas.version))
|
|
224
|
-
.limit(1);
|
|
225
|
-
if (schemasByFileId.length > 0) {
|
|
226
|
-
logger$c(`Found schema by schemaFileId "${schemaName}" (name in DB: ${schemasByFileId[0].name})`);
|
|
227
|
-
dbSchemas = schemasByFileId;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// Try to find by schemaFileId from file
|
|
231
|
-
const shouldTrySchemaFileId = dbSchemas.length === 0 || (dbSchemas.length > 0 && dbSchemas[0].isDraft === false);
|
|
232
|
-
if (shouldTrySchemaFileId) {
|
|
233
|
-
const completeSchemas = await listCompleteSchemaFiles();
|
|
234
|
-
const matchingFileSchemas = completeSchemas.filter((s) => s.name === schemaName);
|
|
235
|
-
if (matchingFileSchemas.length > 0) {
|
|
236
|
-
const latestFile = matchingFileSchemas.reduce((prev, current) => current.version > prev.version ? current : prev);
|
|
237
|
-
try {
|
|
238
|
-
const content = await BaseFileManager.readFileAsString(latestFile.filePath);
|
|
239
|
-
const fileSchema = JSON.parse(content);
|
|
240
|
-
if (fileSchema.id) {
|
|
241
|
-
const draftsByFileId = await db
|
|
242
|
-
.select()
|
|
243
|
-
.from(schemas)
|
|
244
|
-
.where(and(eq(schemas.schemaFileId, fileSchema.id), eq(schemas.isDraft, true)))
|
|
245
|
-
.orderBy(desc(schemas.version))
|
|
246
|
-
.limit(1);
|
|
247
|
-
if (draftsByFileId.length > 0) {
|
|
248
|
-
logger$c(`Found DRAFT by schemaFileId ${fileSchema.id}`);
|
|
249
|
-
dbSchemas = draftsByFileId;
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
const dbSchemasByFileId = await db
|
|
253
|
-
.select()
|
|
254
|
-
.from(schemas)
|
|
255
|
-
.where(eq(schemas.schemaFileId, fileSchema.id))
|
|
256
|
-
.orderBy(desc(schemas.isDraft), desc(schemas.version))
|
|
257
|
-
.limit(1);
|
|
258
|
-
if (dbSchemasByFileId.length > 0) {
|
|
259
|
-
const foundSchema = dbSchemasByFileId[0];
|
|
260
|
-
if (foundSchema.isDraft === true || dbSchemas.length === 0) {
|
|
261
|
-
dbSchemas = dbSchemasByFileId;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
logger$c(`Error reading file to get schemaFileId: ${error}`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
// Check all drafts for normalized name match
|
|
272
|
-
if (dbSchemas.length === 0) {
|
|
273
|
-
const allDrafts = await db
|
|
274
|
-
.select()
|
|
275
|
-
.from(schemas)
|
|
276
|
-
.where(eq(schemas.isDraft, true))
|
|
277
|
-
.orderBy(desc(schemas.version));
|
|
278
|
-
const normalizeName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
279
|
-
const normalizedRequested = normalizeName(schemaName);
|
|
280
|
-
for (const draft of allDrafts) {
|
|
281
|
-
if (draft.schemaData) {
|
|
282
|
-
try {
|
|
283
|
-
const draftSchema = JSON.parse(draft.schemaData);
|
|
284
|
-
const draftMetadataName = draftSchema.metadata?.name || '';
|
|
285
|
-
const draftDbName = draft.name || '';
|
|
286
|
-
const normalizedDraftMetadata = normalizeName(draftMetadataName);
|
|
287
|
-
const normalizedDraftDb = normalizeName(draftDbName);
|
|
288
|
-
if (normalizedDraftMetadata === normalizedRequested || normalizedDraftDb === normalizedRequested) {
|
|
289
|
-
dbSchemas = [draft];
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
// If schema found in database, return it
|
|
301
|
-
if (dbSchemas.length > 0) {
|
|
302
|
-
const dbSchema = dbSchemas[0];
|
|
303
|
-
logger$c(`Found schema record: name="${dbSchema.name}", isDraft=${dbSchema.isDraft}`);
|
|
304
|
-
// Load schema from schemaData or file
|
|
305
|
-
let schemaFile = null;
|
|
306
|
-
if (dbSchema.isDraft === true && dbSchema.schemaData) {
|
|
307
|
-
try {
|
|
308
|
-
schemaFile = JSON.parse(dbSchema.schemaData);
|
|
309
|
-
// Ensure schema.id matches schemaFileId from database
|
|
310
|
-
if (dbSchema.schemaFileId && schemaFile.id !== dbSchema.schemaFileId) {
|
|
311
|
-
schemaFile.id = dbSchema.schemaFileId;
|
|
312
|
-
}
|
|
313
|
-
else if (!schemaFile.id && dbSchema.schemaFileId) {
|
|
314
|
-
schemaFile.id = dbSchema.schemaFileId;
|
|
315
|
-
}
|
|
316
|
-
// Merge models from database
|
|
317
|
-
if (dbSchema.id) {
|
|
318
|
-
const dbModels = await loadModelsFromDbForSchema(dbSchema.id);
|
|
319
|
-
if (Object.keys(dbModels).length > 0) {
|
|
320
|
-
schemaFile.models = mergeDbModelsIntoSchemaModels(schemaFile.models, dbModels);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
catch (error) {
|
|
325
|
-
logger$c(`Error parsing schemaData: ${error}`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
else if (dbSchema.schemaFileId) {
|
|
329
|
-
// Try to load from file
|
|
330
|
-
try {
|
|
331
|
-
const completeSchemas = await listCompleteSchemaFiles();
|
|
332
|
-
// Match version too: the row is per schema name, so after a new version is imported
|
|
333
|
-
// under a new id it can carry the newer version with the older version's schemaFileId.
|
|
334
|
-
const idBasedMatch = completeSchemas.find((s) => s.schemaFileId === dbSchema.schemaFileId && s.version === dbSchema.version);
|
|
335
|
-
if (idBasedMatch) {
|
|
336
|
-
const content = await BaseFileManager.readFileAsString(idBasedMatch.filePath);
|
|
337
|
-
schemaFile = JSON.parse(content);
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
const filePath = getSchemaFilePath$1(dbSchema.name, dbSchema.version, dbSchema.schemaFileId);
|
|
341
|
-
try {
|
|
342
|
-
const content = await BaseFileManager.readFileAsString(filePath);
|
|
343
|
-
schemaFile = JSON.parse(content);
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
346
|
-
// File doesn't exist
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
// Ensure schema.id matches schemaFileId from database (database is source of truth)
|
|
350
|
-
if (schemaFile && dbSchema.schemaFileId) {
|
|
351
|
-
if (schemaFile.id !== dbSchema.schemaFileId) {
|
|
352
|
-
logger$c(`Fixing schema ID mismatch: file has id="${schemaFile.id}", DB has schemaFileId="${dbSchema.schemaFileId}". Using DB value.`);
|
|
353
|
-
schemaFile.id = dbSchema.schemaFileId;
|
|
354
|
-
}
|
|
355
|
-
else if (!schemaFile.id && dbSchema.schemaFileId) {
|
|
356
|
-
logger$c(`Schema missing id, using schemaFileId from database: "${dbSchema.schemaFileId}"`);
|
|
357
|
-
schemaFile.id = dbSchema.schemaFileId;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
if (schemaFile && dbSchema.id) {
|
|
361
|
-
const dbModels = await loadModelsFromDbForSchema(dbSchema.id);
|
|
362
|
-
if (Object.keys(dbModels).length > 0) {
|
|
363
|
-
schemaFile.models = mergeDbModelsIntoSchemaModels(schemaFile.models, dbModels);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
catch (error) {
|
|
368
|
-
logger$c(`Error loading from file: ${error}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (schemaFile) {
|
|
372
|
-
// Query model IDs and create instances
|
|
373
|
-
let modelIds = [];
|
|
374
|
-
if (dbSchema.id) {
|
|
375
|
-
modelIds = await getModelIdsForSchema(dbSchema.id);
|
|
376
|
-
await createModelInstances$1(modelIds, evictionEpoch);
|
|
377
|
-
}
|
|
378
|
-
const loadedAt = Date.now();
|
|
379
|
-
const dbVersion = dbSchema.version || schemaFile.version;
|
|
380
|
-
const dbUpdatedAt = dbSchema.updatedAt || loadedAt;
|
|
381
|
-
sendBack({
|
|
382
|
-
type: 'schemaFound',
|
|
383
|
-
schema: schemaFile,
|
|
384
|
-
schemaRecord: dbSchema,
|
|
385
|
-
modelIds,
|
|
386
|
-
loadedAt,
|
|
387
|
-
dbVersion,
|
|
388
|
-
dbUpdatedAt,
|
|
389
|
-
});
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
// Check for existing schema files (backward compatibility)
|
|
394
|
-
const completeSchemas = await listCompleteSchemaFiles();
|
|
395
|
-
const matchingSchemas = completeSchemas.filter((s) => s.name === schemaName);
|
|
396
|
-
if (matchingSchemas.length > 0) {
|
|
397
|
-
// Check for matching draft in database
|
|
398
|
-
const allDrafts = await db
|
|
399
|
-
.select()
|
|
400
|
-
.from(schemas)
|
|
401
|
-
.where(eq(schemas.isDraft, true))
|
|
402
|
-
.orderBy(desc(schemas.version));
|
|
403
|
-
const normalizeName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
404
|
-
const normalizedRequested = normalizeName(schemaName);
|
|
405
|
-
let matchingDraft = null;
|
|
406
|
-
for (const draft of allDrafts) {
|
|
407
|
-
if (draft.schemaData) {
|
|
408
|
-
try {
|
|
409
|
-
const draftSchema = JSON.parse(draft.schemaData);
|
|
410
|
-
const draftMetadataName = draftSchema.metadata?.name || '';
|
|
411
|
-
const draftDbName = draft.name || '';
|
|
412
|
-
const normalizedDraftMetadata = normalizeName(draftMetadataName);
|
|
413
|
-
const normalizedDraftDb = normalizeName(draftDbName);
|
|
414
|
-
if (normalizedDraftMetadata === normalizedRequested || normalizedDraftDb === normalizedRequested) {
|
|
415
|
-
matchingDraft = draft;
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
catch {
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
if (matchingDraft && matchingDraft.schemaData) {
|
|
425
|
-
try {
|
|
426
|
-
const schemaFile = JSON.parse(matchingDraft.schemaData);
|
|
427
|
-
let modelIds = [];
|
|
428
|
-
if (matchingDraft.id) {
|
|
429
|
-
modelIds = await getModelIdsForSchema(matchingDraft.id);
|
|
430
|
-
await createModelInstances$1(modelIds, evictionEpoch);
|
|
431
|
-
}
|
|
432
|
-
const loadedAt = Date.now();
|
|
433
|
-
const dbVersion = matchingDraft.version || schemaFile.version;
|
|
434
|
-
const dbUpdatedAt = matchingDraft.updatedAt || loadedAt;
|
|
435
|
-
sendBack({
|
|
436
|
-
type: 'schemaFound',
|
|
437
|
-
schema: schemaFile,
|
|
438
|
-
schemaRecord: matchingDraft,
|
|
439
|
-
modelIds,
|
|
440
|
-
loadedAt,
|
|
441
|
-
dbVersion,
|
|
442
|
-
dbUpdatedAt,
|
|
443
|
-
});
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
catch (error) {
|
|
447
|
-
logger$c(`Error parsing draft schemaData: ${error}`);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
// Load from file
|
|
451
|
-
const latest = matchingSchemas.reduce((prev, current) => current.version > prev.version ? current : prev);
|
|
452
|
-
const content = await BaseFileManager.readFileAsString(latest.filePath);
|
|
453
|
-
const schemaFile = JSON.parse(content);
|
|
454
|
-
// Query model IDs if schema exists in DB
|
|
455
|
-
let modelIds = [];
|
|
456
|
-
try {
|
|
457
|
-
if (db && schemaFile.id) {
|
|
458
|
-
const schemaRecords = await db
|
|
459
|
-
.select()
|
|
460
|
-
.from(schemas)
|
|
461
|
-
.where(eq(schemas.schemaFileId, schemaFile.id))
|
|
462
|
-
.limit(1);
|
|
463
|
-
if (schemaRecords.length > 0 && schemaRecords[0].id) {
|
|
464
|
-
modelIds = await getModelIdsForSchema(schemaRecords[0].id);
|
|
465
|
-
await createModelInstances$1(modelIds, evictionEpoch);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
catch (error) {
|
|
470
|
-
logger$c(`Error querying model IDs for schema from file: ${error}`);
|
|
471
|
-
}
|
|
472
|
-
const loadedAt = Date.now();
|
|
473
|
-
const dbVersion = schemaFile.version;
|
|
474
|
-
const dbUpdatedAt = new Date(schemaFile.metadata.updatedAt).getTime() || loadedAt;
|
|
475
|
-
sendBack({
|
|
476
|
-
type: 'schemaFound',
|
|
477
|
-
schema: schemaFile,
|
|
478
|
-
schemaRecord: null,
|
|
479
|
-
modelIds,
|
|
480
|
-
loadedAt,
|
|
481
|
-
dbVersion,
|
|
482
|
-
dbUpdatedAt,
|
|
483
|
-
});
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
// No schema found
|
|
487
|
-
sendBack({
|
|
488
|
-
type: 'schemaNotFound',
|
|
489
|
-
});
|
|
490
|
-
};
|
|
491
|
-
_check().catch((error) => {
|
|
492
|
-
logger$c('Error in checkExistingSchema:', error);
|
|
493
|
-
sendBack({
|
|
494
|
-
type: 'schemaNotFound',
|
|
495
|
-
});
|
|
496
|
-
});
|
|
497
|
-
return () => {
|
|
498
|
-
// Cleanup function (optional)
|
|
499
|
-
};
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
const logger$b = debug('seedSdk:schema:actors:writeSchemaToDb');
|
|
503
|
-
const writeSchemaToDb = fromCallback(({ sendBack, input }) => {
|
|
504
|
-
const _write = async () => {
|
|
505
|
-
const { schemaName, schemaFile, existingDbSchema } = input;
|
|
506
|
-
try {
|
|
507
|
-
// Check if this is an internal SDK schema (Seed Protocol) — use static import so consumer bundles resolve correctly
|
|
508
|
-
const isInternal = isInternalSchema(schemaName);
|
|
509
|
-
let finalSchema;
|
|
510
|
-
let schemaRecord;
|
|
511
|
-
if (isInternal && schemaName === SEED_PROTOCOL_SCHEMA_NAME) {
|
|
512
|
-
// For Seed Protocol, load from internal file
|
|
513
|
-
logger$b(`Loading internal Seed Protocol schema from SDK`);
|
|
514
|
-
const _mod_2 = await import('./ClientManager-DqL0q_eu.js');
|
|
515
|
-
const _ns_2 = _mod_2.T;
|
|
516
|
-
const internalSchema = _ns_2;
|
|
517
|
-
finalSchema = internalSchema.default;
|
|
518
|
-
const db = BaseDb.getAppDb();
|
|
519
|
-
if (db && finalSchema.id) {
|
|
520
|
-
// Check if it exists in database
|
|
521
|
-
const existing = await db
|
|
522
|
-
.select()
|
|
523
|
-
.from(schemas)
|
|
524
|
-
.where(eq(schemas.schemaFileId, finalSchema.id))
|
|
525
|
-
.limit(1);
|
|
526
|
-
if (existing.length === 0) {
|
|
527
|
-
// Add to database if not present
|
|
528
|
-
const schemaData = JSON.stringify(finalSchema, null, 2);
|
|
529
|
-
schemaRecord = await addSchemaToDb({
|
|
530
|
-
name: schemaName,
|
|
531
|
-
version: finalSchema.version,
|
|
532
|
-
createdAt: new Date(finalSchema.metadata.createdAt).getTime(),
|
|
533
|
-
updatedAt: new Date(finalSchema.metadata.updatedAt).getTime(),
|
|
534
|
-
}, finalSchema.id, schemaData, false, // isDraft = false (it's a published internal schema)
|
|
535
|
-
false);
|
|
536
|
-
logger$b(`Added Seed Protocol schema to database`);
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
schemaRecord = existing[0];
|
|
540
|
-
logger$b(`Seed Protocol schema already exists in database`);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
throw new Error('Database not available for Seed Protocol schema');
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
else if (schemaFile) {
|
|
548
|
-
// Use provided schema file
|
|
549
|
-
finalSchema = schemaFile;
|
|
550
|
-
// Ensure schema has an ID
|
|
551
|
-
if (!finalSchema.id) {
|
|
552
|
-
finalSchema.id = generateId();
|
|
553
|
-
logger$b(`Generated schema ID for schema: ${finalSchema.id}`);
|
|
554
|
-
}
|
|
555
|
-
const schemaData = JSON.stringify(finalSchema, null, 2);
|
|
556
|
-
schemaRecord = await addSchemaToDb({
|
|
557
|
-
name: schemaName,
|
|
558
|
-
version: finalSchema.version,
|
|
559
|
-
createdAt: new Date(finalSchema.metadata.createdAt).getTime(),
|
|
560
|
-
updatedAt: new Date(finalSchema.metadata.updatedAt).getTime(),
|
|
561
|
-
}, finalSchema.id, schemaData, true, // isDraft = true for new schemas
|
|
562
|
-
true);
|
|
563
|
-
logger$b(`Created schema ${schemaName} v${finalSchema.version} in database`);
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
// Create new schema
|
|
567
|
-
const latestVersion = existingDbSchema?.version
|
|
568
|
-
? existingDbSchema.version + 1
|
|
569
|
-
: await getLatestSchemaVersion(schemaName);
|
|
570
|
-
const newVersion = latestVersion + 1;
|
|
571
|
-
const now = new Date().toISOString();
|
|
572
|
-
finalSchema = {
|
|
573
|
-
$schema: 'https://seedprotocol.org/schemas/data-model/v1',
|
|
574
|
-
version: newVersion,
|
|
575
|
-
id: generateId(),
|
|
576
|
-
metadata: {
|
|
577
|
-
name: schemaName,
|
|
578
|
-
createdAt: now,
|
|
579
|
-
updatedAt: now,
|
|
580
|
-
},
|
|
581
|
-
models: {},
|
|
582
|
-
enums: {},
|
|
583
|
-
migrations: [
|
|
584
|
-
{
|
|
585
|
-
version: newVersion,
|
|
586
|
-
timestamp: now,
|
|
587
|
-
description: 'Initial schema',
|
|
588
|
-
changes: [],
|
|
589
|
-
},
|
|
590
|
-
],
|
|
591
|
-
};
|
|
592
|
-
// Save to database as draft
|
|
593
|
-
const schemaData = JSON.stringify(finalSchema, null, 2);
|
|
594
|
-
schemaRecord = await addSchemaToDb({
|
|
595
|
-
name: schemaName,
|
|
596
|
-
version: newVersion,
|
|
597
|
-
createdAt: new Date(now).getTime(),
|
|
598
|
-
updatedAt: new Date(now).getTime(),
|
|
599
|
-
}, finalSchema.id, schemaData, true, // isDraft = true
|
|
600
|
-
true);
|
|
601
|
-
logger$b(`Created new draft schema ${schemaName} v${newVersion} in database`);
|
|
602
|
-
}
|
|
603
|
-
sendBack({
|
|
604
|
-
type: 'schemaWritten',
|
|
605
|
-
schemaRecord,
|
|
606
|
-
schema: finalSchema,
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
catch (error) {
|
|
610
|
-
logger$b(`Error writing schema to database: ${error}`);
|
|
611
|
-
sendBack({
|
|
612
|
-
type: 'writeError',
|
|
613
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
};
|
|
617
|
-
_write().catch((error) => {
|
|
618
|
-
logger$b('Error in writeSchemaToDb:', error);
|
|
619
|
-
sendBack({
|
|
620
|
-
type: 'writeError',
|
|
621
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
return () => {
|
|
625
|
-
// Cleanup function (optional)
|
|
626
|
-
};
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
const logger$a = debug('seedSdk:schema:actors:verifySchemaInDb');
|
|
630
|
-
/**
|
|
631
|
-
* Verify that a schema record exists in the database with retry logic
|
|
632
|
-
* Uses exponential backoff: 100ms, 200ms, 400ms, 800ms, 1600ms
|
|
633
|
-
* Total timeout: ~3.1 seconds
|
|
634
|
-
*/
|
|
635
|
-
async function verifyWithRetry$4(verifyFn, maxRetries = 5, initialDelay = 100) {
|
|
636
|
-
let lastError = null;
|
|
637
|
-
let delay = initialDelay;
|
|
638
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
639
|
-
try {
|
|
640
|
-
return await verifyFn();
|
|
641
|
-
}
|
|
642
|
-
catch (error) {
|
|
643
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
644
|
-
if (attempt < maxRetries - 1) {
|
|
645
|
-
logger$a(`Verification attempt ${attempt + 1} failed, retrying in ${delay}ms: ${lastError.message}`);
|
|
646
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
647
|
-
delay *= 2; // Exponential backoff
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
throw lastError || new Error('Verification failed after retries');
|
|
652
|
-
}
|
|
653
|
-
const verifySchemaInDb = fromCallback(({ sendBack, input }) => {
|
|
654
|
-
const _verify = async () => {
|
|
655
|
-
const { schemaFileId, expectedSchemaId } = input;
|
|
656
|
-
try {
|
|
657
|
-
const result = await verifyWithRetry$4(async () => {
|
|
658
|
-
const db = BaseDb.getAppDb();
|
|
659
|
-
if (!db) {
|
|
660
|
-
throw new Error('Database not available');
|
|
661
|
-
}
|
|
662
|
-
// Query for schema by schemaFileId
|
|
663
|
-
const schemaRecords = await db
|
|
664
|
-
.select()
|
|
665
|
-
.from(schemas)
|
|
666
|
-
.where(eq(schemas.schemaFileId, schemaFileId))
|
|
667
|
-
.limit(1);
|
|
668
|
-
if (schemaRecords.length === 0) {
|
|
669
|
-
throw new Error(`Schema with schemaFileId "${schemaFileId}" not found in database`);
|
|
670
|
-
}
|
|
671
|
-
const schemaRecord = schemaRecords[0];
|
|
672
|
-
// If expectedSchemaId is provided, verify it matches
|
|
673
|
-
if (expectedSchemaId !== undefined && schemaRecord.id !== expectedSchemaId) {
|
|
674
|
-
throw new Error(`Schema ID mismatch: expected ${expectedSchemaId}, found ${schemaRecord.id}`);
|
|
675
|
-
}
|
|
676
|
-
logger$a(`Schema verified: schemaFileId="${schemaFileId}", schemaId=${schemaRecord.id}`);
|
|
677
|
-
return schemaRecord.id;
|
|
678
|
-
});
|
|
679
|
-
sendBack({
|
|
680
|
-
type: 'schemaVerified',
|
|
681
|
-
schemaId: result,
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
catch (error) {
|
|
685
|
-
logger$a(`Schema verification failed after retries: ${error}`);
|
|
686
|
-
sendBack({
|
|
687
|
-
type: 'verificationFailed',
|
|
688
|
-
stage: 'verifySchema',
|
|
689
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
_verify().catch((error) => {
|
|
694
|
-
logger$a('Error in verifySchemaInDb:', error);
|
|
695
|
-
sendBack({
|
|
696
|
-
type: 'verificationFailed',
|
|
697
|
-
stage: 'verifySchema',
|
|
698
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
699
|
-
});
|
|
700
|
-
});
|
|
701
|
-
return () => {
|
|
702
|
-
// Cleanup function (optional)
|
|
703
|
-
};
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
const logger$9 = debug('seedSdk:schema:actors:writeModelsToDb');
|
|
707
|
-
const writeModelsToDb = fromCallback(({ sendBack, input }) => {
|
|
708
|
-
const _write = async () => {
|
|
709
|
-
const { schema, schemaRecord, schemaName } = input;
|
|
710
|
-
try {
|
|
711
|
-
// Check if models already exist in database
|
|
712
|
-
const db = BaseDb.getAppDb();
|
|
713
|
-
if (!db) {
|
|
714
|
-
throw new Error('Database not available');
|
|
715
|
-
}
|
|
716
|
-
// Check if models are linked to the schema
|
|
717
|
-
const modelLinks = await db
|
|
718
|
-
.select({
|
|
719
|
-
modelId: modelSchemas.modelId,
|
|
720
|
-
modelName: models.name,
|
|
721
|
-
})
|
|
722
|
-
.from(modelSchemas)
|
|
723
|
-
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
724
|
-
.where(eq(modelSchemas.schemaId, schemaRecord.id));
|
|
725
|
-
// Check if we have all expected models
|
|
726
|
-
const expectedModelNames = Object.keys(schema.models || {});
|
|
727
|
-
const linkedModelNames = modelLinks
|
|
728
|
-
.map((link) => link.modelName)
|
|
729
|
-
.filter((n) => n !== null);
|
|
730
|
-
const missingModels = expectedModelNames.filter(name => !linkedModelNames.includes(name));
|
|
731
|
-
// If all models exist, skip writing
|
|
732
|
-
if (missingModels.length === 0 && modelLinks.length > 0) {
|
|
733
|
-
logger$9(`All models already exist in database for schema ${schemaName}`);
|
|
734
|
-
// Extract model IDs from database
|
|
735
|
-
const modelFileIds = [];
|
|
736
|
-
for (const link of modelLinks) {
|
|
737
|
-
if (link.modelId) {
|
|
738
|
-
const modelRecord = await db
|
|
739
|
-
.select({ schemaFileId: models.schemaFileId })
|
|
740
|
-
.from(models)
|
|
741
|
-
.where(eq(models.id, link.modelId))
|
|
742
|
-
.limit(1);
|
|
743
|
-
if (modelRecord.length > 0 && modelRecord[0].schemaFileId) {
|
|
744
|
-
modelFileIds.push(modelRecord[0].schemaFileId);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
sendBack({
|
|
749
|
-
type: 'modelsWritten',
|
|
750
|
-
modelIds: modelFileIds,
|
|
751
|
-
});
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
// Check if this is Seed Protocol schema (has different format) — use static import so consumer bundles resolve correctly
|
|
755
|
-
const isInternal = isInternalSchema(schemaName);
|
|
756
|
-
let importData;
|
|
757
|
-
if (isInternal && schemaName === SEED_PROTOCOL_SCHEMA_NAME) {
|
|
758
|
-
// Convert Seed Protocol schema format to JSON import format
|
|
759
|
-
importData = {
|
|
760
|
-
name: schemaName,
|
|
761
|
-
models: Object.fromEntries(Object.entries(schema.models || {}).map(([modelName, model]) => [
|
|
762
|
-
modelName,
|
|
763
|
-
{
|
|
764
|
-
...model,
|
|
765
|
-
id: undefined,
|
|
766
|
-
properties: Object.fromEntries(Object.entries(model.properties || {}).map(([propName, prop]) => {
|
|
767
|
-
const schemaProp = prop;
|
|
768
|
-
const jsonProp = {
|
|
769
|
-
type: schemaProp.dataType || schemaProp.type,
|
|
770
|
-
};
|
|
771
|
-
// Copy other properties
|
|
772
|
-
Object.keys(schemaProp).forEach(key => {
|
|
773
|
-
if (key !== 'id' && key !== 'dataType') {
|
|
774
|
-
jsonProp[key] = schemaProp[key];
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
// Handle Relation type
|
|
778
|
-
if (schemaProp.ref || schemaProp.refModelName) {
|
|
779
|
-
jsonProp.model = schemaProp.refModelName || schemaProp.ref;
|
|
780
|
-
}
|
|
781
|
-
// Handle List type
|
|
782
|
-
if (isDataType(schemaProp.dataType, ModelPropertyDataTypes.List) && schemaProp.refValueType) {
|
|
783
|
-
jsonProp.refValueType = schemaProp.refValueType;
|
|
784
|
-
if (schemaProp.ref || schemaProp.refModelName) {
|
|
785
|
-
jsonProp.ref = schemaProp.refModelName || schemaProp.ref;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
// Handle storage configuration
|
|
789
|
-
if (schemaProp.storageType || schemaProp.localStorageDir || schemaProp.filenameSuffix) {
|
|
790
|
-
jsonProp.storage = {
|
|
791
|
-
type: schemaProp.storageType === 'ItemStorage' ? 'ItemStorage' : 'PropertyStorage',
|
|
792
|
-
path: schemaProp.localStorageDir,
|
|
793
|
-
extension: schemaProp.filenameSuffix,
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
return [propName, jsonProp];
|
|
797
|
-
})),
|
|
798
|
-
},
|
|
799
|
-
])),
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
else {
|
|
803
|
-
// Standard schema format
|
|
804
|
-
importData = {
|
|
805
|
-
name: schemaName,
|
|
806
|
-
models: Object.fromEntries(Object.entries(schema.models || {}).map(([modelName, model]) => [
|
|
807
|
-
modelName,
|
|
808
|
-
{
|
|
809
|
-
...model,
|
|
810
|
-
id: undefined,
|
|
811
|
-
properties: model.properties || {},
|
|
812
|
-
},
|
|
813
|
-
])),
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
// Generate schemaFileIds from JSON file and generate missing ones BEFORE creating models
|
|
817
|
-
const modelFileIds = new Map();
|
|
818
|
-
const propertyFileIds = new Map();
|
|
819
|
-
for (const [modelName, model] of Object.entries(schema.models || {})) {
|
|
820
|
-
// Generate model ID if missing
|
|
821
|
-
if (!model.id) {
|
|
822
|
-
model.id = generateId();
|
|
823
|
-
logger$9(`Generated model ID for ${modelName}: ${model.id}`);
|
|
824
|
-
}
|
|
825
|
-
modelFileIds.set(modelName, model.id);
|
|
826
|
-
const propIds = new Map();
|
|
827
|
-
for (const [propName, prop] of Object.entries(model.properties || {})) {
|
|
828
|
-
// Generate property ID if missing
|
|
829
|
-
if (!prop.id) {
|
|
830
|
-
prop.id = generateId();
|
|
831
|
-
logger$9(`Generated property ID for ${modelName}.${propName}: ${prop.id}`);
|
|
832
|
-
}
|
|
833
|
-
propIds.set(propName, prop.id);
|
|
834
|
-
}
|
|
835
|
-
if (propIds.size > 0) {
|
|
836
|
-
propertyFileIds.set(modelName, propIds);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
// Convert JSON models to Model classes. addModelsToDb below writes their rows; the models adopt them
|
|
840
|
-
// once it has run.
|
|
841
|
-
const rowsWritten = createRowsWrittenSignal();
|
|
842
|
-
const modelDefinitions = await createModelsFromJson(importData, modelFileIds, propertyFileIds, rowsWritten.promise);
|
|
843
|
-
// Add models to database
|
|
844
|
-
if (Object.keys(modelDefinitions).length > 0) {
|
|
845
|
-
await addModelsToDb(modelDefinitions, schemaRecord, undefined, {
|
|
846
|
-
schemaFileId: schema.id,
|
|
847
|
-
modelFileIds,
|
|
848
|
-
propertyFileIds,
|
|
849
|
-
}).finally(rowsWritten.settle);
|
|
850
|
-
logger$9(`Added ${Object.keys(modelDefinitions).length} models and their properties to database`);
|
|
851
|
-
}
|
|
852
|
-
// Extract model IDs that were written
|
|
853
|
-
const writtenModelIds = Array.from(modelFileIds.values());
|
|
854
|
-
sendBack({
|
|
855
|
-
type: 'modelsWritten',
|
|
856
|
-
modelIds: writtenModelIds,
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
catch (error) {
|
|
860
|
-
logger$9(`Error writing models to database: ${error}`);
|
|
861
|
-
sendBack({
|
|
862
|
-
type: 'writeError',
|
|
863
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
};
|
|
867
|
-
_write().catch((error) => {
|
|
868
|
-
logger$9('Error in writeModelsToDb:', error);
|
|
869
|
-
sendBack({
|
|
870
|
-
type: 'writeError',
|
|
871
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
872
|
-
});
|
|
873
|
-
});
|
|
874
|
-
return () => {
|
|
875
|
-
// Cleanup function (optional)
|
|
876
|
-
};
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
const logger$8 = debug('seedSdk:schema:actors:verifyModelsInDb');
|
|
880
|
-
/**
|
|
881
|
-
* Verify that model records exist in the database for a schema with retry logic
|
|
882
|
-
*/
|
|
883
|
-
async function verifyWithRetry$3(verifyFn, maxRetries = 5, initialDelay = 100) {
|
|
884
|
-
let lastError = null;
|
|
885
|
-
let delay = initialDelay;
|
|
886
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
887
|
-
try {
|
|
888
|
-
return await verifyFn();
|
|
889
|
-
}
|
|
890
|
-
catch (error) {
|
|
891
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
892
|
-
if (attempt < maxRetries - 1) {
|
|
893
|
-
logger$8(`Verification attempt ${attempt + 1} failed, retrying in ${delay}ms: ${lastError.message}`);
|
|
894
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
895
|
-
delay *= 2; // Exponential backoff
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
throw lastError || new Error('Verification failed after retries');
|
|
900
|
-
}
|
|
901
|
-
const verifyModelsInDb = fromCallback(({ sendBack, input }) => {
|
|
902
|
-
const _verify = async () => {
|
|
903
|
-
const { schemaId, expectedModelIds } = input;
|
|
904
|
-
try {
|
|
905
|
-
const result = await verifyWithRetry$3(async () => {
|
|
906
|
-
const db = BaseDb.getAppDb();
|
|
907
|
-
if (!db) {
|
|
908
|
-
throw new Error('Database not available');
|
|
909
|
-
}
|
|
910
|
-
// Query for models linked to this schema
|
|
911
|
-
const modelRecords = await db
|
|
912
|
-
.select({
|
|
913
|
-
modelFileId: models.schemaFileId,
|
|
914
|
-
modelId: models.id,
|
|
915
|
-
})
|
|
916
|
-
.from(modelSchemas)
|
|
917
|
-
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
918
|
-
.where(eq(modelSchemas.schemaId, schemaId));
|
|
919
|
-
// If expectedModelIds is provided and empty, it means the schema should have no models
|
|
920
|
-
// This is valid for empty schemas
|
|
921
|
-
if (modelRecords.length === 0) {
|
|
922
|
-
if (expectedModelIds !== undefined && expectedModelIds.length === 0) {
|
|
923
|
-
// Empty schema is expected and valid
|
|
924
|
-
logger$8(`Schema ${schemaId} has no models (as expected)`);
|
|
925
|
-
return [];
|
|
926
|
-
}
|
|
927
|
-
throw new Error(`No models found for schema (id: ${schemaId})`);
|
|
928
|
-
}
|
|
929
|
-
const modelIds = modelRecords
|
|
930
|
-
.map((row) => row.modelFileId)
|
|
931
|
-
.filter((id) => id !== null && id !== undefined);
|
|
932
|
-
// If expectedModelIds provided, verify all are present
|
|
933
|
-
if (expectedModelIds && expectedModelIds.length > 0) {
|
|
934
|
-
const missingIds = expectedModelIds.filter(id => !modelIds.includes(id));
|
|
935
|
-
if (missingIds.length > 0) {
|
|
936
|
-
throw new Error(`Missing expected model IDs: ${missingIds.join(', ')}. Found: ${modelIds.join(', ')}`);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
logger$8(`Models verified: schemaId=${schemaId}, found ${modelIds.length} models: ${modelIds.join(', ')}`);
|
|
940
|
-
return modelIds;
|
|
941
|
-
});
|
|
942
|
-
sendBack({
|
|
943
|
-
type: 'modelsVerified',
|
|
944
|
-
modelIds: result,
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
catch (error) {
|
|
948
|
-
logger$8(`Model verification failed after retries: ${error}`);
|
|
949
|
-
sendBack({
|
|
950
|
-
type: 'verificationFailed',
|
|
951
|
-
stage: 'verifyModels',
|
|
952
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
953
|
-
});
|
|
954
|
-
}
|
|
955
|
-
};
|
|
956
|
-
_verify().catch((error) => {
|
|
957
|
-
logger$8('Error in verifyModelsInDb:', error);
|
|
958
|
-
sendBack({
|
|
959
|
-
type: 'verificationFailed',
|
|
960
|
-
stage: 'verifyModels',
|
|
961
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
962
|
-
});
|
|
963
|
-
});
|
|
964
|
-
return () => {
|
|
965
|
-
// Cleanup function (optional)
|
|
966
|
-
};
|
|
967
|
-
});
|
|
968
|
-
|
|
969
|
-
const logger$7 = debug('seedSdk:schema:actors:createModelInstances');
|
|
970
|
-
/**
|
|
971
|
-
* Create Model instances for all model IDs to ensure they're cached
|
|
972
|
-
* This ensures that Model.getById() in Schema.getContext() will find the instances
|
|
973
|
-
*/
|
|
974
|
-
const createModelInstances = fromCallback(({ sendBack, input }) => {
|
|
975
|
-
const evictionEpoch = currentEvictionEpoch();
|
|
976
|
-
const _create = async () => {
|
|
977
|
-
const { modelIds } = input;
|
|
978
|
-
if (modelIds.length === 0) {
|
|
979
|
-
logger$7('No model IDs provided, skipping instance creation');
|
|
980
|
-
sendBack({
|
|
981
|
-
type: 'instancesCreated',
|
|
982
|
-
count: 0,
|
|
983
|
-
});
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
try {
|
|
987
|
-
const _mod_3 = await import('./errors-CS3nDpUU.js');
|
|
988
|
-
const _ns_3 = _mod_3.i;
|
|
989
|
-
const modelMod = _ns_3;
|
|
990
|
-
const { Model } = modelMod;
|
|
991
|
-
// Create instances for all model IDs in parallel
|
|
992
|
-
// Model.createById() will check cache first, then query DB and create if needed
|
|
993
|
-
const createPromises = modelIds.map(async (modelFileId) => {
|
|
994
|
-
try {
|
|
995
|
-
// Not if the schema was evicted (Schema.destroy, test cleanup) since this started
|
|
996
|
-
const schemaHold = Model.takeSchemaHold(modelFileId);
|
|
997
|
-
const model = await Model.createById(modelFileId, { evictionEpoch, ...(!schemaHold && { hold: false }) });
|
|
998
|
-
if (!model && schemaHold)
|
|
999
|
-
Model.dropSchemaHold(modelFileId);
|
|
1000
|
-
if (model) {
|
|
1001
|
-
logger$7(`Created/cached Model instance for modelFileId "${modelFileId}"`);
|
|
1002
|
-
return true;
|
|
1003
|
-
}
|
|
1004
|
-
else {
|
|
1005
|
-
logger$7(`Model.createById returned undefined for modelFileId "${modelFileId}" (may not exist in DB yet)`);
|
|
1006
|
-
return false;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
catch (error) {
|
|
1010
|
-
logger$7(`Error creating Model instance for modelFileId "${modelFileId}": ${error}`);
|
|
1011
|
-
// Don't throw - continue with other models
|
|
1012
|
-
return false;
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
const results = await Promise.all(createPromises);
|
|
1016
|
-
const successCount = results.filter(Boolean).length;
|
|
1017
|
-
logger$7(`Finished creating/caching ${successCount}/${modelIds.length} Model instances`);
|
|
1018
|
-
sendBack({
|
|
1019
|
-
type: 'instancesCreated',
|
|
1020
|
-
count: successCount,
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
catch (error) {
|
|
1024
|
-
logger$7(`Error in createModelInstances: ${error}`);
|
|
1025
|
-
sendBack({
|
|
1026
|
-
type: 'writeError',
|
|
1027
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1030
|
-
};
|
|
1031
|
-
_create().catch((error) => {
|
|
1032
|
-
logger$7('Error in createModelInstances:', error);
|
|
1033
|
-
sendBack({
|
|
1034
|
-
type: 'writeError',
|
|
1035
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1036
|
-
});
|
|
1037
|
-
});
|
|
1038
|
-
return () => {
|
|
1039
|
-
// Cleanup function (optional)
|
|
1040
|
-
};
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
const logger$6 = debug('seedSdk:schema:actors:verifyModelInstancesInCache');
|
|
1044
|
-
/**
|
|
1045
|
-
* Verify that Model instances exist in the static cache with retry logic
|
|
1046
|
-
*/
|
|
1047
|
-
async function verifyWithRetry$2(verifyFn, maxRetries = 5, initialDelay = 100) {
|
|
1048
|
-
let lastError = null;
|
|
1049
|
-
let delay = initialDelay;
|
|
1050
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1051
|
-
try {
|
|
1052
|
-
return await verifyFn();
|
|
1053
|
-
}
|
|
1054
|
-
catch (error) {
|
|
1055
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1056
|
-
if (attempt < maxRetries - 1) {
|
|
1057
|
-
logger$6(`Verification attempt ${attempt + 1} failed, retrying in ${delay}ms: ${lastError.message}`);
|
|
1058
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1059
|
-
delay *= 2; // Exponential backoff
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
throw lastError || new Error('Verification failed after retries');
|
|
1064
|
-
}
|
|
1065
|
-
const verifyModelInstancesInCache = fromCallback(({ sendBack, input }) => {
|
|
1066
|
-
const _verify = async () => {
|
|
1067
|
-
const { modelIds } = input;
|
|
1068
|
-
if (modelIds.length === 0) {
|
|
1069
|
-
// No models means no instances to verify - this is valid
|
|
1070
|
-
logger$6('No model IDs provided, skipping instance verification');
|
|
1071
|
-
sendBack({
|
|
1072
|
-
type: 'instancesVerified',
|
|
1073
|
-
count: 0,
|
|
1074
|
-
});
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
try {
|
|
1078
|
-
const result = await verifyWithRetry$2(async () => {
|
|
1079
|
-
const _mod_4 = await import('./errors-CS3nDpUU.js');
|
|
1080
|
-
const _ns_4 = _mod_4.i;
|
|
1081
|
-
const modelMod = _ns_4;
|
|
1082
|
-
const { Model } = modelMod;
|
|
1083
|
-
// Check each model ID in the cache
|
|
1084
|
-
const verifiedInstances = [];
|
|
1085
|
-
const missingIds = [];
|
|
1086
|
-
for (const modelFileId of modelIds) {
|
|
1087
|
-
// Try to get instance from cache
|
|
1088
|
-
const instance = Model.peek(modelFileId);
|
|
1089
|
-
if (instance) {
|
|
1090
|
-
verifiedInstances.push(modelFileId);
|
|
1091
|
-
}
|
|
1092
|
-
else {
|
|
1093
|
-
missingIds.push(modelFileId);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
if (missingIds.length > 0) {
|
|
1097
|
-
throw new Error(`Model instances not found in cache: ${missingIds.join(', ')}. Found: ${verifiedInstances.length}/${modelIds.length}`);
|
|
1098
|
-
}
|
|
1099
|
-
logger$6(`Model instances verified: ${verifiedInstances.length} instances in cache`);
|
|
1100
|
-
return verifiedInstances.length;
|
|
1101
|
-
});
|
|
1102
|
-
sendBack({
|
|
1103
|
-
type: 'instancesVerified',
|
|
1104
|
-
count: result,
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
catch (error) {
|
|
1108
|
-
logger$6(`Model instance verification failed after retries: ${error}`);
|
|
1109
|
-
sendBack({
|
|
1110
|
-
type: 'verificationFailed',
|
|
1111
|
-
stage: 'verifyModelInstances',
|
|
1112
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1113
|
-
});
|
|
1114
|
-
}
|
|
1115
|
-
};
|
|
1116
|
-
_verify().catch((error) => {
|
|
1117
|
-
logger$6('Error in verifyModelInstancesInCache:', error);
|
|
1118
|
-
sendBack({
|
|
1119
|
-
type: 'verificationFailed',
|
|
1120
|
-
stage: 'verifyModelInstances',
|
|
1121
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1122
|
-
});
|
|
1123
|
-
});
|
|
1124
|
-
return () => {
|
|
1125
|
-
// Cleanup function (optional)
|
|
1126
|
-
};
|
|
1127
|
-
});
|
|
1128
|
-
|
|
1129
|
-
const logger$5 = debug('seedSdk:schema:actors:writePropertiesToDb');
|
|
1130
|
-
/**
|
|
1131
|
-
* Properties are typically written to DB as part of writeModelsToDb
|
|
1132
|
-
* This actor verifies that properties exist and extracts their IDs
|
|
1133
|
-
*/
|
|
1134
|
-
const writePropertiesToDb = fromCallback(({ sendBack, input }) => {
|
|
1135
|
-
const _write = async () => {
|
|
1136
|
-
const { modelIds } = input;
|
|
1137
|
-
try {
|
|
1138
|
-
const _mod_5 = await import('./index-rOTW-2QZ.js');
|
|
1139
|
-
const _ns_5 = _mod_5.ba;
|
|
1140
|
-
const modelSchemaMod = _ns_5;
|
|
1141
|
-
const { models: modelsTable, properties: propertiesTable } = modelSchemaMod;
|
|
1142
|
-
const drizzleMod = await import('drizzle-orm');
|
|
1143
|
-
const { eq } = drizzleMod;
|
|
1144
|
-
const db = BaseDb.getAppDb();
|
|
1145
|
-
if (!db) {
|
|
1146
|
-
throw new Error('Database not available');
|
|
1147
|
-
}
|
|
1148
|
-
// Get property IDs for all models
|
|
1149
|
-
const propertyIds = [];
|
|
1150
|
-
for (const modelFileId of modelIds) {
|
|
1151
|
-
// Get model DB ID from modelFileId
|
|
1152
|
-
const modelRecords = await db
|
|
1153
|
-
.select({ id: modelsTable.id })
|
|
1154
|
-
.from(modelsTable)
|
|
1155
|
-
.where(eq(modelsTable.schemaFileId, modelFileId))
|
|
1156
|
-
.limit(1);
|
|
1157
|
-
if (modelRecords.length > 0 && modelRecords[0].id) {
|
|
1158
|
-
// Get properties for this model
|
|
1159
|
-
const propertyRecords = await db
|
|
1160
|
-
.select({ schemaFileId: propertiesTable.schemaFileId })
|
|
1161
|
-
.from(propertiesTable)
|
|
1162
|
-
.where(eq(propertiesTable.modelId, modelRecords[0].id));
|
|
1163
|
-
for (const prop of propertyRecords) {
|
|
1164
|
-
if (prop.schemaFileId) {
|
|
1165
|
-
propertyIds.push(prop.schemaFileId);
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
logger$5(`Found ${propertyIds.length} properties for ${modelIds.length} models`);
|
|
1171
|
-
sendBack({
|
|
1172
|
-
type: 'propertiesWritten',
|
|
1173
|
-
propertyIds,
|
|
1174
|
-
});
|
|
1175
|
-
}
|
|
1176
|
-
catch (error) {
|
|
1177
|
-
logger$5(`Error writing/verifying properties to database: ${error}`);
|
|
1178
|
-
sendBack({
|
|
1179
|
-
type: 'writeError',
|
|
1180
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1181
|
-
});
|
|
1182
|
-
}
|
|
1183
|
-
};
|
|
1184
|
-
_write().catch((error) => {
|
|
1185
|
-
logger$5('Error in writePropertiesToDb:', error);
|
|
1186
|
-
sendBack({
|
|
1187
|
-
type: 'writeError',
|
|
1188
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1189
|
-
});
|
|
1190
|
-
});
|
|
1191
|
-
return () => {
|
|
1192
|
-
// Cleanup function (optional)
|
|
1193
|
-
};
|
|
1194
|
-
});
|
|
1195
|
-
|
|
1196
|
-
const logger$4 = debug('seedSdk:schema:actors:verifyPropertiesInDb');
|
|
1197
|
-
/**
|
|
1198
|
-
* Verify that property records exist in the database for models with retry logic
|
|
1199
|
-
*/
|
|
1200
|
-
async function verifyWithRetry$1(verifyFn, maxRetries = 5, initialDelay = 100) {
|
|
1201
|
-
let lastError = null;
|
|
1202
|
-
let delay = initialDelay;
|
|
1203
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1204
|
-
try {
|
|
1205
|
-
return await verifyFn();
|
|
1206
|
-
}
|
|
1207
|
-
catch (error) {
|
|
1208
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1209
|
-
if (attempt < maxRetries - 1) {
|
|
1210
|
-
logger$4(`Verification attempt ${attempt + 1} failed, retrying in ${delay}ms: ${lastError.message}`);
|
|
1211
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1212
|
-
delay *= 2; // Exponential backoff
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
throw lastError || new Error('Verification failed after retries');
|
|
1217
|
-
}
|
|
1218
|
-
const verifyPropertiesInDb = fromCallback(({ sendBack, input }) => {
|
|
1219
|
-
const _verify = async () => {
|
|
1220
|
-
const { modelIds, modelFileIds, expectedPropertyIds } = input;
|
|
1221
|
-
// Convert model file IDs to DB IDs if needed
|
|
1222
|
-
let dbModelIds = [];
|
|
1223
|
-
if (modelIds && modelIds.length > 0) {
|
|
1224
|
-
dbModelIds = modelIds;
|
|
1225
|
-
}
|
|
1226
|
-
else if (modelFileIds && modelFileIds.length > 0) {
|
|
1227
|
-
// Convert file IDs to DB IDs
|
|
1228
|
-
const db = BaseDb.getAppDb();
|
|
1229
|
-
if (!db) {
|
|
1230
|
-
throw new Error('Database not available');
|
|
1231
|
-
}
|
|
1232
|
-
for (const modelFileId of modelFileIds) {
|
|
1233
|
-
const modelRecords = await db
|
|
1234
|
-
.select({ id: models.id })
|
|
1235
|
-
.from(models)
|
|
1236
|
-
.where(eq(models.schemaFileId, modelFileId))
|
|
1237
|
-
.limit(1);
|
|
1238
|
-
if (modelRecords.length > 0 && modelRecords[0].id) {
|
|
1239
|
-
dbModelIds.push(modelRecords[0].id);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
if (dbModelIds.length === 0) {
|
|
1244
|
-
// No models means no properties to verify - this is valid
|
|
1245
|
-
logger$4('No models provided, skipping property verification');
|
|
1246
|
-
sendBack({
|
|
1247
|
-
type: 'propertiesVerified',
|
|
1248
|
-
propertyIds: [],
|
|
1249
|
-
});
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
try {
|
|
1253
|
-
const result = await verifyWithRetry$1(async () => {
|
|
1254
|
-
const db = BaseDb.getAppDb();
|
|
1255
|
-
if (!db) {
|
|
1256
|
-
throw new Error('Database not available');
|
|
1257
|
-
}
|
|
1258
|
-
// Query for properties for all models
|
|
1259
|
-
const allProperties = await Promise.all(dbModelIds.map(async (modelId) => {
|
|
1260
|
-
const props = await db
|
|
1261
|
-
.select({
|
|
1262
|
-
propertyFileId: properties.schemaFileId,
|
|
1263
|
-
propertyId: properties.id,
|
|
1264
|
-
})
|
|
1265
|
-
.from(properties)
|
|
1266
|
-
.where(eq(properties.modelId, modelId));
|
|
1267
|
-
return props;
|
|
1268
|
-
}));
|
|
1269
|
-
const propertyIds = allProperties
|
|
1270
|
-
.flat()
|
|
1271
|
-
.map((row) => row.propertyFileId)
|
|
1272
|
-
.filter((id) => id !== null && id !== undefined);
|
|
1273
|
-
// If expectedPropertyIds provided, verify all are present
|
|
1274
|
-
if (expectedPropertyIds && expectedPropertyIds.length > 0) {
|
|
1275
|
-
const missingIds = expectedPropertyIds.filter(id => !propertyIds.includes(id));
|
|
1276
|
-
if (missingIds.length > 0) {
|
|
1277
|
-
throw new Error(`Missing expected property IDs: ${missingIds.join(', ')}. Found: ${propertyIds.join(', ')}`);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
logger$4(`Properties verified: found ${propertyIds.length} properties for ${dbModelIds.length} models`);
|
|
1281
|
-
return propertyIds;
|
|
1282
|
-
});
|
|
1283
|
-
sendBack({
|
|
1284
|
-
type: 'propertiesVerified',
|
|
1285
|
-
propertyIds: result,
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
catch (error) {
|
|
1289
|
-
logger$4(`Property verification failed after retries: ${error}`);
|
|
1290
|
-
sendBack({
|
|
1291
|
-
type: 'verificationFailed',
|
|
1292
|
-
stage: 'verifyProperties',
|
|
1293
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
};
|
|
1297
|
-
_verify().catch((error) => {
|
|
1298
|
-
logger$4('Error in verifyPropertiesInDb:', error);
|
|
1299
|
-
sendBack({
|
|
1300
|
-
type: 'verificationFailed',
|
|
1301
|
-
stage: 'verifyProperties',
|
|
1302
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1303
|
-
});
|
|
1304
|
-
});
|
|
1305
|
-
return () => {
|
|
1306
|
-
// Cleanup function (optional)
|
|
1307
|
-
};
|
|
1308
|
-
});
|
|
1309
|
-
|
|
1310
|
-
const logger$3 = debug('seedSdk:schema:actors:createPropertyInstances');
|
|
1311
|
-
/**
|
|
1312
|
-
* Create ModelProperty instances for all property IDs to ensure they're cached
|
|
1313
|
-
* Properties are typically created when Model instances are created, but this
|
|
1314
|
-
* ensures they're all available in the cache
|
|
1315
|
-
*/
|
|
1316
|
-
const createPropertyInstances = fromCallback(({ sendBack, input }) => {
|
|
1317
|
-
const _create = async () => {
|
|
1318
|
-
const { propertyIds, modelIds } = input;
|
|
1319
|
-
if (propertyIds.length === 0) {
|
|
1320
|
-
logger$3('No property IDs provided, skipping instance creation');
|
|
1321
|
-
sendBack({
|
|
1322
|
-
type: 'instancesCreated',
|
|
1323
|
-
count: 0,
|
|
1324
|
-
});
|
|
1325
|
-
return;
|
|
1326
|
-
}
|
|
1327
|
-
try {
|
|
1328
|
-
const modProp = await import('./ModelProperty-DDD47KVm.js');
|
|
1329
|
-
const ModelProperty = modProp?.ModelProperty ?? modProp?.default;
|
|
1330
|
-
const _mod_6 = await import('./errors-CS3nDpUU.js');
|
|
1331
|
-
const _ns_6 = _mod_6.i;
|
|
1332
|
-
const modModel = _ns_6;
|
|
1333
|
-
const Model = modModel?.Model ?? modModel?.default;
|
|
1334
|
-
if (!ModelProperty || !Model) {
|
|
1335
|
-
logger$3('ModelProperty or Model not available from dynamic import');
|
|
1336
|
-
sendBack({ type: 'instancesCreated', count: 0 });
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
// Properties are typically loaded when Model instances are loaded
|
|
1340
|
-
// But we can verify they exist by checking Model instances
|
|
1341
|
-
let successCount = 0;
|
|
1342
|
-
// For each model, check if properties are loaded
|
|
1343
|
-
for (const modelFileId of modelIds) {
|
|
1344
|
-
const model = Model.peek(modelFileId);
|
|
1345
|
-
if (model && model.properties) {
|
|
1346
|
-
// Count properties that match our property IDs
|
|
1347
|
-
for (const prop of model.properties) {
|
|
1348
|
-
const context = prop._getSnapshotContext();
|
|
1349
|
-
const propFileId = context._propertyFileId || context.id;
|
|
1350
|
-
if (propFileId && propertyIds.includes(String(propFileId))) {
|
|
1351
|
-
successCount++;
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
// Also try to get properties directly by ID
|
|
1357
|
-
for (const propertyFileId of propertyIds) {
|
|
1358
|
-
try {
|
|
1359
|
-
const property = ModelProperty.getById(propertyFileId);
|
|
1360
|
-
if (property) {
|
|
1361
|
-
// Check if we already counted this
|
|
1362
|
-
// Access _service via type assertion since it's protected
|
|
1363
|
-
const context = property._service.getSnapshot().context;
|
|
1364
|
-
const propFileId = context._propertyFileId || context.id;
|
|
1365
|
-
if (!propertyIds.slice(0, propertyIds.indexOf(propertyFileId)).includes(String(propFileId))) {
|
|
1366
|
-
successCount++;
|
|
1367
|
-
}
|
|
1368
|
-
logger$3(`Found/cached ModelProperty instance for propertyFileId "${propertyFileId}"`);
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
catch (error) {
|
|
1372
|
-
logger$3(`Error getting ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
logger$3(`Finished verifying/caching ${successCount}/${propertyIds.length} Property instances`);
|
|
1376
|
-
sendBack({
|
|
1377
|
-
type: 'instancesCreated',
|
|
1378
|
-
count: successCount,
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
catch (error) {
|
|
1382
|
-
logger$3(`Error in createPropertyInstances: ${error}`);
|
|
1383
|
-
sendBack({
|
|
1384
|
-
type: 'writeError',
|
|
1385
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
};
|
|
1389
|
-
_create().catch((error) => {
|
|
1390
|
-
logger$3('Error in createPropertyInstances:', error);
|
|
1391
|
-
sendBack({
|
|
1392
|
-
type: 'writeError',
|
|
1393
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1394
|
-
});
|
|
1395
|
-
});
|
|
1396
|
-
return () => {
|
|
1397
|
-
// Cleanup function (optional)
|
|
1398
|
-
};
|
|
1399
|
-
});
|
|
1400
|
-
|
|
1401
|
-
const logger$2 = debug('seedSdk:schema:actors:verifyPropertyInstancesInCache');
|
|
1402
|
-
/**
|
|
1403
|
-
* Verify that ModelProperty instances exist in the static cache with retry logic
|
|
1404
|
-
*/
|
|
1405
|
-
async function verifyWithRetry(verifyFn, maxRetries = 5, initialDelay = 100) {
|
|
1406
|
-
let lastError = null;
|
|
1407
|
-
let delay = initialDelay;
|
|
1408
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1409
|
-
try {
|
|
1410
|
-
return await verifyFn();
|
|
1411
|
-
}
|
|
1412
|
-
catch (error) {
|
|
1413
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1414
|
-
if (attempt < maxRetries - 1) {
|
|
1415
|
-
logger$2(`Verification attempt ${attempt + 1} failed, retrying in ${delay}ms: ${lastError.message}`);
|
|
1416
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1417
|
-
delay *= 2; // Exponential backoff
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
throw lastError || new Error('Verification failed after retries');
|
|
1422
|
-
}
|
|
1423
|
-
const verifyPropertyInstancesInCache = fromCallback(({ sendBack, input }) => {
|
|
1424
|
-
const _verify = async () => {
|
|
1425
|
-
const { propertyIds } = input;
|
|
1426
|
-
if (propertyIds.length === 0) {
|
|
1427
|
-
// No properties means no instances to verify - this is valid
|
|
1428
|
-
logger$2('No property IDs provided, skipping instance verification');
|
|
1429
|
-
sendBack({
|
|
1430
|
-
type: 'instancesVerified',
|
|
1431
|
-
count: 0,
|
|
1432
|
-
});
|
|
1433
|
-
return;
|
|
1434
|
-
}
|
|
1435
|
-
try {
|
|
1436
|
-
const result = await verifyWithRetry(async () => {
|
|
1437
|
-
const mod = await import('./ModelProperty-DDD47KVm.js');
|
|
1438
|
-
const ModelProperty = mod?.ModelProperty ?? mod?.default;
|
|
1439
|
-
if (!ModelProperty) {
|
|
1440
|
-
throw new Error('ModelProperty not available from dynamic import');
|
|
1441
|
-
}
|
|
1442
|
-
// Check each property ID in the cache
|
|
1443
|
-
const verifiedInstances = [];
|
|
1444
|
-
const missingIds = [];
|
|
1445
|
-
for (const propertyFileId of propertyIds) {
|
|
1446
|
-
// Try to get instance from cache
|
|
1447
|
-
const instance = ModelProperty.getById(propertyFileId);
|
|
1448
|
-
if (instance) {
|
|
1449
|
-
verifiedInstances.push(propertyFileId);
|
|
1450
|
-
}
|
|
1451
|
-
else {
|
|
1452
|
-
missingIds.push(propertyFileId);
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
if (missingIds.length > 0) {
|
|
1456
|
-
throw new Error(`Property instances not found in cache: ${missingIds.join(', ')}. Found: ${verifiedInstances.length}/${propertyIds.length}`);
|
|
1457
|
-
}
|
|
1458
|
-
logger$2(`Property instances verified: ${verifiedInstances.length} instances in cache`);
|
|
1459
|
-
return verifiedInstances.length;
|
|
1460
|
-
});
|
|
1461
|
-
sendBack({
|
|
1462
|
-
type: 'instancesVerified',
|
|
1463
|
-
count: result,
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
catch (error) {
|
|
1467
|
-
logger$2(`Property instance verification failed after retries: ${error}`);
|
|
1468
|
-
sendBack({
|
|
1469
|
-
type: 'verificationFailed',
|
|
1470
|
-
stage: 'verifyPropertyInstances',
|
|
1471
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1472
|
-
});
|
|
1473
|
-
}
|
|
1474
|
-
};
|
|
1475
|
-
_verify().catch((error) => {
|
|
1476
|
-
logger$2('Error in verifyPropertyInstancesInCache:', error);
|
|
1477
|
-
sendBack({
|
|
1478
|
-
type: 'verificationFailed',
|
|
1479
|
-
stage: 'verifyPropertyInstances',
|
|
1480
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1481
|
-
});
|
|
1482
|
-
});
|
|
1483
|
-
return () => {
|
|
1484
|
-
// Cleanup function (optional)
|
|
1485
|
-
};
|
|
1486
|
-
});
|
|
1487
|
-
|
|
1488
|
-
const schemaMachine = setup({
|
|
1489
|
-
types: {
|
|
1490
|
-
context: {},
|
|
1491
|
-
input: {},
|
|
1492
|
-
events: {},
|
|
1493
|
-
},
|
|
1494
|
-
actors: {
|
|
1495
|
-
validateSchema,
|
|
1496
|
-
// Staged loading actors
|
|
1497
|
-
checkExistingSchema,
|
|
1498
|
-
writeSchemaToDb,
|
|
1499
|
-
verifySchemaInDb,
|
|
1500
|
-
writeModelsToDb,
|
|
1501
|
-
verifyModelsInDb,
|
|
1502
|
-
createModelInstances,
|
|
1503
|
-
verifyModelInstancesInCache,
|
|
1504
|
-
writePropertiesToDb,
|
|
1505
|
-
verifyPropertiesInDb,
|
|
1506
|
-
createPropertyInstances,
|
|
1507
|
-
verifyPropertyInstancesInCache,
|
|
1508
|
-
},
|
|
1509
|
-
guards: {
|
|
1510
|
-
isSchemaValid: ({ context }) => {
|
|
1511
|
-
return !context._validationErrors || context._validationErrors.length === 0;
|
|
1512
|
-
},
|
|
1513
|
-
},
|
|
1514
|
-
}).createMachine({
|
|
1515
|
-
id: 'schema',
|
|
1516
|
-
initial: 'loading',
|
|
1517
|
-
context: ({ input }) => ({
|
|
1518
|
-
schemaName: input.schemaName,
|
|
1519
|
-
_isDraft: false,
|
|
1520
|
-
_editedProperties: new Set(),
|
|
1521
|
-
_validationErrors: undefined,
|
|
1522
|
-
_liveQueryModelIds: [],
|
|
1523
|
-
}),
|
|
1524
|
-
on: {
|
|
1525
|
-
updateContext: [{
|
|
1526
|
-
actions: assign(({ context, event }) => {
|
|
1527
|
-
const newContext = Object.assign({}, context);
|
|
1528
|
-
// Check if this is only updating internal fields
|
|
1529
|
-
Object.keys(event).every((key) => {
|
|
1530
|
-
return key === 'type' || key.startsWith('_');
|
|
1531
|
-
});
|
|
1532
|
-
for (let i = 0; i < Object.keys(event).length; i++) {
|
|
1533
|
-
const key = Object.keys(event)[i];
|
|
1534
|
-
if (key === 'type') {
|
|
1535
|
-
continue;
|
|
1536
|
-
}
|
|
1537
|
-
if (key in newContext || key.startsWith('_')) {
|
|
1538
|
-
newContext[key] = event[key];
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
// Clear validation errors on context update (will be re-validated if needed)
|
|
1542
|
-
newContext._validationErrors = undefined;
|
|
1543
|
-
return newContext;
|
|
1544
|
-
}),
|
|
1545
|
-
// Only trigger validation if we're updating non-internal fields
|
|
1546
|
-
guard: ({ event }) => {
|
|
1547
|
-
return !Object.keys(event).every((key) => {
|
|
1548
|
-
return key === 'type' || key.startsWith('_');
|
|
1549
|
-
});
|
|
1550
|
-
},
|
|
1551
|
-
target: '.validating',
|
|
1552
|
-
}, {
|
|
1553
|
-
// Internal-only updates (e.g. the _dbUpdatedAt refresh after a draft save) are applied
|
|
1554
|
-
// without re-validating. Dropping them made the next save's conflict check compare
|
|
1555
|
-
// against a stale _dbUpdatedAt.
|
|
1556
|
-
actions: assign(({ context, event }) => {
|
|
1557
|
-
const newContext = Object.assign({}, context);
|
|
1558
|
-
for (const key of Object.keys(event)) {
|
|
1559
|
-
if (key.startsWith('_')) {
|
|
1560
|
-
newContext[key] = event[key];
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
return newContext;
|
|
1564
|
-
}),
|
|
1565
|
-
}],
|
|
1566
|
-
validateSchema: {
|
|
1567
|
-
target: '.validating',
|
|
1568
|
-
},
|
|
1569
|
-
validationSuccess: {
|
|
1570
|
-
actions: assign(({ context }) => ({
|
|
1571
|
-
...context,
|
|
1572
|
-
_validationErrors: [],
|
|
1573
|
-
})),
|
|
1574
|
-
},
|
|
1575
|
-
validationError: {
|
|
1576
|
-
actions: assign(({ context, event }) => ({
|
|
1577
|
-
...context,
|
|
1578
|
-
_validationErrors: event.errors,
|
|
1579
|
-
})),
|
|
1580
|
-
},
|
|
1581
|
-
markAsDraft: {
|
|
1582
|
-
actions: assign(({ context, event }) => ({
|
|
1583
|
-
...context,
|
|
1584
|
-
_isDraft: true,
|
|
1585
|
-
_isEdited: true,
|
|
1586
|
-
_editedProperties: new Set([
|
|
1587
|
-
...(context._editedProperties || []),
|
|
1588
|
-
event.propertyKey,
|
|
1589
|
-
]),
|
|
1590
|
-
})),
|
|
1591
|
-
},
|
|
1592
|
-
clearDraft: {
|
|
1593
|
-
actions: assign(({ context, event }) => {
|
|
1594
|
-
const newContext = { ...context };
|
|
1595
|
-
newContext._isDraft = false;
|
|
1596
|
-
newContext._isEdited = false;
|
|
1597
|
-
newContext._editedProperties = new Set();
|
|
1598
|
-
// Update conflict detection metadata if provided
|
|
1599
|
-
if (event._dbUpdatedAt !== undefined) {
|
|
1600
|
-
newContext._dbUpdatedAt = event._dbUpdatedAt;
|
|
1601
|
-
}
|
|
1602
|
-
if (event._dbVersion !== undefined) {
|
|
1603
|
-
newContext._dbVersion = event._dbVersion;
|
|
1604
|
-
}
|
|
1605
|
-
// saveNewVersion passes the schema it just wrote: the instance now is that version.
|
|
1606
|
-
const saved = event.savedSchema;
|
|
1607
|
-
if (saved) {
|
|
1608
|
-
newContext.version = saved.version;
|
|
1609
|
-
newContext.metadata = saved.metadata;
|
|
1610
|
-
newContext.migrations = saved.migrations;
|
|
1611
|
-
}
|
|
1612
|
-
return newContext;
|
|
1613
|
-
}),
|
|
1614
|
-
},
|
|
1615
|
-
destroyStarted: {
|
|
1616
|
-
actions: assign({ _destroyError: null }),
|
|
1617
|
-
},
|
|
1618
|
-
destroyError: {
|
|
1619
|
-
actions: assign(({ event }) => ({
|
|
1620
|
-
_destroyError: event.error instanceof Error
|
|
1621
|
-
? { message: event.error.message, name: event.error.name }
|
|
1622
|
-
: { message: String(event.error) },
|
|
1623
|
-
})),
|
|
1624
|
-
},
|
|
1625
|
-
},
|
|
1626
|
-
states: {
|
|
1627
|
-
loading: {
|
|
1628
|
-
initial: 'checkingExisting',
|
|
1629
|
-
states: {
|
|
1630
|
-
// Stage 0: Check for existing schema
|
|
1631
|
-
checkingExisting: {
|
|
1632
|
-
invoke: {
|
|
1633
|
-
src: 'checkExistingSchema',
|
|
1634
|
-
input: ({ context }) => ({ context }),
|
|
1635
|
-
},
|
|
1636
|
-
on: {
|
|
1637
|
-
schemaFound: {
|
|
1638
|
-
target: '#schema.idle',
|
|
1639
|
-
actions: assign(({ event }) => {
|
|
1640
|
-
return {
|
|
1641
|
-
schemaName: event.schema.metadata.name,
|
|
1642
|
-
$schema: event.schema.$schema,
|
|
1643
|
-
version: event.schema.version,
|
|
1644
|
-
metadata: event.schema.metadata,
|
|
1645
|
-
models: event.schema.models,
|
|
1646
|
-
enums: event.schema.enums,
|
|
1647
|
-
migrations: event.schema.migrations,
|
|
1648
|
-
nameMatch: event.schema.nameMatch,
|
|
1649
|
-
_isDraft: false,
|
|
1650
|
-
_isEdited: event.schemaRecord?.isEdited ?? false,
|
|
1651
|
-
_editedProperties: new Set(),
|
|
1652
|
-
_validationErrors: undefined,
|
|
1653
|
-
id: event.schema.id, // schemaFileId (string)
|
|
1654
|
-
_dbId: event.schemaRecord?.id, // Database integer ID
|
|
1655
|
-
_loadedAt: event.loadedAt,
|
|
1656
|
-
_dbVersion: event.dbVersion,
|
|
1657
|
-
_dbUpdatedAt: event.dbUpdatedAt,
|
|
1658
|
-
_liveQueryModelIds: event.modelIds || [],
|
|
1659
|
-
_schemaRecord: event.schemaRecord,
|
|
1660
|
-
};
|
|
1661
|
-
}),
|
|
1662
|
-
},
|
|
1663
|
-
schemaNotFound: {
|
|
1664
|
-
target: 'writingSchema',
|
|
1665
|
-
},
|
|
1666
|
-
},
|
|
1667
|
-
},
|
|
1668
|
-
// Stage 1: Write and verify schema
|
|
1669
|
-
writingSchema: {
|
|
1670
|
-
entry: assign({
|
|
1671
|
-
_loadingStage: 'writingSchema',
|
|
1672
|
-
}),
|
|
1673
|
-
invoke: {
|
|
1674
|
-
src: 'writeSchemaToDb',
|
|
1675
|
-
input: ({ context }) => ({
|
|
1676
|
-
schemaName: context.schemaName,
|
|
1677
|
-
}),
|
|
1678
|
-
},
|
|
1679
|
-
on: {
|
|
1680
|
-
schemaWritten: {
|
|
1681
|
-
target: 'verifyingSchema',
|
|
1682
|
-
actions: assign({
|
|
1683
|
-
_schemaRecord: ({ event }) => event.schemaRecord,
|
|
1684
|
-
id: ({ event }) => event.schema.id, // schemaFileId (string)
|
|
1685
|
-
_dbId: ({ event }) => event.schemaRecord?.id, // Database integer ID
|
|
1686
|
-
// A schema created in code has these only in the written record; without them
|
|
1687
|
-
// validation (and so saveNewVersion) failed with "Schema metadata is required".
|
|
1688
|
-
$schema: ({ context, event }) => context.$schema ?? event.schema.$schema,
|
|
1689
|
-
version: ({ context, event }) => context.version ?? event.schema.version,
|
|
1690
|
-
metadata: ({ context, event }) => context.metadata ?? event.schema.metadata,
|
|
1691
|
-
enums: ({ context, event }) => context.enums ?? event.schema.enums,
|
|
1692
|
-
migrations: ({ context, event }) => context.migrations ?? event.schema.migrations,
|
|
1693
|
-
nameMatch: ({ context, event }) => context.nameMatch ?? event.schema.nameMatch,
|
|
1694
|
-
}),
|
|
1695
|
-
},
|
|
1696
|
-
writeError: {
|
|
1697
|
-
target: '#schema.error',
|
|
1698
|
-
actions: assign({
|
|
1699
|
-
_loadingError: ({ event }) => ({
|
|
1700
|
-
stage: 'writingSchema',
|
|
1701
|
-
error: event.error,
|
|
1702
|
-
}),
|
|
1703
|
-
}),
|
|
1704
|
-
},
|
|
1705
|
-
},
|
|
1706
|
-
},
|
|
1707
|
-
verifyingSchema: {
|
|
1708
|
-
entry: assign({
|
|
1709
|
-
_loadingStage: 'verifyingSchema',
|
|
1710
|
-
}),
|
|
1711
|
-
invoke: {
|
|
1712
|
-
src: 'verifySchemaInDb',
|
|
1713
|
-
input: ({ context }) => ({
|
|
1714
|
-
schemaFileId: context.id,
|
|
1715
|
-
expectedSchemaId: context._dbId,
|
|
1716
|
-
}),
|
|
1717
|
-
},
|
|
1718
|
-
on: {
|
|
1719
|
-
schemaVerified: {
|
|
1720
|
-
target: 'writingModels',
|
|
1721
|
-
actions: assign({
|
|
1722
|
-
_dbId: ({ event }) => event.schemaId, // Store database integer ID
|
|
1723
|
-
}),
|
|
1724
|
-
},
|
|
1725
|
-
verificationFailed: {
|
|
1726
|
-
target: '#schema.error',
|
|
1727
|
-
actions: assign({
|
|
1728
|
-
_loadingError: ({ event }) => ({
|
|
1729
|
-
stage: event.stage,
|
|
1730
|
-
error: event.error,
|
|
1731
|
-
}),
|
|
1732
|
-
}),
|
|
1733
|
-
},
|
|
1734
|
-
},
|
|
1735
|
-
},
|
|
1736
|
-
// Stage 2: Write and verify models
|
|
1737
|
-
writingModels: {
|
|
1738
|
-
entry: assign({
|
|
1739
|
-
_loadingStage: 'writingModels',
|
|
1740
|
-
}),
|
|
1741
|
-
invoke: {
|
|
1742
|
-
src: 'writeModelsToDb',
|
|
1743
|
-
input: ({ context }) => ({
|
|
1744
|
-
schema: {
|
|
1745
|
-
id: context.id,
|
|
1746
|
-
models: context.models || {},
|
|
1747
|
-
version: context.version || 1,
|
|
1748
|
-
metadata: context.metadata || { name: context.schemaName, createdAt: '', updatedAt: '' },
|
|
1749
|
-
},
|
|
1750
|
-
schemaRecord: context._schemaRecord,
|
|
1751
|
-
schemaName: context.schemaName,
|
|
1752
|
-
}),
|
|
1753
|
-
},
|
|
1754
|
-
on: {
|
|
1755
|
-
modelsWritten: {
|
|
1756
|
-
target: 'verifyingModels',
|
|
1757
|
-
actions: assign({
|
|
1758
|
-
_modelIds: ({ event }) => event.modelIds,
|
|
1759
|
-
}),
|
|
1760
|
-
},
|
|
1761
|
-
writeError: {
|
|
1762
|
-
target: '#schema.error',
|
|
1763
|
-
actions: assign({
|
|
1764
|
-
_loadingError: ({ event }) => ({
|
|
1765
|
-
stage: 'writingModels',
|
|
1766
|
-
error: event.error,
|
|
1767
|
-
}),
|
|
1768
|
-
}),
|
|
1769
|
-
},
|
|
1770
|
-
},
|
|
1771
|
-
},
|
|
1772
|
-
verifyingModels: {
|
|
1773
|
-
entry: assign({
|
|
1774
|
-
_loadingStage: 'verifyingModels',
|
|
1775
|
-
}),
|
|
1776
|
-
invoke: {
|
|
1777
|
-
src: 'verifyModelsInDb',
|
|
1778
|
-
input: ({ context }) => ({
|
|
1779
|
-
schemaId: context._dbId,
|
|
1780
|
-
expectedModelIds: context._modelIds,
|
|
1781
|
-
}),
|
|
1782
|
-
},
|
|
1783
|
-
on: {
|
|
1784
|
-
modelsVerified: {
|
|
1785
|
-
target: 'creatingModelInstances',
|
|
1786
|
-
actions: assign({
|
|
1787
|
-
_modelIds: ({ event }) => event.modelIds,
|
|
1788
|
-
}),
|
|
1789
|
-
},
|
|
1790
|
-
verificationFailed: {
|
|
1791
|
-
target: '#schema.error',
|
|
1792
|
-
actions: assign({
|
|
1793
|
-
_loadingError: ({ event }) => ({
|
|
1794
|
-
stage: event.stage,
|
|
1795
|
-
error: event.error,
|
|
1796
|
-
}),
|
|
1797
|
-
}),
|
|
1798
|
-
},
|
|
1799
|
-
},
|
|
1800
|
-
},
|
|
1801
|
-
// Stage 3: Create and verify model instances
|
|
1802
|
-
creatingModelInstances: {
|
|
1803
|
-
entry: assign({
|
|
1804
|
-
_loadingStage: 'creatingModelInstances',
|
|
1805
|
-
}),
|
|
1806
|
-
invoke: {
|
|
1807
|
-
src: 'createModelInstances',
|
|
1808
|
-
input: ({ context }) => ({
|
|
1809
|
-
modelIds: context._modelIds || [],
|
|
1810
|
-
schemaName: context.schemaName,
|
|
1811
|
-
}),
|
|
1812
|
-
},
|
|
1813
|
-
on: {
|
|
1814
|
-
instancesCreated: {
|
|
1815
|
-
target: 'verifyingModelInstances',
|
|
1816
|
-
},
|
|
1817
|
-
writeError: {
|
|
1818
|
-
target: '#schema.error',
|
|
1819
|
-
actions: assign({
|
|
1820
|
-
_loadingError: ({ event }) => ({
|
|
1821
|
-
stage: 'creatingModelInstances',
|
|
1822
|
-
error: event.error,
|
|
1823
|
-
}),
|
|
1824
|
-
}),
|
|
1825
|
-
},
|
|
1826
|
-
},
|
|
1827
|
-
},
|
|
1828
|
-
verifyingModelInstances: {
|
|
1829
|
-
entry: assign({
|
|
1830
|
-
_loadingStage: 'verifyingModelInstances',
|
|
1831
|
-
}),
|
|
1832
|
-
invoke: {
|
|
1833
|
-
src: 'verifyModelInstancesInCache',
|
|
1834
|
-
input: ({ context }) => ({
|
|
1835
|
-
modelIds: context._modelIds || [],
|
|
1836
|
-
}),
|
|
1837
|
-
},
|
|
1838
|
-
on: {
|
|
1839
|
-
instancesVerified: {
|
|
1840
|
-
target: 'writingProperties',
|
|
1841
|
-
},
|
|
1842
|
-
verificationFailed: {
|
|
1843
|
-
target: '#schema.error',
|
|
1844
|
-
actions: assign({
|
|
1845
|
-
_loadingError: ({ event }) => ({
|
|
1846
|
-
stage: event.stage,
|
|
1847
|
-
error: event.error,
|
|
1848
|
-
}),
|
|
1849
|
-
}),
|
|
1850
|
-
},
|
|
1851
|
-
},
|
|
1852
|
-
},
|
|
1853
|
-
// Stage 4: Write and verify properties
|
|
1854
|
-
writingProperties: {
|
|
1855
|
-
entry: assign({
|
|
1856
|
-
_loadingStage: 'writingProperties',
|
|
1857
|
-
}),
|
|
1858
|
-
invoke: {
|
|
1859
|
-
src: 'writePropertiesToDb',
|
|
1860
|
-
input: ({ context }) => ({
|
|
1861
|
-
modelIds: context._modelIds || [],
|
|
1862
|
-
}),
|
|
1863
|
-
},
|
|
1864
|
-
on: {
|
|
1865
|
-
propertiesWritten: {
|
|
1866
|
-
target: 'verifyingProperties',
|
|
1867
|
-
actions: assign({
|
|
1868
|
-
_propertyIds: ({ event }) => event.propertyIds,
|
|
1869
|
-
}),
|
|
1870
|
-
},
|
|
1871
|
-
writeError: {
|
|
1872
|
-
target: '#schema.error',
|
|
1873
|
-
actions: assign({
|
|
1874
|
-
_loadingError: ({ event }) => ({
|
|
1875
|
-
stage: 'writingProperties',
|
|
1876
|
-
error: event.error,
|
|
1877
|
-
}),
|
|
1878
|
-
}),
|
|
1879
|
-
},
|
|
1880
|
-
},
|
|
1881
|
-
},
|
|
1882
|
-
verifyingProperties: {
|
|
1883
|
-
entry: assign({
|
|
1884
|
-
_loadingStage: 'verifyingProperties',
|
|
1885
|
-
}),
|
|
1886
|
-
invoke: {
|
|
1887
|
-
src: 'verifyPropertiesInDb',
|
|
1888
|
-
input: ({ context }) => ({
|
|
1889
|
-
modelFileIds: context._modelIds,
|
|
1890
|
-
expectedPropertyIds: context._propertyIds,
|
|
1891
|
-
}),
|
|
1892
|
-
},
|
|
1893
|
-
on: {
|
|
1894
|
-
propertiesVerified: {
|
|
1895
|
-
target: 'creatingPropertyInstances',
|
|
1896
|
-
actions: assign({
|
|
1897
|
-
_propertyIds: ({ event }) => event.propertyIds,
|
|
1898
|
-
}),
|
|
1899
|
-
},
|
|
1900
|
-
verificationFailed: {
|
|
1901
|
-
target: '#schema.error',
|
|
1902
|
-
actions: assign({
|
|
1903
|
-
_loadingError: ({ event }) => ({
|
|
1904
|
-
stage: event.stage,
|
|
1905
|
-
error: event.error,
|
|
1906
|
-
}),
|
|
1907
|
-
}),
|
|
1908
|
-
},
|
|
1909
|
-
},
|
|
1910
|
-
},
|
|
1911
|
-
// Stage 5: Create and verify property instances
|
|
1912
|
-
creatingPropertyInstances: {
|
|
1913
|
-
entry: assign({
|
|
1914
|
-
_loadingStage: 'creatingPropertyInstances',
|
|
1915
|
-
}),
|
|
1916
|
-
invoke: {
|
|
1917
|
-
src: 'createPropertyInstances',
|
|
1918
|
-
input: ({ context }) => ({
|
|
1919
|
-
propertyIds: context._propertyIds || [],
|
|
1920
|
-
modelIds: context._modelIds || [],
|
|
1921
|
-
}),
|
|
1922
|
-
},
|
|
1923
|
-
on: {
|
|
1924
|
-
instancesCreated: {
|
|
1925
|
-
target: 'verifyingPropertyInstances',
|
|
1926
|
-
},
|
|
1927
|
-
writeError: {
|
|
1928
|
-
target: '#schema.error',
|
|
1929
|
-
actions: assign({
|
|
1930
|
-
_loadingError: ({ event }) => ({
|
|
1931
|
-
stage: 'creatingPropertyInstances',
|
|
1932
|
-
error: event.error,
|
|
1933
|
-
}),
|
|
1934
|
-
}),
|
|
1935
|
-
},
|
|
1936
|
-
},
|
|
1937
|
-
},
|
|
1938
|
-
verifyingPropertyInstances: {
|
|
1939
|
-
entry: assign({
|
|
1940
|
-
_loadingStage: 'verifyingPropertyInstances',
|
|
1941
|
-
}),
|
|
1942
|
-
invoke: {
|
|
1943
|
-
src: 'verifyPropertyInstancesInCache',
|
|
1944
|
-
input: ({ context }) => ({
|
|
1945
|
-
propertyIds: context._propertyIds || [],
|
|
1946
|
-
}),
|
|
1947
|
-
},
|
|
1948
|
-
on: {
|
|
1949
|
-
instancesVerified: {
|
|
1950
|
-
target: '#schema.idle',
|
|
1951
|
-
actions: assign({
|
|
1952
|
-
_liveQueryModelIds: ({ context }) => context._modelIds || [],
|
|
1953
|
-
_loadingStage: undefined,
|
|
1954
|
-
}),
|
|
1955
|
-
},
|
|
1956
|
-
verificationFailed: {
|
|
1957
|
-
target: '#schema.error',
|
|
1958
|
-
actions: assign({
|
|
1959
|
-
_loadingError: ({ event }) => ({
|
|
1960
|
-
stage: event.stage,
|
|
1961
|
-
error: event.error,
|
|
1962
|
-
}),
|
|
1963
|
-
}),
|
|
1964
|
-
},
|
|
1965
|
-
},
|
|
1966
|
-
},
|
|
1967
|
-
},
|
|
1968
|
-
},
|
|
1969
|
-
idle: {
|
|
1970
|
-
on: {
|
|
1971
|
-
validateSchema: {
|
|
1972
|
-
target: 'validating',
|
|
1973
|
-
},
|
|
1974
|
-
reloadFromDb: {
|
|
1975
|
-
target: 'loading',
|
|
1976
|
-
},
|
|
1977
|
-
},
|
|
1978
|
-
// No `always: hasValidationErrors -> validating` here: validation is re-run when the context changes
|
|
1979
|
-
// (updateContext) or on request (validateSchema). Re-entering while errors exist would loop
|
|
1980
|
-
// forever on a schema that is still invalid.
|
|
1981
|
-
},
|
|
1982
|
-
validating: {
|
|
1983
|
-
on: {
|
|
1984
|
-
// These state-level handlers take precedence over the root ones, so they must store the result
|
|
1985
|
-
// themselves.
|
|
1986
|
-
validationSuccess: {
|
|
1987
|
-
target: 'idle',
|
|
1988
|
-
actions: assign(({ context }) => ({
|
|
1989
|
-
...context,
|
|
1990
|
-
_validationErrors: [],
|
|
1991
|
-
})),
|
|
1992
|
-
},
|
|
1993
|
-
validationError: {
|
|
1994
|
-
target: 'idle',
|
|
1995
|
-
actions: assign(({ context, event }) => ({
|
|
1996
|
-
...context,
|
|
1997
|
-
_validationErrors: event.errors,
|
|
1998
|
-
})),
|
|
1999
|
-
},
|
|
2000
|
-
},
|
|
2001
|
-
invoke: {
|
|
2002
|
-
src: 'validateSchema',
|
|
2003
|
-
input: ({ context }) => ({ context }),
|
|
2004
|
-
},
|
|
2005
|
-
},
|
|
2006
|
-
error: {},
|
|
2007
|
-
},
|
|
2008
|
-
});
|
|
2009
|
-
|
|
2010
|
-
const logger$1 = debug('seedSdk:helpers:updateSchema');
|
|
2011
|
-
// Schema reloads renameModelProperty deferred and that haven't finished yet
|
|
2012
|
-
const deferredSchemaReloads = new Set();
|
|
2013
|
-
/**
|
|
2014
|
-
* Get the file path for a schema file
|
|
2015
|
-
*/
|
|
2016
|
-
/**
|
|
2017
|
-
* Sanitize a schema name to be filesystem-safe
|
|
2018
|
-
* Replaces all special characters (except alphanumeric, hyphens, underscores) with underscores
|
|
2019
|
-
* Converts spaces to underscores
|
|
2020
|
-
* Removes leading/trailing underscores
|
|
2021
|
-
*
|
|
2022
|
-
* @param name - Schema name to sanitize
|
|
2023
|
-
* @returns Sanitized name safe for use in filenames
|
|
2024
|
-
*/
|
|
2025
|
-
const sanitizeSchemaName = (name) => {
|
|
2026
|
-
return name
|
|
2027
|
-
.replace(/[^a-zA-Z0-9\s_-]/g, '_') // Replace special chars (except spaces, hyphens, underscores) with underscore
|
|
2028
|
-
.replace(/\s+/g, '_') // Convert spaces to underscores
|
|
2029
|
-
.replace(/^_+|_+$/g, '') // Remove leading/trailing underscores
|
|
2030
|
-
.replace(/_+/g, '_'); // Collapse multiple underscores to single
|
|
2031
|
-
};
|
|
2032
|
-
/**
|
|
2033
|
-
* Get the full file path for a schema
|
|
2034
|
-
* Format: {schemaFileId}_{schemaName}_v{version}.json
|
|
2035
|
-
*
|
|
2036
|
-
* The ID-first format ensures all files for a schema group together when sorted alphabetically.
|
|
2037
|
-
*
|
|
2038
|
-
* @param name - Schema name
|
|
2039
|
-
* @param version - Schema version
|
|
2040
|
-
* @param schemaFileId - Schema file ID (required)
|
|
2041
|
-
*/
|
|
2042
|
-
const getSchemaFilePath = (name, version, schemaFileId) => {
|
|
2043
|
-
const path = BaseFileManager.getPathModule();
|
|
2044
|
-
const workingDir = BaseFileManager.getWorkingDir();
|
|
2045
|
-
const sanitizedName = sanitizeSchemaName(name);
|
|
2046
|
-
const filename = schemaFileId
|
|
2047
|
-
? `${schemaFileId}_${sanitizedName}_v${version}.json`
|
|
2048
|
-
: `${sanitizedName}_v${version}.json`;
|
|
2049
|
-
return path.join(workingDir, filename);
|
|
2050
|
-
};
|
|
2051
|
-
/**
|
|
2052
|
-
* Get schemaFileId from database for a schema
|
|
2053
|
-
* @param schemaName - Schema name
|
|
2054
|
-
* @returns Schema file ID
|
|
2055
|
-
* @throws Error if schema not found or missing schemaFileId
|
|
2056
|
-
*/
|
|
2057
|
-
async function getSchemaFileId(schemaName) {
|
|
2058
|
-
const db = BaseDb.getAppDb();
|
|
2059
|
-
if (!db)
|
|
2060
|
-
throw new Error('Database not available');
|
|
2061
|
-
const dbSchema = await db
|
|
2062
|
-
.select()
|
|
2063
|
-
.from(schemas)
|
|
2064
|
-
.where(eq(schemas.name, schemaName))
|
|
2065
|
-
.orderBy(desc(schemas.version))
|
|
2066
|
-
.limit(1);
|
|
2067
|
-
if (dbSchema.length === 0 || !dbSchema[0].schemaFileId) {
|
|
2068
|
-
throw new Error(`Schema ${schemaName} not found in database or missing schemaFileId`);
|
|
2069
|
-
}
|
|
2070
|
-
return dbSchema[0].schemaFileId;
|
|
2071
|
-
}
|
|
2072
|
-
/**
|
|
2073
|
-
* Write the full schema to a new version file (e.g. when new models were added).
|
|
2074
|
-
* Used when _editedProperties contains 'schema:models' and there are no property-level updates.
|
|
2075
|
-
* @param schemaName - Schema name
|
|
2076
|
-
* @param schema - Full schema object (e.g. from _buildModelsFromInstances)
|
|
2077
|
-
* @returns The file path of the new schema version
|
|
2078
|
-
*/
|
|
2079
|
-
async function writeFullSchemaNewVersion(schemaName, schema) {
|
|
2080
|
-
const latestVersion = await getLatestSchemaVersion(schemaName);
|
|
2081
|
-
const newVersion = latestVersion + 1;
|
|
2082
|
-
const schemaWithNewVersion = {
|
|
2083
|
-
...schema,
|
|
2084
|
-
version: newVersion,
|
|
2085
|
-
metadata: {
|
|
2086
|
-
...schema.metadata,
|
|
2087
|
-
updatedAt: new Date().toISOString(),
|
|
2088
|
-
},
|
|
2089
|
-
migrations: [
|
|
2090
|
-
...(schema.migrations || []),
|
|
2091
|
-
{
|
|
2092
|
-
version: newVersion,
|
|
2093
|
-
timestamp: new Date().toISOString(),
|
|
2094
|
-
description: 'New schema version (e.g. new models added)',
|
|
2095
|
-
changes: [{ type: 'full_schema_write' }],
|
|
2096
|
-
},
|
|
2097
|
-
],
|
|
2098
|
-
};
|
|
2099
|
-
const newFilePath = getSchemaFilePath(schemaName, newVersion, schema.id ?? (await getSchemaFileId(schemaName)));
|
|
2100
|
-
const newContent = JSON.stringify(schemaWithNewVersion, null, 2);
|
|
2101
|
-
await BaseFileManager.saveFile(newFilePath, newContent);
|
|
2102
|
-
await BaseFileManager.waitForFileWithContent(newFilePath);
|
|
2103
|
-
logger$1(`Created new schema version ${newVersion} for ${schemaName} at ${newFilePath}`);
|
|
2104
|
-
return newFilePath;
|
|
2105
|
-
}
|
|
2106
|
-
/**
|
|
2107
|
-
* Get model name from modelId
|
|
2108
|
-
* @param modelId - The model ID to look up
|
|
2109
|
-
* @returns The model name, or undefined if not found
|
|
2110
|
-
*/
|
|
2111
|
-
async function getModelNameFromId(modelId) {
|
|
2112
|
-
if (!modelId) {
|
|
2113
|
-
return undefined;
|
|
2114
|
-
}
|
|
2115
|
-
const db = BaseDb.getAppDb();
|
|
2116
|
-
if (!db) {
|
|
2117
|
-
throw new Error('Database not found');
|
|
2118
|
-
}
|
|
2119
|
-
const modelRecords = await db
|
|
2120
|
-
.select()
|
|
2121
|
-
.from(models)
|
|
2122
|
-
.where(eq(models.id, modelId))
|
|
2123
|
-
.limit(1);
|
|
2124
|
-
if (modelRecords.length === 0) {
|
|
2125
|
-
return undefined;
|
|
2126
|
-
}
|
|
2127
|
-
return modelRecords[0].name;
|
|
2128
|
-
}
|
|
2129
|
-
/**
|
|
2130
|
-
* Convert a TProperty/ModelPropertyMachineContext to SchemaPropertyUpdate format
|
|
2131
|
-
* This function converts the internal property representation to the schema file format
|
|
2132
|
-
* @param property - The TProperty instance to convert
|
|
2133
|
-
* @param modelName - The name of the model this property belongs to
|
|
2134
|
-
* @param propertyName - The name of the property
|
|
2135
|
-
* @returns A SchemaPropertyUpdate object ready to be passed to updateModelProperties
|
|
2136
|
-
*/
|
|
2137
|
-
async function convertPropertyToSchemaUpdate(property, modelName, propertyName) {
|
|
2138
|
-
const updates = {};
|
|
2139
|
-
// Convert dataType to type
|
|
2140
|
-
if (property.dataType) {
|
|
2141
|
-
updates.type = property.dataType;
|
|
2142
|
-
}
|
|
2143
|
-
// Handle Relation type
|
|
2144
|
-
if (isDataType(property.dataType, ModelPropertyDataTypes.Relation)) {
|
|
2145
|
-
if (property.ref) {
|
|
2146
|
-
updates.model = property.ref;
|
|
2147
|
-
}
|
|
2148
|
-
else if (property.refModelId) {
|
|
2149
|
-
// If ref is not set but refModelId is, get the model name from the database
|
|
2150
|
-
const refModelName = await getModelNameFromId(property.refModelId);
|
|
2151
|
-
if (refModelName) {
|
|
2152
|
-
updates.model = refModelName;
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
// Handle List type
|
|
2157
|
-
if (isDataType(property.dataType, ModelPropertyDataTypes.List)) {
|
|
2158
|
-
if (property.refValueType) {
|
|
2159
|
-
updates.refValueType = property.refValueType;
|
|
2160
|
-
if (property.ref) {
|
|
2161
|
-
updates.ref = property.ref;
|
|
2162
|
-
}
|
|
2163
|
-
else if (property.refModelId) {
|
|
2164
|
-
// If ref is not set but refModelId is, get the model name from the database
|
|
2165
|
-
const refModelName = await getModelNameFromId(property.refModelId);
|
|
2166
|
-
if (refModelName) {
|
|
2167
|
-
updates.ref = refModelName;
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
// Handle required (validation constraint)
|
|
2173
|
-
if (property.required !== undefined) {
|
|
2174
|
-
updates.required = property.required;
|
|
2175
|
-
}
|
|
2176
|
-
// Handle storage configuration (for Text properties with storage)
|
|
2177
|
-
if (property.storageType || property.localStorageDir || property.filenameSuffix) {
|
|
2178
|
-
const storageType = property.storageType || 'ItemStorage'; // Default to ItemStorage if not specified
|
|
2179
|
-
updates.storage = {
|
|
2180
|
-
type: storageType,
|
|
2181
|
-
};
|
|
2182
|
-
if (property.localStorageDir) {
|
|
2183
|
-
updates.storage.path = property.localStorageDir;
|
|
2184
|
-
}
|
|
2185
|
-
if (property.filenameSuffix) {
|
|
2186
|
-
updates.storage.extension = property.filenameSuffix;
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
return {
|
|
2190
|
-
modelName,
|
|
2191
|
-
propertyName,
|
|
2192
|
-
updates,
|
|
2193
|
-
};
|
|
2194
|
-
}
|
|
2195
|
-
/**
|
|
2196
|
-
* Update model properties in a schema and create a new version
|
|
2197
|
-
* @param schemaName - The name of the schema to update
|
|
2198
|
-
* @param propertyUpdates - Array of property updates to apply
|
|
2199
|
-
* @param modelUpdates - Optional array of model renames
|
|
2200
|
-
* @returns The file path of the new schema version
|
|
2201
|
-
* @throws Error if schema not found or updates are invalid
|
|
2202
|
-
*/
|
|
2203
|
-
async function updateModelProperties(schemaName, propertyUpdates, modelUpdates) {
|
|
2204
|
-
// Get the latest version of the schema
|
|
2205
|
-
const latestVersion = await getLatestSchemaVersion(schemaName);
|
|
2206
|
-
if (latestVersion === 0) {
|
|
2207
|
-
throw new Error(`Schema ${schemaName} not found`);
|
|
2208
|
-
}
|
|
2209
|
-
// Get schemaFileId from database
|
|
2210
|
-
const schemaFileId = await getSchemaFileId(schemaName);
|
|
2211
|
-
// Load the latest schema file
|
|
2212
|
-
const latestFilePath = getSchemaFilePath(schemaName, latestVersion, schemaFileId);
|
|
2213
|
-
const content = await BaseFileManager.readFileAsString(latestFilePath);
|
|
2214
|
-
const schemaFile = JSON.parse(content);
|
|
2215
|
-
if (!schemaFile.$schema) {
|
|
2216
|
-
throw new Error(`Schema file ${latestFilePath} is not a complete schema file`);
|
|
2217
|
-
}
|
|
2218
|
-
// Create a copy of the schema for the new version
|
|
2219
|
-
// Preserve schema ID and all model/property IDs
|
|
2220
|
-
const newVersion = latestVersion + 1;
|
|
2221
|
-
const updatedSchema = {
|
|
2222
|
-
...schemaFile,
|
|
2223
|
-
version: newVersion,
|
|
2224
|
-
// Preserve schema ID from previous version
|
|
2225
|
-
id: schemaFile.id,
|
|
2226
|
-
metadata: {
|
|
2227
|
-
...schemaFile.metadata,
|
|
2228
|
-
updatedAt: new Date().toISOString(),
|
|
2229
|
-
},
|
|
2230
|
-
// Deep copy models to preserve IDs
|
|
2231
|
-
models: Object.fromEntries(Object.entries(schemaFile.models).map(([modelName, model]) => [
|
|
2232
|
-
modelName,
|
|
2233
|
-
{
|
|
2234
|
-
...model,
|
|
2235
|
-
// Preserve model ID
|
|
2236
|
-
id: model.id,
|
|
2237
|
-
// Deep copy properties to preserve IDs
|
|
2238
|
-
properties: Object.fromEntries(Object.entries(model.properties).map(([propName, prop]) => [
|
|
2239
|
-
propName,
|
|
2240
|
-
{
|
|
2241
|
-
...prop,
|
|
2242
|
-
// Preserve property ID
|
|
2243
|
-
id: prop.id,
|
|
2244
|
-
},
|
|
2245
|
-
])),
|
|
2246
|
-
},
|
|
2247
|
-
])),
|
|
2248
|
-
migrations: [
|
|
2249
|
-
...schemaFile.migrations,
|
|
2250
|
-
{
|
|
2251
|
-
version: newVersion,
|
|
2252
|
-
timestamp: new Date().toISOString(),
|
|
2253
|
-
description: `Updated model properties: ${propertyUpdates.map(u => `${u.modelName}.${u.propertyName}`).join(', ')}`,
|
|
2254
|
-
changes: propertyUpdates.map(update => ({
|
|
2255
|
-
type: 'property_update',
|
|
2256
|
-
modelName: update.modelName,
|
|
2257
|
-
propertyName: update.propertyName,
|
|
2258
|
-
updates: update.updates,
|
|
2259
|
-
})),
|
|
2260
|
-
},
|
|
2261
|
-
],
|
|
2262
|
-
};
|
|
2263
|
-
// Apply model renames first (if any)
|
|
2264
|
-
if (modelUpdates && modelUpdates.length > 0) {
|
|
2265
|
-
for (const modelUpdate of modelUpdates) {
|
|
2266
|
-
if (updatedSchema.models[modelUpdate.oldName]) {
|
|
2267
|
-
// Rename the model in the models object
|
|
2268
|
-
updatedSchema.models[modelUpdate.newName] = updatedSchema.models[modelUpdate.oldName];
|
|
2269
|
-
delete updatedSchema.models[modelUpdate.oldName];
|
|
2270
|
-
// Update any property references to this model
|
|
2271
|
-
for (const modelName in updatedSchema.models) {
|
|
2272
|
-
const model = updatedSchema.models[modelName];
|
|
2273
|
-
for (const propertyName in model.properties) {
|
|
2274
|
-
const property = model.properties[propertyName];
|
|
2275
|
-
if (property.model === modelUpdate.oldName) {
|
|
2276
|
-
property.model = modelUpdate.newName;
|
|
2277
|
-
}
|
|
2278
|
-
if (property.ref === modelUpdate.oldName && (isDataType(property.type, ModelPropertyDataTypes.List) || isDataType(property.dataType, ModelPropertyDataTypes.List))) {
|
|
2279
|
-
property.ref = modelUpdate.newName;
|
|
2280
|
-
}
|
|
2281
|
-
// Legacy: items.model
|
|
2282
|
-
if (property.items?.model === modelUpdate.oldName) {
|
|
2283
|
-
property.items.model = modelUpdate.newName;
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2287
|
-
// Add to migration changes
|
|
2288
|
-
updatedSchema.migrations[updatedSchema.migrations.length - 1].changes.push({
|
|
2289
|
-
type: 'model_rename',
|
|
2290
|
-
oldName: modelUpdate.oldName,
|
|
2291
|
-
newName: modelUpdate.newName,
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
// Apply property updates
|
|
2297
|
-
for (const update of propertyUpdates) {
|
|
2298
|
-
const model = updatedSchema.models[update.modelName];
|
|
2299
|
-
if (!model) {
|
|
2300
|
-
throw new Error(`Model ${update.modelName} not found in schema ${schemaName}`);
|
|
2301
|
-
}
|
|
2302
|
-
if (!model.properties[update.propertyName]) {
|
|
2303
|
-
throw new Error(`Property ${update.propertyName} not found in model ${update.modelName} of schema ${schemaName}`);
|
|
2304
|
-
}
|
|
2305
|
-
// Update the property with new values
|
|
2306
|
-
const property = model.properties[update.propertyName];
|
|
2307
|
-
Object.assign(property, update.updates);
|
|
2308
|
-
}
|
|
2309
|
-
// Write the new schema version to file using ID-based naming (preferred)
|
|
2310
|
-
const newFilePath = getSchemaFilePath(schemaName, newVersion, updatedSchema.id ?? undefined);
|
|
2311
|
-
const newContent = JSON.stringify(updatedSchema, null, 2);
|
|
2312
|
-
await BaseFileManager.saveFile(newFilePath, newContent);
|
|
2313
|
-
// Wait for the file to be available with content (important for browser/OPFS where writes may not be immediately readable)
|
|
2314
|
-
await BaseFileManager.waitForFileWithContent(newFilePath);
|
|
2315
|
-
logger$1(`Created new schema version ${newVersion} for ${schemaName} at ${newFilePath}`);
|
|
2316
|
-
// Load the new schema file to process models and add them to the database
|
|
2317
|
-
// Extract model renames from migrations to pass to database update
|
|
2318
|
-
const modelRenames = new Map();
|
|
2319
|
-
const latestMigration = updatedSchema.migrations[updatedSchema.migrations.length - 1];
|
|
2320
|
-
for (const change of latestMigration.changes) {
|
|
2321
|
-
if (change.type === 'model_rename') {
|
|
2322
|
-
modelRenames.set(change.oldName, change.newName);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
// Load schema with model renames handled
|
|
2326
|
-
await loadSchemaWithRenames(newFilePath, modelRenames);
|
|
2327
|
-
return newFilePath;
|
|
2328
|
-
}
|
|
2329
|
-
/**
|
|
2330
|
-
* Load a schema file and handle model renames in the database
|
|
2331
|
-
* This is a helper function that processes model renames before loading
|
|
2332
|
-
*/
|
|
2333
|
-
async function loadSchemaWithRenames(schemaFilePath, modelRenames) {
|
|
2334
|
-
const content = await BaseFileManager.readFileAsString(schemaFilePath);
|
|
2335
|
-
const schemaFile = JSON.parse(content);
|
|
2336
|
-
if (!schemaFile.$schema) {
|
|
2337
|
-
throw new Error(`File ${schemaFilePath} is not a complete schema file (missing $schema field).`);
|
|
2338
|
-
}
|
|
2339
|
-
const schemaName = schemaFile.metadata?.name;
|
|
2340
|
-
const version = schemaFile.version;
|
|
2341
|
-
if (!schemaName) {
|
|
2342
|
-
throw new Error('Schema name is required in metadata.name');
|
|
2343
|
-
}
|
|
2344
|
-
// Convert to JsonImportSchema format for processing
|
|
2345
|
-
// Remove id fields for JsonImportSchema format (they're not part of the import format)
|
|
2346
|
-
const importData = {
|
|
2347
|
-
name: schemaName,
|
|
2348
|
-
models: Object.fromEntries(Object.entries(schemaFile.models).map(([modelName, model]) => [
|
|
2349
|
-
modelName,
|
|
2350
|
-
{
|
|
2351
|
-
description: model.description,
|
|
2352
|
-
properties: Object.fromEntries(Object.entries(model.properties).map(([propName, prop]) => [
|
|
2353
|
-
propName,
|
|
2354
|
-
{
|
|
2355
|
-
type: prop.type || 'string', // Ensure type is present
|
|
2356
|
-
...prop,
|
|
2357
|
-
// Remove id field for import format (not part of JsonImportSchema)
|
|
2358
|
-
},
|
|
2359
|
-
])),
|
|
2360
|
-
indexes: model.indexes,
|
|
2361
|
-
},
|
|
2362
|
-
])),
|
|
2363
|
-
};
|
|
2364
|
-
// Generate schema ID if missing
|
|
2365
|
-
if (!schemaFile.id) {
|
|
2366
|
-
schemaFile.id = generateId();
|
|
2367
|
-
logger$1('Generated schema ID for schema:', schemaFile.id);
|
|
2368
|
-
}
|
|
2369
|
-
// Extract schemaFileIds from JSON file and generate missing ones BEFORE creating models
|
|
2370
|
-
// This ensures Model instances are created with correct IDs
|
|
2371
|
-
const modelFileIds = new Map();
|
|
2372
|
-
const propertyFileIds = new Map();
|
|
2373
|
-
for (const [modelName, model] of Object.entries(schemaFile.models)) {
|
|
2374
|
-
// Generate model ID if missing
|
|
2375
|
-
if (!model.id) {
|
|
2376
|
-
model.id = generateId();
|
|
2377
|
-
logger$1(`Generated model ID for ${modelName}:`, model.id);
|
|
2378
|
-
}
|
|
2379
|
-
modelFileIds.set(modelName, model.id);
|
|
2380
|
-
const propIds = new Map();
|
|
2381
|
-
for (const [propName, prop] of Object.entries(model.properties)) {
|
|
2382
|
-
// Generate property ID if missing
|
|
2383
|
-
if (!prop.id) {
|
|
2384
|
-
prop.id = generateId();
|
|
2385
|
-
logger$1(`Generated property ID for ${modelName}.${propName}:`, prop.id);
|
|
2386
|
-
}
|
|
2387
|
-
propIds.set(propName, prop.id);
|
|
2388
|
-
}
|
|
2389
|
-
if (propIds.size > 0) {
|
|
2390
|
-
propertyFileIds.set(modelName, propIds);
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
// Convert schema file metadata to schema input for database
|
|
2394
|
-
const schemaInput = {
|
|
2395
|
-
name: schemaName,
|
|
2396
|
-
version,
|
|
2397
|
-
schemaFileId: schemaFile.id || null,
|
|
2398
|
-
schemaData: null,
|
|
2399
|
-
isDraft: false,
|
|
2400
|
-
isEdited: false,
|
|
2401
|
-
createdAt: new Date(schemaFile.metadata.createdAt).getTime(),
|
|
2402
|
-
updatedAt: new Date(schemaFile.metadata.updatedAt).getTime(),
|
|
2403
|
-
};
|
|
2404
|
-
// Use dynamic import to break circular dependency
|
|
2405
|
-
const _mod_7 = await import('./index-rOTW-2QZ.js');
|
|
2406
|
-
const _ns_7 = _mod_7.bf;
|
|
2407
|
-
const dbMod = _ns_7;
|
|
2408
|
-
const { addSchemaToDb, addModelsToDb } = dbMod;
|
|
2409
|
-
// Add schema to database with schemaFileId
|
|
2410
|
-
const schemaRecord = await addSchemaToDb(schemaInput, schemaFile.id);
|
|
2411
|
-
// A cached Model keeps the properties it was created with (Model.create ignores the ones passed),
|
|
2412
|
-
// so addModelsToDb must take changed properties from this file, as in loadSchemaFromFile
|
|
2413
|
-
const propertyDefinitions = new Map(Object.entries(importData.models).map(([modelName, modelDef]) => [
|
|
2414
|
-
modelName,
|
|
2415
|
-
jsonPropertiesToSchemaProperties(modelName, modelDef, propertyFileIds.get(modelName)),
|
|
2416
|
-
]));
|
|
2417
|
-
// Convert JSON models to Model classes, passing modelFileIds and propertyFileIds so Model instances use correct IDs.
|
|
2418
|
-
// addModelsToDb below writes their rows; the models adopt them once it has run.
|
|
2419
|
-
const rowsWritten = createRowsWrittenSignal();
|
|
2420
|
-
const modelDefinitions = await createModelsFromJson(importData, modelFileIds, propertyFileIds, rowsWritten.promise);
|
|
2421
|
-
// Add models to database with model renames handled and schemaFileIds
|
|
2422
|
-
await addModelsToDb(modelDefinitions, schemaRecord, modelRenames, {
|
|
2423
|
-
schemaFileId: schemaFile.id,
|
|
2424
|
-
modelFileIds,
|
|
2425
|
-
propertyFileIds,
|
|
2426
|
-
propertyDefinitions,
|
|
2427
|
-
}).finally(rowsWritten.settle);
|
|
2428
|
-
// Models are now Model instances, no registration needed
|
|
2429
|
-
// They should be created via Model.create() and are accessible via Model static methods
|
|
2430
|
-
for (const [modelName] of Object.entries(modelDefinitions)) {
|
|
2431
|
-
logger$1('loadSchemaWithRenames - model available:', modelName);
|
|
2432
|
-
}
|
|
2433
|
-
return schemaFilePath;
|
|
2434
|
-
}
|
|
2435
|
-
/**
|
|
2436
|
-
* Rename a property in a model
|
|
2437
|
-
* This is a convenience function that updates the property name
|
|
2438
|
-
* Note: This creates a new property and you may want to handle the old property separately
|
|
2439
|
-
* @param schemaName - The name of the schema
|
|
2440
|
-
* @param modelName - The name of the model
|
|
2441
|
-
* @param oldPropertyName - The current property name
|
|
2442
|
-
* @param newPropertyName - The new property name
|
|
2443
|
-
* @returns The file path of the new schema version
|
|
2444
|
-
*/
|
|
2445
|
-
async function renameModelProperty(schemaName, modelName, oldPropertyName, newPropertyName) {
|
|
2446
|
-
// Get the latest version
|
|
2447
|
-
const latestVersion = await getLatestSchemaVersion(schemaName);
|
|
2448
|
-
if (latestVersion === 0) {
|
|
2449
|
-
throw new Error(`Schema ${schemaName} not found`);
|
|
2450
|
-
}
|
|
2451
|
-
// Get schemaFileId from database
|
|
2452
|
-
const schemaFileId = await getSchemaFileId(schemaName);
|
|
2453
|
-
// Load the latest schema file
|
|
2454
|
-
const latestFilePath = getSchemaFilePath(schemaName, latestVersion, schemaFileId);
|
|
2455
|
-
const content = await BaseFileManager.readFileAsString(latestFilePath);
|
|
2456
|
-
const schemaFile = JSON.parse(content);
|
|
2457
|
-
if (!schemaFile.$schema) {
|
|
2458
|
-
throw new Error(`Schema file ${latestFilePath} is not a complete schema file`);
|
|
2459
|
-
}
|
|
2460
|
-
const model = schemaFile.models[modelName];
|
|
2461
|
-
if (!model) {
|
|
2462
|
-
throw new Error(`Model ${modelName} not found in schema ${schemaName}`);
|
|
2463
|
-
}
|
|
2464
|
-
if (!model.properties[oldPropertyName]) {
|
|
2465
|
-
throw new Error(`Property ${oldPropertyName} not found in model ${modelName} of schema ${schemaName}`);
|
|
2466
|
-
}
|
|
2467
|
-
if (model.properties[newPropertyName]) {
|
|
2468
|
-
throw new Error(`Property ${newPropertyName} already exists in model ${modelName} of schema ${schemaName}`);
|
|
2469
|
-
}
|
|
2470
|
-
// Create new version with renamed property
|
|
2471
|
-
// Preserve all IDs - when renaming, we keep the same property ID
|
|
2472
|
-
const newVersion = latestVersion + 1;
|
|
2473
|
-
const oldProperty = model.properties[oldPropertyName];
|
|
2474
|
-
const updatedSchema = {
|
|
2475
|
-
...schemaFile,
|
|
2476
|
-
version: newVersion,
|
|
2477
|
-
id: schemaFile.id, // Preserve schema ID
|
|
2478
|
-
metadata: {
|
|
2479
|
-
...schemaFile.metadata,
|
|
2480
|
-
updatedAt: new Date().toISOString(),
|
|
2481
|
-
},
|
|
2482
|
-
models: {
|
|
2483
|
-
...schemaFile.models,
|
|
2484
|
-
[modelName]: {
|
|
2485
|
-
...model,
|
|
2486
|
-
id: model.id, // Preserve model ID
|
|
2487
|
-
properties: {
|
|
2488
|
-
...Object.fromEntries(Object.entries(model.properties)
|
|
2489
|
-
.filter(([name]) => name !== oldPropertyName)
|
|
2490
|
-
.map(([name, prop]) => [name, { ...prop, id: prop.id }])),
|
|
2491
|
-
[newPropertyName]: {
|
|
2492
|
-
...oldProperty,
|
|
2493
|
-
id: oldProperty.id, // Preserve property ID when renaming
|
|
2494
|
-
},
|
|
2495
|
-
},
|
|
2496
|
-
},
|
|
2497
|
-
},
|
|
2498
|
-
migrations: [
|
|
2499
|
-
...schemaFile.migrations,
|
|
2500
|
-
{
|
|
2501
|
-
version: newVersion,
|
|
2502
|
-
timestamp: new Date().toISOString(),
|
|
2503
|
-
description: `Renamed property ${modelName}.${oldPropertyName} to ${newPropertyName}`,
|
|
2504
|
-
changes: [
|
|
2505
|
-
{
|
|
2506
|
-
type: 'property_rename',
|
|
2507
|
-
modelName,
|
|
2508
|
-
oldPropertyName,
|
|
2509
|
-
newPropertyName,
|
|
2510
|
-
},
|
|
2511
|
-
],
|
|
2512
|
-
},
|
|
2513
|
-
],
|
|
2514
|
-
};
|
|
2515
|
-
// Remove the old property name
|
|
2516
|
-
delete updatedSchema.models[modelName].properties[oldPropertyName];
|
|
2517
|
-
// Write the new schema version using ID-based naming (preferred)
|
|
2518
|
-
const newFilePath = getSchemaFilePath(schemaName, newVersion, updatedSchema.id ?? undefined);
|
|
2519
|
-
const newContent = JSON.stringify(updatedSchema, null, 2);
|
|
2520
|
-
// Ensure the directory exists before saving
|
|
2521
|
-
const path = BaseFileManager.getPathModule();
|
|
2522
|
-
const dir = path.dirname(newFilePath);
|
|
2523
|
-
await BaseFileManager.createDirIfNotExists(dir);
|
|
2524
|
-
await BaseFileManager.saveFile(newFilePath, newContent);
|
|
2525
|
-
// Wait for the file to be available with content (important for browser/OPFS where writes may not be immediately readable)
|
|
2526
|
-
await BaseFileManager.waitForFileWithContent(newFilePath);
|
|
2527
|
-
logger$1(`Renamed property ${oldPropertyName} to ${newPropertyName} in schema ${schemaName} v${newVersion}`);
|
|
2528
|
-
// Migrate metadata rows so ItemProperty values follow the new name (prevents duplicate ItemProperties)
|
|
2529
|
-
const _mod_8 = await import('./index-rOTW-2QZ.js');
|
|
2530
|
-
const _ns_8 = _mod_8.bf;
|
|
2531
|
-
const { migrateMetadataForPropertyRename } = _ns_8;
|
|
2532
|
-
const migratedCount = await migrateMetadataForPropertyRename(modelName, oldPropertyName, newPropertyName, oldProperty.id, { schemaName });
|
|
2533
|
-
if (migratedCount > 0) {
|
|
2534
|
-
logger$1(`Migrated ${migratedCount} metadata rows for property rename`);
|
|
2535
|
-
}
|
|
2536
|
-
// Defer loadSchemaFromFile to avoid sync cascade: it triggers addSchemaToDb/addModelsToDb,
|
|
2537
|
-
// which post to BroadcastChannel and cause React invalidates. Running that synchronously
|
|
2538
|
-
// can interrupt the save flow and cause freezes (e.g. updateContext/refetch loops).
|
|
2539
|
-
const reload = new Promise((resolve) => setTimeout(resolve, 100))
|
|
2540
|
-
.then(() => loadSchemaFromFile(newFilePath))
|
|
2541
|
-
.then(() => { }, (err) => {
|
|
2542
|
-
logger$1('Deferred loadSchemaFromFile after rename failed:', err);
|
|
2543
|
-
})
|
|
2544
|
-
.finally(() => deferredSchemaReloads.delete(reload));
|
|
2545
|
-
deferredSchemaReloads.add(reload);
|
|
2546
|
-
return newFilePath;
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
var updateSchema = /*#__PURE__*/Object.freeze({
|
|
2550
|
-
__proto__: null,
|
|
2551
|
-
convertPropertyToSchemaUpdate: convertPropertyToSchemaUpdate,
|
|
2552
|
-
getModelNameFromId: getModelNameFromId,
|
|
2553
|
-
renameModelProperty: renameModelProperty,
|
|
2554
|
-
updateModelProperties: updateModelProperties,
|
|
2555
|
-
writeFullSchemaNewVersion: writeFullSchemaNewVersion
|
|
2556
|
-
});
|
|
2557
|
-
|
|
2558
|
-
const logger = debug('seedSdk:schema:saveNewVersion');
|
|
2559
|
-
const saveDraftLogger = debug('seedSdk:schema:saveDraftToDb');
|
|
2560
|
-
// Non-serializable resources per Schema instance (subscriptions, timers, etc.)
|
|
2561
|
-
const schemaInstanceState = new ActorKeyedState();
|
|
2562
|
-
// Cache client initialization state globally to avoid repeated checks
|
|
2563
|
-
let cachedClientInitialized = null;
|
|
2564
|
-
let clientCheckTime = 0;
|
|
2565
|
-
const CLIENT_CHECK_CACHE_MS = 50; // Cache for 50ms to avoid excessive checks (reduced from 100ms for faster updates)
|
|
2566
|
-
// Define tracked properties for the Proxy
|
|
2567
|
-
// These properties will be read from/written to the actor context
|
|
2568
|
-
const TRACKED_PROPERTIES = [
|
|
2569
|
-
'id', // schemaFileId (string) - public ID
|
|
2570
|
-
'$schema',
|
|
2571
|
-
'version',
|
|
2572
|
-
'metadata',
|
|
2573
|
-
'enums',
|
|
2574
|
-
'migrations',
|
|
2575
|
-
'name', // metadata.name (flattened)
|
|
2576
|
-
'createdAt', // metadata.createdAt (flattened)
|
|
2577
|
-
'updatedAt', // metadata.updatedAt (flattened)
|
|
2578
|
-
];
|
|
2579
|
-
class Schema {
|
|
2580
|
-
constructor(schemaName) {
|
|
2581
|
-
const serviceInput = {
|
|
2582
|
-
schemaName,
|
|
2583
|
-
};
|
|
2584
|
-
this._service = createActor(schemaMachine, {
|
|
2585
|
-
input: serviceInput,
|
|
2586
|
-
});
|
|
2587
|
-
this._service.start();
|
|
2588
|
-
// Initialize instance state in WeakMap (only non-serializable resources)
|
|
2589
|
-
schemaInstanceState.set(this, {
|
|
2590
|
-
liveQuerySubscription: null,
|
|
2591
|
-
});
|
|
2592
|
-
// Set up liveQuery subscription for cross-instance model updates
|
|
2593
|
-
// This will be initialized once we have the schemaId
|
|
2594
|
-
this._setupLiveQuerySubscription();
|
|
2595
|
-
// Subscribe to schema context changes to update cache keys when schemaFileId becomes available
|
|
2596
|
-
this._service.subscribe((snapshot) => {
|
|
2597
|
-
// Update cache to use id (schemaFileId) as key once we have it (only once)
|
|
2598
|
-
if (snapshot.value === 'idle' && snapshot.context.metadata?.name && snapshot.context.id) {
|
|
2599
|
-
// Use a static Set to track which schemas have had their cache key updated
|
|
2600
|
-
const cacheKeyUpdatedSet = Schema._cacheKeyUpdatedSet || new Set();
|
|
2601
|
-
if (!Schema._cacheKeyUpdatedSet) {
|
|
2602
|
-
Schema._cacheKeyUpdatedSet = cacheKeyUpdatedSet;
|
|
2603
|
-
}
|
|
2604
|
-
if (!cacheKeyUpdatedSet.has(snapshot.context.id)) {
|
|
2605
|
-
Schema._updateCacheKey(snapshot.context.schemaName, snapshot.context.id);
|
|
2606
|
-
cacheKeyUpdatedSet.add(snapshot.context.id);
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
});
|
|
2610
|
-
// Note: Property getters/setters are now handled by the Proxy in create()
|
|
2611
|
-
}
|
|
2612
|
-
static create(schemaName, options) {
|
|
2613
|
-
if (!schemaName) {
|
|
2614
|
-
throw new Error('Schema name is required');
|
|
2615
|
-
}
|
|
2616
|
-
const waitForReady = options?.waitForReady !== false;
|
|
2617
|
-
const readyTimeout = options?.readyTimeout ?? DEFAULT_READY_TIMEOUT_MS;
|
|
2618
|
-
const holds = options?.hold === false ? 0 : 1;
|
|
2619
|
-
// First, check if we have an instance cached by name
|
|
2620
|
-
const cached = this.instanceCacheByName.get(schemaName);
|
|
2621
|
-
if (cached) {
|
|
2622
|
-
cached.refCount += holds;
|
|
2623
|
-
const { instance } = cached;
|
|
2624
|
-
if (!waitForReady)
|
|
2625
|
-
return instance;
|
|
2626
|
-
return waitForEntityIdle(instance, { timeout: readyTimeout }).then(() => instance);
|
|
2627
|
-
}
|
|
2628
|
-
// Deduplicate concurrent creates for same schema (prevents many parallel checkExistingSchema loads).
|
|
2629
|
-
// The name cache is set before a pending load is recorded, so a hold was already taken above.
|
|
2630
|
-
if (waitForReady) {
|
|
2631
|
-
const pending = this.pendingSchemaByName.get(schemaName);
|
|
2632
|
-
if (pending) {
|
|
2633
|
-
return pending;
|
|
2634
|
-
}
|
|
2635
|
-
}
|
|
2636
|
-
// Create new instance
|
|
2637
|
-
const newInstance = new this(schemaName);
|
|
2638
|
-
// Wrap instance in Proxy for reactive property access
|
|
2639
|
-
const proxiedInstance = createReactiveProxy({
|
|
2640
|
-
instance: newInstance,
|
|
2641
|
-
service: newInstance._service,
|
|
2642
|
-
trackedProperties: TRACKED_PROPERTIES,
|
|
2643
|
-
getContext: (instance) => {
|
|
2644
|
-
// Handle special cases like metadata.name, models array conversion
|
|
2645
|
-
const context = instance._getSnapshotContext();
|
|
2646
|
-
// Get the schema name, ensuring it's never the ID
|
|
2647
|
-
// Prefer metadata.name, then schemaName (but only if it's not the ID)
|
|
2648
|
-
const schemaFileId = context.id; // id is now the schemaFileId (string)
|
|
2649
|
-
let name = context.metadata?.name;
|
|
2650
|
-
// If metadata.name is not available, use schemaName but only if it's not the ID
|
|
2651
|
-
// This prevents returning the ID when the schema is still loading
|
|
2652
|
-
if (!name) {
|
|
2653
|
-
name = (context.schemaName && context.schemaName !== schemaFileId)
|
|
2654
|
-
? context.schemaName
|
|
2655
|
-
: undefined;
|
|
2656
|
-
}
|
|
2657
|
-
// Final fallback - if we still don't have a name, use schemaName (even if it might be the ID)
|
|
2658
|
-
// This handles edge cases during loading
|
|
2659
|
-
if (!name) {
|
|
2660
|
-
name = context.schemaName;
|
|
2661
|
-
}
|
|
2662
|
-
// CRITICAL: Always create a new metadata object to ensure React detects changes
|
|
2663
|
-
// Even if the metadata content is the same, we need a new reference so React re-renders
|
|
2664
|
-
const metadata = context.metadata ? {
|
|
2665
|
-
...context.metadata,
|
|
2666
|
-
} : undefined;
|
|
2667
|
-
return {
|
|
2668
|
-
...context,
|
|
2669
|
-
// Always return a new metadata object reference so React detects changes
|
|
2670
|
-
metadata,
|
|
2671
|
-
// Flatten metadata properties to top level for convenience
|
|
2672
|
-
// Fall back to schemaName if metadata.name is not available (handles loading states)
|
|
2673
|
-
name,
|
|
2674
|
-
createdAt: context.metadata?.createdAt,
|
|
2675
|
-
updatedAt: context.metadata?.updatedAt,
|
|
2676
|
-
};
|
|
2677
|
-
},
|
|
2678
|
-
sendUpdate: (instance, prop, value) => {
|
|
2679
|
-
// Handle special property updates
|
|
2680
|
-
if (prop === 'name') {
|
|
2681
|
-
// Update both metadata.name and schemaName
|
|
2682
|
-
const context = instance._getSnapshotContext();
|
|
2683
|
-
const currentMetadata = context.metadata || {
|
|
2684
|
-
name: schemaName,
|
|
2685
|
-
createdAt: new Date().toISOString(),
|
|
2686
|
-
updatedAt: new Date().toISOString(),
|
|
2687
|
-
};
|
|
2688
|
-
const oldName = currentMetadata.name || context.schemaName;
|
|
2689
|
-
const newName = value;
|
|
2690
|
-
// Only update if the name actually changed
|
|
2691
|
-
if (oldName !== newName) {
|
|
2692
|
-
logger(`Updating schema name from "${oldName}" to "${newName}"`);
|
|
2693
|
-
// Check if service is still running before sending events
|
|
2694
|
-
let snapshot = instance._service.getSnapshot();
|
|
2695
|
-
const wasServiceStopped = snapshot.status === 'stopped';
|
|
2696
|
-
if (wasServiceStopped) {
|
|
2697
|
-
// CRITICAL: Update the schemaName in the service context BEFORE restarting
|
|
2698
|
-
// This ensures that when loadOrCreateSchema runs, it looks for the schema with the NEW name
|
|
2699
|
-
// We need to do this by sending updateContext to a running service, but if it's stopped,
|
|
2700
|
-
// we need to restart it first, update, then it will reload with the new name
|
|
2701
|
-
logger(`Service is stopped, will update schemaName to "${newName}" then restart`);
|
|
2702
|
-
// Start the service first
|
|
2703
|
-
instance._service.start();
|
|
2704
|
-
// Immediately update the schemaName so loadOrCreateSchema uses the new name
|
|
2705
|
-
// This must happen before loadOrCreateSchema runs
|
|
2706
|
-
instance._service.send({
|
|
2707
|
-
type: 'updateContext',
|
|
2708
|
-
schemaName: newName,
|
|
2709
|
-
});
|
|
2710
|
-
logger(`Updated schemaName to "${newName}" before loadOrCreateSchema runs`);
|
|
2711
|
-
// Re-check snapshot after restart - service might be loading or already idle
|
|
2712
|
-
snapshot = instance._service.getSnapshot();
|
|
2713
|
-
}
|
|
2714
|
-
// Build the update event with the new name
|
|
2715
|
-
const updateEvent = {
|
|
2716
|
-
type: 'updateContext',
|
|
2717
|
-
schemaName: newName,
|
|
2718
|
-
metadata: {
|
|
2719
|
-
...(currentMetadata || {
|
|
2720
|
-
name: newName,
|
|
2721
|
-
createdAt: new Date().toISOString(),
|
|
2722
|
-
updatedAt: new Date().toISOString(),
|
|
2723
|
-
}),
|
|
2724
|
-
name: newName, // Always use the new name
|
|
2725
|
-
updatedAt: new Date().toISOString(), // Always update timestamp
|
|
2726
|
-
},
|
|
2727
|
-
};
|
|
2728
|
-
// Check current state after potential restart
|
|
2729
|
-
const currentState = snapshot.value;
|
|
2730
|
-
// Check if state is a loading state object (XState v5 nested states)
|
|
2731
|
-
const isServiceLoading = typeof currentState === 'object' && 'loading' in currentState;
|
|
2732
|
-
// If service is loading (or was just restarted and might be loading), wait for it to finish
|
|
2733
|
-
// Otherwise loadOrCreateSchemaSuccess might overwrite our update with old data
|
|
2734
|
-
if (isServiceLoading || wasServiceStopped) {
|
|
2735
|
-
logger(`Service is ${isServiceLoading ? 'loading' : 'was stopped'}, will send updateContext after loading completes (current state: ${currentState})`);
|
|
2736
|
-
// Helper function to send the update
|
|
2737
|
-
const sendUpdate = () => {
|
|
2738
|
-
logger(`Sending updateContext with newName="${newName}"`);
|
|
2739
|
-
instance._service.send(updateEvent);
|
|
2740
|
-
// Verify the update
|
|
2741
|
-
setTimeout(() => {
|
|
2742
|
-
const verifySnapshot = instance._service.getSnapshot();
|
|
2743
|
-
logger(`After updateContext: context schemaName="${verifySnapshot.context.schemaName}", metadata.name="${verifySnapshot.context.metadata?.name}"`);
|
|
2744
|
-
}, 0);
|
|
2745
|
-
};
|
|
2746
|
-
// Check if already idle (might have loaded synchronously)
|
|
2747
|
-
if (currentState === 'idle') {
|
|
2748
|
-
// Service is already idle, but loadOrCreateSchemaSuccess might have just fired
|
|
2749
|
-
// Use a small delay to ensure loadOrCreateSchemaSuccess has been processed
|
|
2750
|
-
logger(`Service is already idle after restart, will send updateContext after brief delay to ensure loadOrCreateSchemaSuccess processed`);
|
|
2751
|
-
setTimeout(() => {
|
|
2752
|
-
sendUpdate();
|
|
2753
|
-
}, 10);
|
|
2754
|
-
}
|
|
2755
|
-
else {
|
|
2756
|
-
// Subscribe to wait for loading to complete, then send the update
|
|
2757
|
-
// Use a small delay after idle to ensure loadOrCreateSchemaSuccess has been processed
|
|
2758
|
-
const loadingSubscription = instance._service.subscribe((snapshot) => {
|
|
2759
|
-
if (snapshot.value === 'idle') {
|
|
2760
|
-
loadingSubscription.unsubscribe();
|
|
2761
|
-
logger(`Service finished loading, will send updateContext with newName="${newName}" after brief delay`);
|
|
2762
|
-
setTimeout(() => {
|
|
2763
|
-
sendUpdate();
|
|
2764
|
-
}, 10);
|
|
2765
|
-
}
|
|
2766
|
-
else if (snapshot.value === 'error') {
|
|
2767
|
-
loadingSubscription.unsubscribe();
|
|
2768
|
-
logger(`Service failed to load, cannot update context`);
|
|
2769
|
-
}
|
|
2770
|
-
});
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
else {
|
|
2774
|
-
// Service is already idle, send update immediately
|
|
2775
|
-
logger(`Service is already idle, sending updateContext immediately: newName="${newName}", current context schemaName="${snapshot.context.schemaName}"`);
|
|
2776
|
-
instance._service.send(updateEvent);
|
|
2777
|
-
// Verify the update
|
|
2778
|
-
setTimeout(() => {
|
|
2779
|
-
const verifySnapshot = instance._service.getSnapshot();
|
|
2780
|
-
logger(`After updateContext: context schemaName="${verifySnapshot.context.schemaName}", metadata.name="${verifySnapshot.context.metadata?.name}"`);
|
|
2781
|
-
}, 0);
|
|
2782
|
-
}
|
|
2783
|
-
// Mark schema as draft when name changes
|
|
2784
|
-
instance._service.send({
|
|
2785
|
-
type: 'markAsDraft',
|
|
2786
|
-
propertyKey: 'schema:name', // Special key for schema name changes
|
|
2787
|
-
});
|
|
2788
|
-
// Save draft to database immediately so changes persist
|
|
2789
|
-
saveDraftLogger(`Name change detected, calling _saveDraftToDb (oldName: "${oldName}", newName: "${newName}")`);
|
|
2790
|
-
trackDraftSave(instance._service, instance._saveDraftToDb(oldName, newName), `Schema "${newName}"`);
|
|
2791
|
-
}
|
|
2792
|
-
else {
|
|
2793
|
-
logger(`Schema name unchanged: "${oldName}"`);
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
else if (prop === 'models') {
|
|
2797
|
-
// Models are read-only, computed from Model instances. Array assignment was removed because
|
|
2798
|
-
// the draft save could run before the new Model instances existed, losing them.
|
|
2799
|
-
throw new Error('Cannot set schema.models directly. Models are computed from Model instances; ' +
|
|
2800
|
-
'use Model.create(modelName, schema, { properties }) to add one.');
|
|
2801
|
-
}
|
|
2802
|
-
else if (prop === 'createdAt' || prop === 'updatedAt') {
|
|
2803
|
-
// Update metadata object
|
|
2804
|
-
const metadataContext = newInstance._getSnapshotContext();
|
|
2805
|
-
const currentMetadata = metadataContext.metadata || {
|
|
2806
|
-
name: schemaName,
|
|
2807
|
-
createdAt: new Date().toISOString(),
|
|
2808
|
-
updatedAt: new Date().toISOString(),
|
|
2809
|
-
};
|
|
2810
|
-
newInstance._service.send({
|
|
2811
|
-
type: 'updateContext',
|
|
2812
|
-
metadata: {
|
|
2813
|
-
...currentMetadata,
|
|
2814
|
-
[prop]: value,
|
|
2815
|
-
updatedAt: new Date().toISOString(),
|
|
2816
|
-
},
|
|
2817
|
-
});
|
|
2818
|
-
}
|
|
2819
|
-
else {
|
|
2820
|
-
// Standard property update
|
|
2821
|
-
newInstance._service.send({
|
|
2822
|
-
type: 'updateContext',
|
|
2823
|
-
[prop]: value,
|
|
2824
|
-
});
|
|
2825
|
-
// Mark schema as draft for other property changes
|
|
2826
|
-
newInstance._service.send({
|
|
2827
|
-
type: 'markAsDraft',
|
|
2828
|
-
propertyKey: prop,
|
|
2829
|
-
});
|
|
2830
|
-
// Save draft to database for tracked properties
|
|
2831
|
-
if (TRACKED_PROPERTIES.includes(prop)) {
|
|
2832
|
-
const trackedPropContext = newInstance._getSnapshotContext();
|
|
2833
|
-
const schemaName = trackedPropContext.metadata?.name || trackedPropContext.schemaName;
|
|
2834
|
-
trackDraftSave(newInstance._service, newInstance._saveDraftToDb(schemaName, schemaName), `Schema "${schemaName}"`);
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
|
-
},
|
|
2838
|
-
});
|
|
2839
|
-
// Cache by name initially (will be moved to id-based cache once schemaFileId is available)
|
|
2840
|
-
this.instanceCacheByName.set(schemaName, {
|
|
2841
|
-
instance: proxiedInstance,
|
|
2842
|
-
refCount: holds,
|
|
2843
|
-
});
|
|
2844
|
-
const schema = proxiedInstance;
|
|
2845
|
-
if (!waitForReady)
|
|
2846
|
-
return schema;
|
|
2847
|
-
const readyPromise = waitForEntityIdle(schema, { timeout: readyTimeout }).then(() => schema);
|
|
2848
|
-
this.pendingSchemaByName.set(schemaName, readyPromise);
|
|
2849
|
-
// Clear on both outcomes without a .finally() chain: that derived promise would reject unhandled
|
|
2850
|
-
// whenever readyPromise does, even when the caller handles the rejection.
|
|
2851
|
-
const clearPending = () => {
|
|
2852
|
-
this.pendingSchemaByName.delete(schemaName);
|
|
2853
|
-
};
|
|
2854
|
-
readyPromise.then(clearPending, clearPending);
|
|
2855
|
-
return readyPromise;
|
|
2856
|
-
}
|
|
2857
|
-
/**
|
|
2858
|
-
* Update the cache to use schemaFileId as the key instead of schemaName
|
|
2859
|
-
* This should be called once the schema is loaded and we have the schemaFileId
|
|
2860
|
-
* We keep the instance in BOTH caches for efficient lookups by either name or ID
|
|
2861
|
-
*/
|
|
2862
|
-
static _updateCacheKey(schemaName, schemaFileId) {
|
|
2863
|
-
// If we already have an entry with this id, skip (already processed)
|
|
2864
|
-
if (this.instanceCacheById.has(schemaFileId)) {
|
|
2865
|
-
return;
|
|
2866
|
-
}
|
|
2867
|
-
// If we have an instance cached by name, also add it to id-based cache
|
|
2868
|
-
// Keep it in both caches for efficient lookups, as one entry: one hold count for both keys
|
|
2869
|
-
if (this.instanceCacheByName.has(schemaName)) {
|
|
2870
|
-
const cacheEntry = this.instanceCacheByName.get(schemaName);
|
|
2871
|
-
// Add to ID cache (don't remove from name cache)
|
|
2872
|
-
this.instanceCacheById.set(schemaFileId, cacheEntry);
|
|
2873
|
-
logger(`Added schema cache entry to id "${schemaFileId}" (kept in name cache: "${schemaName}")`);
|
|
2874
|
-
}
|
|
2875
|
-
}
|
|
2876
|
-
/**
|
|
2877
|
-
* Get schema instance by schemaFileId (preferred method)
|
|
2878
|
-
* Returns null if not found in cache
|
|
2879
|
-
*/
|
|
2880
|
-
static getById(schemaFileId) {
|
|
2881
|
-
const entry = this.instanceCacheById.get(schemaFileId);
|
|
2882
|
-
if (!entry)
|
|
2883
|
-
return null;
|
|
2884
|
-
entry.refCount += 1;
|
|
2885
|
-
return entry.instance;
|
|
2886
|
-
}
|
|
2887
|
-
/**
|
|
2888
|
-
* The cached schema with this file id or name, without taking a hold. For SDK code that reads
|
|
2889
|
-
* the schema and doesn't hand it to a caller; callers that keep it use `create` or `find`.
|
|
2890
|
-
*/
|
|
2891
|
-
static peek(schemaFileIdOrName) {
|
|
2892
|
-
return (this.instanceCacheById.get(schemaFileIdOrName)?.instance ??
|
|
2893
|
-
this.instanceCacheByName.get(schemaFileIdOrName)?.instance);
|
|
2894
|
-
}
|
|
2895
|
-
/**
|
|
2896
|
-
* Clear all cached Schema instances.
|
|
2897
|
-
* This is primarily useful for test cleanup.
|
|
2898
|
-
* All cached instances will be unloaded and removed from both caches.
|
|
2899
|
-
*/
|
|
2900
|
-
static clearCache() {
|
|
2901
|
-
// Collect all unique instances from both caches
|
|
2902
|
-
const instances = new Set();
|
|
2903
|
-
// Collect from ID-based cache
|
|
2904
|
-
for (const { instance } of this.instanceCacheById.values()) {
|
|
2905
|
-
instances.add(instance);
|
|
2906
|
-
}
|
|
2907
|
-
// Collect from name-based cache
|
|
2908
|
-
for (const { instance } of this.instanceCacheByName.values()) {
|
|
2909
|
-
instances.add(instance);
|
|
2910
|
-
}
|
|
2911
|
-
// Unload all instances (this will properly clean up services and state)
|
|
2912
|
-
for (const instance of instances) {
|
|
2913
|
-
try {
|
|
2914
|
-
instance.unload();
|
|
2915
|
-
}
|
|
2916
|
-
catch {
|
|
2917
|
-
// Ignore errors during cleanup (instance might already be unloaded)
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
// Clear both caches and pending map
|
|
2921
|
-
this.instanceCacheById.clear();
|
|
2922
|
-
this.instanceCacheByName.clear();
|
|
2923
|
-
this.pendingSchemaByName.clear();
|
|
2924
|
-
}
|
|
2925
|
-
/**
|
|
2926
|
-
* Create or get schema instance by schemaFileId
|
|
2927
|
-
* Queries the database to find the schema name if not cached
|
|
2928
|
-
* @param schemaFileId - The schema file ID
|
|
2929
|
-
* @returns Schema instance, with a hold taken (unless `hold: false`)
|
|
2930
|
-
*/
|
|
2931
|
-
static async createById(schemaFileId,
|
|
2932
|
-
/** @internal `hold: false` as for `create` */
|
|
2933
|
-
options) {
|
|
2934
|
-
if (!schemaFileId) {
|
|
2935
|
-
throw new Error('Schema file ID is required');
|
|
2936
|
-
}
|
|
2937
|
-
// First, check if we have an instance cached by ID
|
|
2938
|
-
const cachedInstance = options?.hold === false ? this.peek(schemaFileId) : this.getById(schemaFileId);
|
|
2939
|
-
if (cachedInstance) {
|
|
2940
|
-
return cachedInstance;
|
|
2941
|
-
}
|
|
2942
|
-
// Query database to get schema name from ID
|
|
2943
|
-
const db = BaseDb.getAppDb();
|
|
2944
|
-
if (!db) {
|
|
2945
|
-
throw new Error('Database not available');
|
|
2946
|
-
}
|
|
2947
|
-
const dbSchemas = await db
|
|
2948
|
-
.select()
|
|
2949
|
-
.from(schemas)
|
|
2950
|
-
.where(eq(schemas.schemaFileId, schemaFileId))
|
|
2951
|
-
.orderBy(desc(schemas.version))
|
|
2952
|
-
.limit(1);
|
|
2953
|
-
if (dbSchemas.length === 0) {
|
|
2954
|
-
throw new Error(`Schema with ID "${schemaFileId}" not found in database`);
|
|
2955
|
-
}
|
|
2956
|
-
const dbSchema = dbSchemas[0];
|
|
2957
|
-
const schemaName = dbSchema.name;
|
|
2958
|
-
if (!schemaName) {
|
|
2959
|
-
throw new Error(`Schema with ID "${schemaFileId}" has no name in database`);
|
|
2960
|
-
}
|
|
2961
|
-
// Create schema using the name (this will load from database/file)
|
|
2962
|
-
return this.create(schemaName, options?.hold === false ? { hold: false } : undefined);
|
|
2963
|
-
}
|
|
2964
|
-
/**
|
|
2965
|
-
* Find schema instance by schemaFileId
|
|
2966
|
-
* Waits for the schema to be fully loaded (idle state) by default
|
|
2967
|
-
* @param options - Find options including schemaFileId and wait configuration
|
|
2968
|
-
* @returns Schema instance if found, undefined otherwise
|
|
2969
|
-
*/
|
|
2970
|
-
static async find({ schemaFileId, waitForReady = true, readyTimeout = DEFAULT_READY_TIMEOUT_MS, }) {
|
|
2971
|
-
if (!schemaFileId) {
|
|
2972
|
-
return undefined;
|
|
2973
|
-
}
|
|
2974
|
-
try {
|
|
2975
|
-
return await findEntity({
|
|
2976
|
-
getById: (id) => Schema.getById(id) ?? undefined,
|
|
2977
|
-
createById: (id) => Schema.createById(id),
|
|
2978
|
-
}, { id: schemaFileId }, {
|
|
2979
|
-
waitForReady,
|
|
2980
|
-
readyTimeout,
|
|
2981
|
-
});
|
|
2982
|
-
}
|
|
2983
|
-
catch {
|
|
2984
|
-
return undefined;
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
/**
|
|
2988
|
-
* Get instantiated Schema objects for all schemas (from database and files)
|
|
2989
|
-
* By default, returns only the latest version of each schema and excludes the internal Seed Protocol schema.
|
|
2990
|
-
* Uses loadAllSchemasFromDb() as the single source of truth, which intelligently merges database and file data.
|
|
2991
|
-
* Takes no holds on the instances it returns: a caller that keeps one uses `Schema.create` or `Schema.find`.
|
|
2992
|
-
*
|
|
2993
|
-
* @param options - Configuration options
|
|
2994
|
-
* @param options.includeAllVersions - If true, returns all versions of each schema. Default: false
|
|
2995
|
-
* @param options.includeInternal - If true, includes the internal Seed Protocol schema. Default: false
|
|
2996
|
-
* @returns Array of Schema instances
|
|
2997
|
-
*/
|
|
2998
|
-
static async all(options = {}) {
|
|
2999
|
-
const { includeAllVersions = false, includeInternal = false, waitForReady = false, readyTimeout = DEFAULT_READY_TIMEOUT_MS, } = options;
|
|
3000
|
-
try {
|
|
3001
|
-
// Use loadAllSchemasFromDb as single source of truth
|
|
3002
|
-
// This intelligently merges database and file data, including drafts
|
|
3003
|
-
const allSchemasData = await loadAllSchemasFromDb();
|
|
3004
|
-
// Filter internal schemas unless explicitly included
|
|
3005
|
-
let filteredSchemas = includeInternal
|
|
3006
|
-
? allSchemasData
|
|
3007
|
-
: allSchemasData.filter(schemaData => {
|
|
3008
|
-
const schema = schemaData.schema;
|
|
3009
|
-
// Check if this is an internal schema by name or ID
|
|
3010
|
-
return !isInternalSchema(schema.metadata?.name || '', schema.id);
|
|
3011
|
-
});
|
|
3012
|
-
// Filter to latest versions if needed
|
|
3013
|
-
if (!includeAllVersions) {
|
|
3014
|
-
// Group by schema name and keep only the latest version of each
|
|
3015
|
-
const schemaMap = new Map();
|
|
3016
|
-
for (const schemaData of filteredSchemas) {
|
|
3017
|
-
const schemaName = schemaData.schema.metadata?.name || '';
|
|
3018
|
-
if (!schemaName)
|
|
3019
|
-
continue;
|
|
3020
|
-
const existing = schemaMap.get(schemaName);
|
|
3021
|
-
if (!existing || schemaData.schema.version > existing.schema.version) {
|
|
3022
|
-
schemaMap.set(schemaName, schemaData);
|
|
3023
|
-
}
|
|
3024
|
-
else if (schemaData.schema.version === existing.schema.version) {
|
|
3025
|
-
// If versions are equal, use updatedAt as tiebreaker
|
|
3026
|
-
const currentUpdatedAt = new Date(schemaData.schema.metadata?.updatedAt || 0).getTime();
|
|
3027
|
-
const existingUpdatedAt = new Date(existing.schema.metadata?.updatedAt || 0).getTime();
|
|
3028
|
-
if (currentUpdatedAt > existingUpdatedAt) {
|
|
3029
|
-
schemaMap.set(schemaName, schemaData);
|
|
3030
|
-
}
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
|
-
filteredSchemas = Array.from(schemaMap.values());
|
|
3034
|
-
}
|
|
3035
|
-
// Create Schema instances using the schemaFileId from filtered data to ensure correct version
|
|
3036
|
-
const schemaInstances = [];
|
|
3037
|
-
const processedSchemaNames = new Set();
|
|
3038
|
-
for (const schemaData of filteredSchemas) {
|
|
3039
|
-
const schemaName = schemaData.schema.metadata?.name;
|
|
3040
|
-
const schemaFileId = schemaData.schema.id;
|
|
3041
|
-
const expectedVersion = schemaData.schema.version;
|
|
3042
|
-
if (!schemaName)
|
|
3043
|
-
continue;
|
|
3044
|
-
// Skip if we've already processed this schema name (to avoid duplicates)
|
|
3045
|
-
if (processedSchemaNames.has(schemaName)) {
|
|
3046
|
-
continue;
|
|
3047
|
-
}
|
|
3048
|
-
processedSchemaNames.add(schemaName);
|
|
3049
|
-
// Try to use schemaFileId to ensure we load the correct version
|
|
3050
|
-
// First check if we have a cached instance by schemaFileId. Schema.all takes no holds: it
|
|
3051
|
-
// lists schemas (useSchemas caches the list and never unloads it).
|
|
3052
|
-
if (schemaFileId) {
|
|
3053
|
-
const cachedById = this.peek(schemaFileId);
|
|
3054
|
-
if (cachedById) {
|
|
3055
|
-
// Verify the cached instance has the correct version
|
|
3056
|
-
const cachedContext = cachedById.getService().getSnapshot().context;
|
|
3057
|
-
if (cachedContext.version === expectedVersion) {
|
|
3058
|
-
schemaInstances.push(cachedById);
|
|
3059
|
-
continue;
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
}
|
|
3063
|
-
// If no cached instance by ID, check by name but verify version
|
|
3064
|
-
const cachedByName = this.instanceCacheByName.get(schemaName);
|
|
3065
|
-
if (cachedByName) {
|
|
3066
|
-
const cachedContext = cachedByName.instance.getService().getSnapshot().context;
|
|
3067
|
-
// Only use cached instance if it has the correct version
|
|
3068
|
-
if (cachedContext.version === expectedVersion && (!schemaFileId || cachedContext.id === schemaFileId)) {
|
|
3069
|
-
schemaInstances.push(cachedByName.instance);
|
|
3070
|
-
continue;
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3073
|
-
// Create new instance - it will load from database
|
|
3074
|
-
// The loadOrCreateSchema actor should query by name and get the latest version
|
|
3075
|
-
// But to ensure we get the correct version, we'll use createById if we have the schemaFileId
|
|
3076
|
-
if (schemaFileId) {
|
|
3077
|
-
try {
|
|
3078
|
-
const instance = await this.createById(schemaFileId, { hold: false });
|
|
3079
|
-
schemaInstances.push(instance);
|
|
3080
|
-
}
|
|
3081
|
-
catch {
|
|
3082
|
-
// Fallback to creating by name if createById fails
|
|
3083
|
-
schemaInstances.push(this.create(schemaName, { waitForReady: false, hold: false }));
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
3086
|
-
else {
|
|
3087
|
-
schemaInstances.push(this.create(schemaName, { waitForReady: false, hold: false }));
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
if (waitForReady && schemaInstances.length > 0) {
|
|
3091
|
-
await Promise.all(schemaInstances.map((s) => waitForEntityIdle(s, {
|
|
3092
|
-
timeout: readyTimeout,
|
|
3093
|
-
})));
|
|
3094
|
-
}
|
|
3095
|
-
return schemaInstances;
|
|
3096
|
-
}
|
|
3097
|
-
catch (error) {
|
|
3098
|
-
// Fallback to file-based approach if database is unavailable
|
|
3099
|
-
// This maintains backward compatibility for environments without database
|
|
3100
|
-
logger(`Error loading schemas from database, falling back to files: ${error instanceof Error ? error.message : String(error)}`);
|
|
3101
|
-
// Use existing file-based implementation as fallback
|
|
3102
|
-
const allSchemaFiles = await listCompleteSchemaFiles();
|
|
3103
|
-
// Filter out internal schemas unless explicitly included
|
|
3104
|
-
const filteredSchemas = includeInternal
|
|
3105
|
-
? allSchemaFiles
|
|
3106
|
-
: allSchemaFiles.filter(schema => {
|
|
3107
|
-
// Check if this is an internal schema by name or ID
|
|
3108
|
-
return !isInternalSchema(schema.name, schema.schemaFileId);
|
|
3109
|
-
});
|
|
3110
|
-
// If includeAllVersions is false, filter to only latest version of each schema
|
|
3111
|
-
const schemasToUse = includeAllVersions
|
|
3112
|
-
? filteredSchemas
|
|
3113
|
-
: (() => {
|
|
3114
|
-
// Group by schema name and keep only the latest version of each
|
|
3115
|
-
const schemaMap = new Map();
|
|
3116
|
-
for (const schema of filteredSchemas) {
|
|
3117
|
-
const existing = schemaMap.get(schema.name);
|
|
3118
|
-
if (!existing || schema.version > existing.version) {
|
|
3119
|
-
schemaMap.set(schema.name, schema);
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
return Array.from(schemaMap.values());
|
|
3123
|
-
})();
|
|
3124
|
-
// Get unique schema names (one instance per schema name)
|
|
3125
|
-
const uniqueSchemaNames = new Set();
|
|
3126
|
-
for (const schema of schemasToUse) {
|
|
3127
|
-
uniqueSchemaNames.add(schema.name);
|
|
3128
|
-
}
|
|
3129
|
-
// Create Schema instances for each unique schema name
|
|
3130
|
-
const schemaInstances = [];
|
|
3131
|
-
for (const schemaName of uniqueSchemaNames) {
|
|
3132
|
-
schemaInstances.push(this.create(schemaName, { waitForReady: false, hold: false }));
|
|
3133
|
-
}
|
|
3134
|
-
if (waitForReady && schemaInstances.length > 0) {
|
|
3135
|
-
await Promise.all(schemaInstances.map((s) => waitForEntityIdle(s, {
|
|
3136
|
-
timeout: readyTimeout,
|
|
3137
|
-
})));
|
|
3138
|
-
}
|
|
3139
|
-
return schemaInstances;
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
getService() {
|
|
3143
|
-
return this._service;
|
|
3144
|
-
}
|
|
3145
|
-
_getSnapshot() {
|
|
3146
|
-
return this._service.getSnapshot();
|
|
3147
|
-
}
|
|
3148
|
-
_getSnapshotContext() {
|
|
3149
|
-
return this._getSnapshot().context;
|
|
3150
|
-
}
|
|
3151
|
-
get schemaName() {
|
|
3152
|
-
const context = this._getSnapshotContext();
|
|
3153
|
-
const schemaFileId = context.id; // id is now the schemaFileId (string)
|
|
3154
|
-
// Prefer metadata.name if available (most reliable)
|
|
3155
|
-
if (context.metadata?.name) {
|
|
3156
|
-
return context.metadata.name;
|
|
3157
|
-
}
|
|
3158
|
-
// If metadata.name is not available, use schemaName but only if it's not the ID
|
|
3159
|
-
// This prevents returning the ID when the schema is still loading or if an ID was mistakenly passed
|
|
3160
|
-
if (context.schemaName && context.schemaName !== schemaFileId) {
|
|
3161
|
-
return context.schemaName;
|
|
3162
|
-
}
|
|
3163
|
-
// Final fallback - return schemaName even if it might be the ID
|
|
3164
|
-
// This handles edge cases during loading, but should be rare
|
|
3165
|
-
return context.schemaName || '';
|
|
3166
|
-
}
|
|
3167
|
-
get schemaFileId() {
|
|
3168
|
-
return this._getSnapshotContext().id; // id is now the schemaFileId (string)
|
|
3169
|
-
}
|
|
3170
|
-
get id() {
|
|
3171
|
-
return this._getSnapshotContext().id; // id is now the schemaFileId (string)
|
|
3172
|
-
}
|
|
3173
|
-
/**
|
|
3174
|
-
* Returns Model instances for this schema
|
|
3175
|
-
* This is a computed property that reads from the service context
|
|
3176
|
-
* Note: This is NOT reactive - use useModels() hook for reactivity
|
|
3177
|
-
*/
|
|
3178
|
-
get models() {
|
|
3179
|
-
const context = this._getSnapshotContext();
|
|
3180
|
-
const schemaName = context.schemaName;
|
|
3181
|
-
// Get model IDs from service context (reactive state)
|
|
3182
|
-
const liveQueryIds = context._liveQueryModelIds || [];
|
|
3183
|
-
// Get pending model IDs (not yet in DB)
|
|
3184
|
-
// Note: schemaId lookup is async, so we skip pending IDs here
|
|
3185
|
-
// They will be included when schemaId is available asynchronously
|
|
3186
|
-
const pendingIds = [];
|
|
3187
|
-
// Combine and deduplicate
|
|
3188
|
-
const allModelIds = [...new Set([...liveQueryIds, ...pendingIds])];
|
|
3189
|
-
// Get Model instances from static cache (from schema load)
|
|
3190
|
-
const seen = new Set();
|
|
3191
|
-
const modelInstances = [];
|
|
3192
|
-
for (const modelFileId of allModelIds) {
|
|
3193
|
-
const model = Model.peek(modelFileId);
|
|
3194
|
-
if (model && modelFileId && !seen.has(modelFileId)) {
|
|
3195
|
-
modelInstances.push(model);
|
|
3196
|
-
seen.add(modelFileId);
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
// Include models created at runtime via Model.create() that belong to this schema
|
|
3200
|
-
// (they may not be in _liveQueryModelIds until schema context is updated)
|
|
3201
|
-
if (schemaName) {
|
|
3202
|
-
const cachedForSchema = Model.getCachedInstancesForSchema(schemaName);
|
|
3203
|
-
for (const model of cachedForSchema) {
|
|
3204
|
-
const id = model.id;
|
|
3205
|
-
if (id && !seen.has(id)) {
|
|
3206
|
-
modelInstances.push(model);
|
|
3207
|
-
seen.add(id);
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
}
|
|
3211
|
-
// Return a new array reference (snapshot at time of access)
|
|
3212
|
-
return [...modelInstances];
|
|
3213
|
-
}
|
|
3214
|
-
get status() {
|
|
3215
|
-
return this._getSnapshot().value;
|
|
3216
|
-
}
|
|
3217
|
-
get isEdited() {
|
|
3218
|
-
const context = this._getSnapshotContext();
|
|
3219
|
-
// First check in-memory state
|
|
3220
|
-
if (context._isEdited !== undefined) {
|
|
3221
|
-
return context._isEdited;
|
|
3222
|
-
}
|
|
3223
|
-
// Fall back to reading from database if we have _dbId
|
|
3224
|
-
if (context._dbId) {
|
|
3225
|
-
// Load from database asynchronously (fire-and-forget for now)
|
|
3226
|
-
// For synchronous getter, we'll need to cache it or make it async
|
|
3227
|
-
// For now, return false if not in context
|
|
3228
|
-
return false;
|
|
3229
|
-
}
|
|
3230
|
-
return false;
|
|
3231
|
-
}
|
|
3232
|
-
get validationErrors() {
|
|
3233
|
-
return this._getSnapshotContext()._validationErrors || [];
|
|
3234
|
-
}
|
|
3235
|
-
/**
|
|
3236
|
-
* The error from the last draft save that an assignment started (e.g. `schema.name = ...`),
|
|
3237
|
-
* such as a ConflictError; undefined once a later one succeeds.
|
|
3238
|
-
*/
|
|
3239
|
-
get lastSaveError() {
|
|
3240
|
-
return getDraftSaveError(this._service);
|
|
3241
|
-
}
|
|
3242
|
-
get isValid() {
|
|
3243
|
-
const errors = this.validationErrors;
|
|
3244
|
-
return errors.length === 0;
|
|
3245
|
-
}
|
|
3246
|
-
/**
|
|
3247
|
-
* Validate the schema
|
|
3248
|
-
* @returns Validation result
|
|
3249
|
-
*/
|
|
3250
|
-
async validate() {
|
|
3251
|
-
return new Promise((resolve) => {
|
|
3252
|
-
let resolved = false;
|
|
3253
|
-
const subscription = this._service.subscribe((snapshot) => {
|
|
3254
|
-
if (snapshot.value === 'idle' || snapshot.value === 'error') {
|
|
3255
|
-
if (!resolved) {
|
|
3256
|
-
resolved = true;
|
|
3257
|
-
subscription.unsubscribe();
|
|
3258
|
-
const errors = snapshot.context._validationErrors || [];
|
|
3259
|
-
resolve({
|
|
3260
|
-
isValid: errors.length === 0,
|
|
3261
|
-
errors,
|
|
3262
|
-
});
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3265
|
-
});
|
|
3266
|
-
this._service.send({ type: 'validateSchema' });
|
|
3267
|
-
// Timeout fallback to ensure we always resolve (15 seconds - longer than actor timeout)
|
|
3268
|
-
setTimeout(() => {
|
|
3269
|
-
if (!resolved) {
|
|
3270
|
-
resolved = true;
|
|
3271
|
-
subscription.unsubscribe();
|
|
3272
|
-
const errors = this._getSnapshotContext()._validationErrors || [];
|
|
3273
|
-
resolve({
|
|
3274
|
-
isValid: errors.length === 0,
|
|
3275
|
-
errors,
|
|
3276
|
-
});
|
|
3277
|
-
}
|
|
3278
|
-
}, 15000);
|
|
3279
|
-
});
|
|
3280
|
-
}
|
|
3281
|
-
/**
|
|
3282
|
-
* Build the file-format `models` object from this schema's Model instances: every model
|
|
3283
|
-
* (those created at runtime too), each property as its file definition ({ id, type, model,
|
|
3284
|
-
* ref, refValueType, required, storage, validation, ... }).
|
|
3285
|
-
*/
|
|
3286
|
-
async _buildModelsFromInstances() {
|
|
3287
|
-
const context = this._getSnapshotContext();
|
|
3288
|
-
const { convertPropertyToSchemaUpdate } = await Promise.resolve().then(function () { return updateSchema; });
|
|
3289
|
-
const models = {};
|
|
3290
|
-
for (const modelInstance of this.models) {
|
|
3291
|
-
try {
|
|
3292
|
-
const modelName = modelInstance.modelName;
|
|
3293
|
-
if (!modelName)
|
|
3294
|
-
continue;
|
|
3295
|
-
const properties$1 = {};
|
|
3296
|
-
let propertyContexts = (modelInstance.properties ?? []).map((property) => property.getService().getSnapshot().context);
|
|
3297
|
-
// model.properties can still be empty for a model created at runtime (the browser fills
|
|
3298
|
-
// it from a live query): read the rows, preferring each row's cached instance.
|
|
3299
|
-
const modelDbId = modelInstance.getService().getSnapshot().context._dbId;
|
|
3300
|
-
const db = BaseDb.getAppDb();
|
|
3301
|
-
if (propertyContexts.length === 0 && modelDbId && db) {
|
|
3302
|
-
const { ModelProperty } = await import('./ModelProperty-DDD47KVm.js');
|
|
3303
|
-
const rows = await db.select().from(properties).where(eq(properties.modelId, modelDbId));
|
|
3304
|
-
propertyContexts = rows.map((row) => {
|
|
3305
|
-
const cached = row.schemaFileId ? ModelProperty.getById(row.schemaFileId) : undefined;
|
|
3306
|
-
return cached
|
|
3307
|
-
? cached.getService().getSnapshot().context
|
|
3308
|
-
: {
|
|
3309
|
-
...row,
|
|
3310
|
-
id: row.schemaFileId ?? undefined,
|
|
3311
|
-
refModelId: row.refModelId ?? undefined,
|
|
3312
|
-
refValueType: row.refValueType ?? undefined,
|
|
3313
|
-
storageType: row.storageType ?? undefined,
|
|
3314
|
-
localStorageDir: row.localStorageDir ?? undefined,
|
|
3315
|
-
filenameSuffix: row.filenameSuffix ?? undefined,
|
|
3316
|
-
};
|
|
3317
|
-
});
|
|
3318
|
-
}
|
|
3319
|
-
const contextModel = context.models?.[modelName];
|
|
3320
|
-
const fromContext = contextModel?.properties;
|
|
3321
|
-
// nameMatch lives only in the schema file (ADR 0009): keep each property's, found by id
|
|
3322
|
-
// first so a renamed property keeps it.
|
|
3323
|
-
const contextNameMatch = (ctx) => {
|
|
3324
|
-
const defs = Object.values(fromContext ?? {});
|
|
3325
|
-
const def = (ctx.id ? defs.find((d) => d?.id === ctx.id) : undefined) ?? fromContext?.[ctx.name];
|
|
3326
|
-
return def?.nameMatch;
|
|
3327
|
-
};
|
|
3328
|
-
for (const ctx of propertyContexts) {
|
|
3329
|
-
if (!ctx.name)
|
|
3330
|
-
continue;
|
|
3331
|
-
const { updates } = await convertPropertyToSchemaUpdate(ctx, modelName, ctx.name);
|
|
3332
|
-
const nameMatch = contextNameMatch(ctx);
|
|
3333
|
-
properties$1[ctx.name] = {
|
|
3334
|
-
...(ctx.id ? { id: ctx.id } : {}),
|
|
3335
|
-
...updates,
|
|
3336
|
-
...(ctx.validation ? { validation: ctx.validation } : {}),
|
|
3337
|
-
...(ctx.htmlEmbeddedDataUriPolicy ? { htmlEmbeddedDataUriPolicy: ctx.htmlEmbeddedDataUriPolicy } : {}),
|
|
3338
|
-
...(nameMatch !== undefined ? { nameMatch } : {}),
|
|
3339
|
-
};
|
|
3340
|
-
}
|
|
3341
|
-
// No property instances loaded for this model: keep the definitions the schema has.
|
|
3342
|
-
models[modelName] = {
|
|
3343
|
-
...(modelInstance.id ? { id: modelInstance.id } : {}),
|
|
3344
|
-
...(contextModel?.nameMatch !== undefined ? { nameMatch: contextModel.nameMatch } : {}),
|
|
3345
|
-
properties: Object.keys(properties$1).length > 0 || !fromContext ? properties$1 : fromContext,
|
|
3346
|
-
};
|
|
3347
|
-
}
|
|
3348
|
-
catch (error) {
|
|
3349
|
-
logger(`Error building model from instance: ${error instanceof Error ? error.message : String(error)}`);
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
logger(`Built ${Object.keys(models).length} models from Model instances`);
|
|
3353
|
-
return models;
|
|
3354
|
-
}
|
|
3355
|
-
/**
|
|
3356
|
-
* Saves all edited properties to a new schema version.
|
|
3357
|
-
* This writes the changes to a new JSON file and clears the draft flags.
|
|
3358
|
-
* Validates the schema before saving.
|
|
3359
|
-
* Transitions schema from draft (DB-only) to published (file + DB).
|
|
3360
|
-
* @returns The file path of the new schema version
|
|
3361
|
-
*/
|
|
3362
|
-
async saveNewVersion() {
|
|
3363
|
-
// Check for conflicts before saving
|
|
3364
|
-
const conflictCheck = await this._checkForConflicts();
|
|
3365
|
-
if (conflictCheck.hasConflict) {
|
|
3366
|
-
const errorMessage = conflictCheck.message || 'Database was updated externally. Please reload the schema and try again.';
|
|
3367
|
-
logger(`CONFLICT DETECTED: ${errorMessage}`);
|
|
3368
|
-
throw new ConflictError(errorMessage, conflictCheck);
|
|
3369
|
-
}
|
|
3370
|
-
const context = this._getSnapshotContext();
|
|
3371
|
-
if (!context._isDraft || !context._editedProperties || context._editedProperties.size === 0) {
|
|
3372
|
-
logger('No changes to save');
|
|
3373
|
-
return '';
|
|
3374
|
-
}
|
|
3375
|
-
// Validate schema before saving
|
|
3376
|
-
const validationResult = await this.validate();
|
|
3377
|
-
if (!validationResult.isValid) {
|
|
3378
|
-
logger(`Schema validation failed with ${validationResult.errors.length} errors`);
|
|
3379
|
-
throw new Error(`Cannot save schema: validation failed. Errors: ${validationResult.errors.map(e => e.message).join(', ')}`);
|
|
3380
|
-
}
|
|
3381
|
-
logger(`Saving new version for schema ${this.schemaName} with ${context._editedProperties.size} edited properties`);
|
|
3382
|
-
// STEP 1: Ensure draft exists in database and update it with current state
|
|
3383
|
-
const db = BaseDb.getAppDb();
|
|
3384
|
-
if (!db) {
|
|
3385
|
-
throw new Error('Database not found');
|
|
3386
|
-
}
|
|
3387
|
-
// Build current schema state from context
|
|
3388
|
-
// CRITICAL: Read models from Model instances, not from Schema context
|
|
3389
|
-
// Model instances are the source of truth for model data
|
|
3390
|
-
const modelsFromInstances = await this._buildModelsFromInstances();
|
|
3391
|
-
const currentSchema = {
|
|
3392
|
-
id: context.id,
|
|
3393
|
-
$schema: context.$schema || 'https://seedprotocol.org/schemas/data-model/v1',
|
|
3394
|
-
version: context.version || 1,
|
|
3395
|
-
metadata: context.metadata || {
|
|
3396
|
-
name: this.schemaName,
|
|
3397
|
-
createdAt: new Date().toISOString(),
|
|
3398
|
-
updatedAt: new Date().toISOString(),
|
|
3399
|
-
},
|
|
3400
|
-
models: modelsFromInstances, // Read from Model instances, not context.models
|
|
3401
|
-
enums: context.enums || {},
|
|
3402
|
-
migrations: context.migrations || [],
|
|
3403
|
-
...(context.nameMatch !== undefined && { nameMatch: context.nameMatch }),
|
|
3404
|
-
};
|
|
3405
|
-
// Update draft in database with current state
|
|
3406
|
-
await addSchemaToDb({
|
|
3407
|
-
name: this.schemaName,
|
|
3408
|
-
version: currentSchema.version,
|
|
3409
|
-
createdAt: new Date(currentSchema.metadata.createdAt).getTime(),
|
|
3410
|
-
updatedAt: new Date(currentSchema.metadata.updatedAt).getTime(),
|
|
3411
|
-
}, currentSchema.id, JSON.stringify(currentSchema, null, 2), true);
|
|
3412
|
-
const dbSchema = await db
|
|
3413
|
-
.select()
|
|
3414
|
-
.from(schemas)
|
|
3415
|
-
.where(eq(schemas.name, this.schemaName))
|
|
3416
|
-
.limit(1);
|
|
3417
|
-
// Check we have something to save: schema:models (new models) or modelName:propertyName (property edits)
|
|
3418
|
-
const hasSchemaModels = context._editedProperties.has('schema:models');
|
|
3419
|
-
const hasPropertyEdits = [...context._editedProperties].some((key) => {
|
|
3420
|
-
const [modelName, propertyName] = key.split(':');
|
|
3421
|
-
return modelName && propertyName && key !== 'schema:name';
|
|
3422
|
-
});
|
|
3423
|
-
if (!hasSchemaModels && !hasPropertyEdits) {
|
|
3424
|
-
logger('No valid property updates to save');
|
|
3425
|
-
return '';
|
|
3426
|
-
}
|
|
3427
|
-
// Use writeFullSchemaNewVersion for all edits - handles renames, new models, property updates.
|
|
3428
|
-
// Model instances have the correct (current) structure including renamed properties.
|
|
3429
|
-
// STEP 2: Save to new schema version (writes file)
|
|
3430
|
-
const newFilePath = await writeFullSchemaNewVersion(this.schemaName, currentSchema);
|
|
3431
|
-
// STEP 3: After file is written, update database to mark as published (isDraft = false)
|
|
3432
|
-
// Load the file to get the final schema with IDs
|
|
3433
|
-
const fileContent = await BaseFileManager.readFileAsString(newFilePath);
|
|
3434
|
-
const publishedSchema = JSON.parse(fileContent);
|
|
3435
|
-
// Update database record: set isDraft = false and update schemaFileId
|
|
3436
|
-
if (dbSchema.length > 0) {
|
|
3437
|
-
clearNameMatchLevels(); // the stored schema file changes (ADR 0009)
|
|
3438
|
-
await db
|
|
3439
|
-
.update(schemas)
|
|
3440
|
-
.set({
|
|
3441
|
-
isDraft: false,
|
|
3442
|
-
isEdited: false, // Clear isEdited flag after saving to file
|
|
3443
|
-
schemaFileId: publishedSchema.id,
|
|
3444
|
-
schemaData: JSON.stringify(publishedSchema, null, 2),
|
|
3445
|
-
version: publishedSchema.version,
|
|
3446
|
-
updatedAt: new Date(publishedSchema.metadata.updatedAt).getTime(),
|
|
3447
|
-
})
|
|
3448
|
-
.where(eq(schemas.id, dbSchema[0].id));
|
|
3449
|
-
}
|
|
3450
|
-
else {
|
|
3451
|
-
// Create new record if it doesn't exist (shouldn't happen, but safety)
|
|
3452
|
-
await addSchemaToDb({
|
|
3453
|
-
name: this.schemaName,
|
|
3454
|
-
version: publishedSchema.version,
|
|
3455
|
-
createdAt: new Date(publishedSchema.metadata.createdAt).getTime(),
|
|
3456
|
-
updatedAt: new Date(publishedSchema.metadata.updatedAt).getTime(),
|
|
3457
|
-
}, publishedSchema.id, JSON.stringify(publishedSchema, null, 2), false, // isDraft = false (published)
|
|
3458
|
-
false);
|
|
3459
|
-
}
|
|
3460
|
-
// Clear draft flags on Schema and update conflict detection metadata
|
|
3461
|
-
this._service.send({
|
|
3462
|
-
type: 'clearDraft',
|
|
3463
|
-
_dbUpdatedAt: new Date(publishedSchema.metadata.updatedAt).getTime(),
|
|
3464
|
-
_dbVersion: publishedSchema.version,
|
|
3465
|
-
savedSchema: publishedSchema,
|
|
3466
|
-
});
|
|
3467
|
-
// Clear edited flags on all ModelProperty instances and in database
|
|
3468
|
-
const modelPropertyMod = await import('./ModelProperty-DDD47KVm.js');
|
|
3469
|
-
const { ModelProperty } = modelPropertyMod;
|
|
3470
|
-
for (const propertyKey of context._editedProperties) {
|
|
3471
|
-
const [modelName, propertyName] = propertyKey.split(':');
|
|
3472
|
-
const ModelPropertyClass = ModelProperty;
|
|
3473
|
-
// Model names are only unique per schema: pick this schema's instance, which may be keyed by id.
|
|
3474
|
-
const thisSchemaName = this.schemaName;
|
|
3475
|
-
const cachedInstance = [...ModelPropertyClass.instanceCache.values()].find(({ instance }) => {
|
|
3476
|
-
const ctx = instance.getService().getSnapshot().context;
|
|
3477
|
-
return (ctx.modelName === modelName &&
|
|
3478
|
-
ctx.name === propertyName &&
|
|
3479
|
-
(!ctx._schemaName || ctx._schemaName === thisSchemaName));
|
|
3480
|
-
});
|
|
3481
|
-
if (cachedInstance) {
|
|
3482
|
-
const modelProperty = cachedInstance.instance;
|
|
3483
|
-
// Update original values to current values and clear edited flag
|
|
3484
|
-
modelProperty.getService().send({
|
|
3485
|
-
type: 'clearEdited',
|
|
3486
|
-
});
|
|
3487
|
-
// Clear isEdited flag in database
|
|
3488
|
-
try {
|
|
3489
|
-
if (db && modelName && propertyName) {
|
|
3490
|
-
// Find this schema's model by name
|
|
3491
|
-
const _mod_9 = await import('./index-rOTW-2QZ.js');
|
|
3492
|
-
const _ns_9 = _mod_9.be;
|
|
3493
|
-
const { resolveModelRecord } = _ns_9;
|
|
3494
|
-
const modelRecord = await resolveModelRecord(modelName, { schemaName: thisSchemaName }, db);
|
|
3495
|
-
const modelRecords = modelRecord ? [modelRecord] : [];
|
|
3496
|
-
if (modelRecords.length > 0) {
|
|
3497
|
-
// Find property by name and modelId
|
|
3498
|
-
const propertyRecords = await db
|
|
3499
|
-
.select({ id: properties.id })
|
|
3500
|
-
.from(properties)
|
|
3501
|
-
.where(and(eq(properties.name, propertyName), eq(properties.modelId, modelRecords[0].id)))
|
|
3502
|
-
.limit(1);
|
|
3503
|
-
if (propertyRecords.length > 0) {
|
|
3504
|
-
// Clear isEdited flag in database
|
|
3505
|
-
await db
|
|
3506
|
-
.update(properties)
|
|
3507
|
-
.set({ isEdited: false })
|
|
3508
|
-
.where(eq(properties.id, propertyRecords[0].id));
|
|
3509
|
-
}
|
|
3510
|
-
}
|
|
3511
|
-
}
|
|
3512
|
-
}
|
|
3513
|
-
catch (error) {
|
|
3514
|
-
logger(`Error clearing isEdited flag in database for property ${propertyKey}: ${error}`);
|
|
3515
|
-
}
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
|
-
// Clear isEdited flags for all models in this schema
|
|
3519
|
-
try {
|
|
3520
|
-
if (db && context._dbId) {
|
|
3521
|
-
// Get all models for this schema
|
|
3522
|
-
const modelRecords = await db
|
|
3523
|
-
.select({ id: models.id })
|
|
3524
|
-
.from(modelSchemas)
|
|
3525
|
-
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
3526
|
-
.where(eq(modelSchemas.schemaId, context._dbId));
|
|
3527
|
-
// Clear isEdited flag for all models
|
|
3528
|
-
for (const modelRecord of modelRecords) {
|
|
3529
|
-
if (modelRecord.id) {
|
|
3530
|
-
await db
|
|
3531
|
-
.update(models)
|
|
3532
|
-
.set({ isEdited: false })
|
|
3533
|
-
.where(eq(models.id, modelRecord.id));
|
|
3534
|
-
}
|
|
3535
|
-
}
|
|
3536
|
-
}
|
|
3537
|
-
}
|
|
3538
|
-
catch (error) {
|
|
3539
|
-
logger(`Error clearing isEdited flags for models: ${error}`);
|
|
3540
|
-
}
|
|
3541
|
-
logger(`Successfully saved new version for schema ${this.schemaName} and published to file`);
|
|
3542
|
-
return newFilePath;
|
|
3543
|
-
}
|
|
3544
|
-
/**
|
|
3545
|
-
* Check for conflicts between actor context and database
|
|
3546
|
-
* @returns ConflictResult indicating if a conflict exists
|
|
3547
|
-
*/
|
|
3548
|
-
async _checkForConflicts() {
|
|
3549
|
-
const context = this._getSnapshotContext();
|
|
3550
|
-
// If we don't have load metadata, can't check for conflicts
|
|
3551
|
-
if (!context._dbUpdatedAt || !context.id) {
|
|
3552
|
-
return { hasConflict: false };
|
|
3553
|
-
}
|
|
3554
|
-
try {
|
|
3555
|
-
const db = BaseDb.getAppDb();
|
|
3556
|
-
if (!db) {
|
|
3557
|
-
return { hasConflict: false }; // Can't check without DB
|
|
3558
|
-
}
|
|
3559
|
-
// Get current DB record
|
|
3560
|
-
const dbSchemas = await db
|
|
3561
|
-
.select()
|
|
3562
|
-
.from(schemas)
|
|
3563
|
-
.where(eq(schemas.schemaFileId, context.id)) // id is now the schemaFileId (string)
|
|
3564
|
-
.orderBy(desc(schemas.version))
|
|
3565
|
-
.limit(1);
|
|
3566
|
-
if (dbSchemas.length === 0) {
|
|
3567
|
-
return { hasConflict: false }; // No DB record, no conflict
|
|
3568
|
-
}
|
|
3569
|
-
const dbRecord = dbSchemas[0];
|
|
3570
|
-
// Check if DB was updated after we loaded
|
|
3571
|
-
const dbUpdatedAt = dbRecord.updatedAt || 0;
|
|
3572
|
-
const localUpdatedAt = context._dbUpdatedAt;
|
|
3573
|
-
if (dbUpdatedAt > localUpdatedAt) {
|
|
3574
|
-
return {
|
|
3575
|
-
hasConflict: true,
|
|
3576
|
-
localVersion: context.version,
|
|
3577
|
-
dbVersion: dbRecord.version,
|
|
3578
|
-
localUpdatedAt: context.metadata?.updatedAt,
|
|
3579
|
-
dbUpdatedAt: new Date(dbUpdatedAt).toISOString(),
|
|
3580
|
-
message: `Database was updated externally. Local version: ${context.version}, DB version: ${dbRecord.version}`,
|
|
3581
|
-
};
|
|
3582
|
-
}
|
|
3583
|
-
return { hasConflict: false };
|
|
3584
|
-
}
|
|
3585
|
-
catch (error) {
|
|
3586
|
-
logger(`Error checking for conflicts: ${error instanceof Error ? error.message : String(error)}`);
|
|
3587
|
-
return { hasConflict: false }; // On error, assume no conflict to allow save
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
3590
|
-
/**
|
|
3591
|
-
* Reload schema from database
|
|
3592
|
-
* This refreshes the actor context with the latest data from the database
|
|
3593
|
-
*/
|
|
3594
|
-
async reload() {
|
|
3595
|
-
logger(`Reloading schema ${this.schemaName} from database`);
|
|
3596
|
-
// Send reload event to machine
|
|
3597
|
-
this._service.send({ type: 'reloadFromDb' });
|
|
3598
|
-
// Wait for reload to complete
|
|
3599
|
-
return new Promise((resolve, reject) => {
|
|
3600
|
-
// Cleared once the reload settles: a pending timer kept Node scripts alive for 10 s.
|
|
3601
|
-
const timer = setTimeout(() => {
|
|
3602
|
-
subscription.unsubscribe();
|
|
3603
|
-
reject(new Error('Timeout waiting for schema reload'));
|
|
3604
|
-
}, 10000);
|
|
3605
|
-
const subscription = this._service.subscribe((snapshot) => {
|
|
3606
|
-
if (snapshot.value === 'idle') {
|
|
3607
|
-
clearTimeout(timer);
|
|
3608
|
-
subscription.unsubscribe();
|
|
3609
|
-
resolve();
|
|
3610
|
-
}
|
|
3611
|
-
else if (snapshot.value === 'error') {
|
|
3612
|
-
clearTimeout(timer);
|
|
3613
|
-
subscription.unsubscribe();
|
|
3614
|
-
reject(new Error('Failed to reload schema from database'));
|
|
3615
|
-
}
|
|
3616
|
-
});
|
|
3617
|
-
});
|
|
3618
|
-
}
|
|
3619
|
-
/**
|
|
3620
|
-
* Save the current schema state to the database as a draft
|
|
3621
|
-
* This persists changes immediately without creating a new file version
|
|
3622
|
-
* @param oldName - Optional old name to look up existing record before name change
|
|
3623
|
-
* @param newName - Optional new name to use (if not provided, uses this.schemaName)
|
|
3624
|
-
*/
|
|
3625
|
-
async _saveDraftToDb(oldName, newName) {
|
|
3626
|
-
saveDraftLogger(`_saveDraftToDb called for schema (oldName: "${oldName}", newName: "${newName}")`);
|
|
3627
|
-
// Don't save during initialization - schemas are being loaded from files, not created as drafts
|
|
3628
|
-
// Check this FIRST before doing any expensive work like _getSnapshotContext()
|
|
3629
|
-
if (typeof window !== 'undefined') {
|
|
3630
|
-
const now = Date.now();
|
|
3631
|
-
// Use cached check to avoid expensive operations
|
|
3632
|
-
// Always check if cache is stale or if we previously got false (to allow recovery)
|
|
3633
|
-
const cacheIsStale = cachedClientInitialized === null || (now - clientCheckTime) > CLIENT_CHECK_CACHE_MS;
|
|
3634
|
-
const shouldRecheck = cacheIsStale || cachedClientInitialized === false;
|
|
3635
|
-
saveDraftLogger(`Client check: cacheIsStale=${cacheIsStale}, shouldRecheck=${shouldRecheck}, cachedValue=${cachedClientInitialized}, timeSinceCheck=${now - clientCheckTime}ms`);
|
|
3636
|
-
if (shouldRecheck) {
|
|
3637
|
-
try {
|
|
3638
|
-
const client = getClient();
|
|
3639
|
-
const clientSnapshot = client.getService().getSnapshot();
|
|
3640
|
-
// Check if state is IDLE (primary check) - isInitialized is set in entry action so should be true
|
|
3641
|
-
// But we check it as a secondary safeguard
|
|
3642
|
-
const isIdle = clientSnapshot.value === ClientManagerState.IDLE;
|
|
3643
|
-
const isInitialized = clientSnapshot.context.isInitialized;
|
|
3644
|
-
// If state is IDLE, trust it even if isInitialized isn't set yet (entry action should set it)
|
|
3645
|
-
// This aligns with useIsClientReady which only checks the state value
|
|
3646
|
-
cachedClientInitialized = isIdle && (isInitialized !== false);
|
|
3647
|
-
clientCheckTime = now;
|
|
3648
|
-
saveDraftLogger(`Client state checked: state=${clientSnapshot.value}, isIdle=${isIdle}, isInitialized=${isInitialized}, result=${cachedClientInitialized}`);
|
|
3649
|
-
}
|
|
3650
|
-
catch (error) {
|
|
3651
|
-
// If we can't check client state, assume not initialized to be safe
|
|
3652
|
-
// But only cache for a short time to allow recovery
|
|
3653
|
-
cachedClientInitialized = false;
|
|
3654
|
-
clientCheckTime = now;
|
|
3655
|
-
saveDraftLogger(`Error checking client state: ${error instanceof Error ? error.message : String(error)}, setting cachedClientInitialized=false`);
|
|
3656
|
-
}
|
|
3657
|
-
}
|
|
3658
|
-
else {
|
|
3659
|
-
saveDraftLogger(`Using cached client state: cachedClientInitialized=${cachedClientInitialized}`);
|
|
3660
|
-
}
|
|
3661
|
-
if (!cachedClientInitialized) {
|
|
3662
|
-
saveDraftLogger(`Client not initialized, skipping save (oldName: "${oldName}", newName: "${newName}")`);
|
|
3663
|
-
// Skip silently during initialization to avoid log spam and reduce overhead
|
|
3664
|
-
return;
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
saveDraftLogger(`Client is initialized, proceeding with save (oldName: "${oldName}", newName: "${newName}")`);
|
|
3668
|
-
// Check for conflicts before saving
|
|
3669
|
-
const conflictCheck = await this._checkForConflicts();
|
|
3670
|
-
if (conflictCheck.hasConflict) {
|
|
3671
|
-
const errorMessage = conflictCheck.message || 'Database was updated externally. Please reload the schema and try again.';
|
|
3672
|
-
saveDraftLogger(`CONFLICT DETECTED: ${errorMessage}`);
|
|
3673
|
-
throw new ConflictError(errorMessage, conflictCheck);
|
|
3674
|
-
}
|
|
3675
|
-
const context = this._getSnapshotContext();
|
|
3676
|
-
const schemaName = newName || context.schemaName || oldName || '';
|
|
3677
|
-
const schemaFileId = context.id || ''; // id is now the schemaFileId (string)
|
|
3678
|
-
// Use schemaFileId as the key if available, otherwise use schemaName
|
|
3679
|
-
const saveKey = schemaFileId || schemaName;
|
|
3680
|
-
// Prevent concurrent saves for the same schema
|
|
3681
|
-
if (Schema.savingSchemas.has(saveKey)) {
|
|
3682
|
-
saveDraftLogger(`Schema ${schemaName} (key: ${saveKey}) is already being saved, skipping concurrent save`);
|
|
3683
|
-
return;
|
|
3684
|
-
}
|
|
3685
|
-
Schema.savingSchemas.add(saveKey);
|
|
3686
|
-
saveDraftLogger(`Starting save for schema ${schemaName} (key: ${saveKey}, schemaFileId: ${schemaFileId})`);
|
|
3687
|
-
try {
|
|
3688
|
-
// Get context - if service is stopped, use the last known context or build from metadata
|
|
3689
|
-
let context;
|
|
3690
|
-
try {
|
|
3691
|
-
context = this._getSnapshotContext();
|
|
3692
|
-
}
|
|
3693
|
-
catch {
|
|
3694
|
-
// Service might be stopped, build context from what we know
|
|
3695
|
-
logger(`Service stopped, building context from available data`);
|
|
3696
|
-
context = {
|
|
3697
|
-
schemaName: newName || oldName || '',
|
|
3698
|
-
metadata: {
|
|
3699
|
-
name: newName || oldName || '',
|
|
3700
|
-
createdAt: new Date().toISOString(),
|
|
3701
|
-
updatedAt: new Date().toISOString(),
|
|
3702
|
-
},
|
|
3703
|
-
version: 1,
|
|
3704
|
-
_isDraft: true,
|
|
3705
|
-
_editedProperties: new Set(),
|
|
3706
|
-
};
|
|
3707
|
-
}
|
|
3708
|
-
const db = BaseDb.getAppDb();
|
|
3709
|
-
if (!db) {
|
|
3710
|
-
throw new Error('Database not found');
|
|
3711
|
-
}
|
|
3712
|
-
// Use provided newName or fall back to context schemaName (avoid getter which might be stale)
|
|
3713
|
-
const finalNewName = newName || context.schemaName || this.schemaName;
|
|
3714
|
-
// CRITICAL: If name changed, we MUST look up by old name to find the existing record
|
|
3715
|
-
// Don't use finalNewName if oldName is provided - that would look up by the new name and not find the old record
|
|
3716
|
-
const lookupName = oldName && oldName !== finalNewName ? oldName : finalNewName;
|
|
3717
|
-
// Try to get existing schema ID from database to preserve it
|
|
3718
|
-
// PRIMARY: Look up by schemaFileId (most reliable, independent of name changes)
|
|
3719
|
-
// FALLBACK: Look up by old name if name changed, otherwise by current name
|
|
3720
|
-
let existingSchemaId;
|
|
3721
|
-
let existingSchemaRecord;
|
|
3722
|
-
// PRIMARY: Look up by schemaFileId if we have it (this is the most reliable way)
|
|
3723
|
-
if (context.id) {
|
|
3724
|
-
logger(`Looking up schema by schemaFileId: ${context.id}`);
|
|
3725
|
-
const schemasById = await db
|
|
3726
|
-
.select()
|
|
3727
|
-
.from(schemas)
|
|
3728
|
-
.where(eq(schemas.schemaFileId, context.id)) // id is now the schemaFileId (string)
|
|
3729
|
-
.limit(1);
|
|
3730
|
-
if (schemasById.length > 0) {
|
|
3731
|
-
const foundRecord = schemasById[0];
|
|
3732
|
-
existingSchemaRecord = foundRecord;
|
|
3733
|
-
existingSchemaId = foundRecord.schemaFileId || context.id;
|
|
3734
|
-
logger(`Found schema by schemaFileId: ${context.id} (id: ${foundRecord.id}, name: ${foundRecord.name})`);
|
|
3735
|
-
}
|
|
3736
|
-
else {
|
|
3737
|
-
logger(`No schema found by schemaFileId: ${context.id}`);
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
3740
|
-
else {
|
|
3741
|
-
logger(`No id (schemaFileId) in context, will look up by name`);
|
|
3742
|
-
}
|
|
3743
|
-
// FALLBACK: If not found by schemaFileId, try by name (for newly created schemas that don't have schemaFileId yet)
|
|
3744
|
-
if (!existingSchemaRecord) {
|
|
3745
|
-
logger(`Looking up schema by name "${lookupName}" (oldName: ${oldName}, finalNewName: ${finalNewName})`);
|
|
3746
|
-
const existingSchemas = await db
|
|
3747
|
-
.select()
|
|
3748
|
-
.from(schemas)
|
|
3749
|
-
.where(eq(schemas.name, lookupName))
|
|
3750
|
-
.limit(10); // Get multiple to find drafts
|
|
3751
|
-
logger(`Found ${existingSchemas.length} records with name "${lookupName}"`);
|
|
3752
|
-
// If name changed, prefer draft records; otherwise prefer any record
|
|
3753
|
-
if (existingSchemas.length > 0) {
|
|
3754
|
-
if (oldName && oldName !== finalNewName) {
|
|
3755
|
-
// When name changes, prefer draft records
|
|
3756
|
-
const draftRecord = existingSchemas.find((s) => s.isDraft === true);
|
|
3757
|
-
const selectedRecord = draftRecord || existingSchemas[0];
|
|
3758
|
-
existingSchemaRecord = selectedRecord;
|
|
3759
|
-
logger(`Selected ${draftRecord ? 'draft' : 'first'} record (id: ${selectedRecord.id}, isDraft: ${selectedRecord.isDraft})`);
|
|
3760
|
-
}
|
|
3761
|
-
else {
|
|
3762
|
-
const selectedRecord = existingSchemas[0];
|
|
3763
|
-
existingSchemaRecord = selectedRecord;
|
|
3764
|
-
logger(`Selected first record (id: ${selectedRecord.id}, isDraft: ${selectedRecord.isDraft})`);
|
|
3765
|
-
}
|
|
3766
|
-
if (existingSchemaRecord?.schemaFileId) {
|
|
3767
|
-
existingSchemaId = existingSchemaRecord.schemaFileId;
|
|
3768
|
-
}
|
|
3769
|
-
}
|
|
3770
|
-
else {
|
|
3771
|
-
logger(`No records found with name "${lookupName}"`);
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
if (existingSchemaRecord) {
|
|
3775
|
-
logger(`Using existing record (id: ${existingSchemaRecord.id}, name: ${existingSchemaRecord.name}, isDraft: ${existingSchemaRecord.isDraft}, schemaFileId: ${existingSchemaRecord.schemaFileId || 'none'})`);
|
|
3776
|
-
}
|
|
3777
|
-
else {
|
|
3778
|
-
logger(`No existing record found`);
|
|
3779
|
-
}
|
|
3780
|
-
// Build current schema state from context
|
|
3781
|
-
// Use existing schemaFileId if we found one, otherwise use the one from context, or generate new
|
|
3782
|
-
const schemaFileId = existingSchemaId || context.id || generateId(); // id is now the schemaFileId (string)
|
|
3783
|
-
// Build metadata - if name changed, use newName, otherwise use context metadata
|
|
3784
|
-
// Always ensure metadata.name matches finalNewName to prevent inconsistencies
|
|
3785
|
-
const currentMetadata = context.metadata ? {
|
|
3786
|
-
...context.metadata,
|
|
3787
|
-
name: finalNewName, // Always use the final name (from newName parameter or context)
|
|
3788
|
-
updatedAt: new Date().toISOString(), // Always update timestamp when saving
|
|
3789
|
-
} : {
|
|
3790
|
-
name: finalNewName,
|
|
3791
|
-
createdAt: new Date().toISOString(),
|
|
3792
|
-
updatedAt: new Date().toISOString(),
|
|
3793
|
-
};
|
|
3794
|
-
// CRITICAL: Ensure schema.id always matches schemaFileId (database is source of truth)
|
|
3795
|
-
// Use existingSchemaRecord.schemaFileId if available, otherwise use the calculated schemaFileId
|
|
3796
|
-
const finalSchemaFileId = existingSchemaRecord?.schemaFileId || schemaFileId;
|
|
3797
|
-
// CRITICAL: Read models from Model instances, not from Schema context
|
|
3798
|
-
// Model instances are the source of truth for model data
|
|
3799
|
-
const modelsFromInstances = await this._buildModelsFromInstances();
|
|
3800
|
-
const currentSchema = {
|
|
3801
|
-
$schema: context.$schema || 'https://seedprotocol.org/schemas/data-model/v1',
|
|
3802
|
-
version: context.version || 1,
|
|
3803
|
-
id: finalSchemaFileId, // Always use schemaFileId from database as source of truth
|
|
3804
|
-
metadata: currentMetadata,
|
|
3805
|
-
models: modelsFromInstances, // Read from Model instances, not context.models
|
|
3806
|
-
enums: context.enums || {},
|
|
3807
|
-
migrations: context.migrations || [],
|
|
3808
|
-
...(context.nameMatch !== undefined && { nameMatch: context.nameMatch }),
|
|
3809
|
-
};
|
|
3810
|
-
// Log if there was a mismatch that we're fixing
|
|
3811
|
-
if (existingSchemaRecord?.schemaFileId && context.id && context.id !== finalSchemaFileId) {
|
|
3812
|
-
saveDraftLogger(`Fixed schema ID mismatch: context.id="${context.id}" does not match DB schemaFileId="${finalSchemaFileId}". Using DB value.`);
|
|
3813
|
-
}
|
|
3814
|
-
saveDraftLogger(`Building schema with metadata.name="${currentMetadata.name}", finalNewName="${finalNewName}"`);
|
|
3815
|
-
// If name changed, we MUST update the existing record (don't create a new one)
|
|
3816
|
-
if (oldName && oldName !== finalNewName) {
|
|
3817
|
-
if (existingSchemaRecord && existingSchemaRecord.id) {
|
|
3818
|
-
logger(`Updating existing schema record (id: ${existingSchemaRecord.id}) from "${oldName}" to "${finalNewName}"`);
|
|
3819
|
-
// Update the existing record with the new name
|
|
3820
|
-
const schemaDataString = JSON.stringify(currentSchema, null, 2);
|
|
3821
|
-
saveDraftLogger(`Saving schemaData with metadata.name="${currentSchema.metadata.name}", schemaData length=${schemaDataString.length}`);
|
|
3822
|
-
// CRITICAL: Ensure schemaFileId column matches schema.id (schema.id is now guaranteed to match finalSchemaFileId)
|
|
3823
|
-
// Always update schemaFileId to match the id in the schema JSON to maintain consistency
|
|
3824
|
-
clearNameMatchLevels(); // the stored schema file changes (ADR 0009)
|
|
3825
|
-
const updateData = {
|
|
3826
|
-
name: finalNewName,
|
|
3827
|
-
schemaData: schemaDataString,
|
|
3828
|
-
schemaFileId: finalSchemaFileId, // Always set to match schema.id
|
|
3829
|
-
version: currentSchema.version,
|
|
3830
|
-
updatedAt: new Date(currentSchema.metadata.updatedAt).getTime(),
|
|
3831
|
-
isDraft: true, // Ensure it's marked as a draft when saving via _saveDraftToDb
|
|
3832
|
-
};
|
|
3833
|
-
// Log if we're fixing a mismatch
|
|
3834
|
-
if (existingSchemaRecord.schemaFileId && existingSchemaRecord.schemaFileId !== finalSchemaFileId) {
|
|
3835
|
-
saveDraftLogger(`Fixing schemaFileId mismatch: DB had "${existingSchemaRecord.schemaFileId}", schema.id is "${finalSchemaFileId}". Updating DB to match schema.id.`);
|
|
3836
|
-
}
|
|
3837
|
-
else if (!existingSchemaRecord.schemaFileId && finalSchemaFileId) {
|
|
3838
|
-
saveDraftLogger(`Setting schemaFileId to ${finalSchemaFileId} (was null)`);
|
|
3839
|
-
}
|
|
3840
|
-
await db
|
|
3841
|
-
.update(schemas)
|
|
3842
|
-
.set(updateData)
|
|
3843
|
-
.where(eq(schemas.id, existingSchemaRecord.id));
|
|
3844
|
-
// Verify what was saved by reading it back
|
|
3845
|
-
const verifyRecord = await db
|
|
3846
|
-
.select()
|
|
3847
|
-
.from(schemas)
|
|
3848
|
-
.where(eq(schemas.id, existingSchemaRecord.id))
|
|
3849
|
-
.limit(1);
|
|
3850
|
-
if (verifyRecord.length > 0 && verifyRecord[0].schemaData) {
|
|
3851
|
-
try {
|
|
3852
|
-
const savedSchema = JSON.parse(verifyRecord[0].schemaData);
|
|
3853
|
-
saveDraftLogger(`Verified saved schemaData: metadata.name="${savedSchema.metadata?.name}", name column="${verifyRecord[0].name}", isDraft=${verifyRecord[0].isDraft}, schemaFileId="${verifyRecord[0].schemaFileId}"`);
|
|
3854
|
-
if (verifyRecord[0].isDraft !== true) {
|
|
3855
|
-
saveDraftLogger(`ERROR: isDraft is not true after save! Expected true, got ${verifyRecord[0].isDraft}. This will cause the schema to load from file instead of database!`);
|
|
3856
|
-
// Try to fix it immediately
|
|
3857
|
-
await db
|
|
3858
|
-
.update(schemas)
|
|
3859
|
-
.set({ isDraft: true })
|
|
3860
|
-
.where(eq(schemas.id, existingSchemaRecord.id));
|
|
3861
|
-
saveDraftLogger(`Attempted to fix isDraft by setting it to true again`);
|
|
3862
|
-
// Verify the fix
|
|
3863
|
-
const fixedRecord = await db
|
|
3864
|
-
.select()
|
|
3865
|
-
.from(schemas)
|
|
3866
|
-
.where(eq(schemas.id, existingSchemaRecord.id))
|
|
3867
|
-
.limit(1);
|
|
3868
|
-
if (fixedRecord.length > 0) {
|
|
3869
|
-
saveDraftLogger(`After fix attempt: isDraft=${fixedRecord[0].isDraft}`);
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
3872
|
-
}
|
|
3873
|
-
catch (error) {
|
|
3874
|
-
saveDraftLogger(`Error parsing saved schemaData for verification: ${error}`);
|
|
3875
|
-
}
|
|
3876
|
-
}
|
|
3877
|
-
else {
|
|
3878
|
-
saveDraftLogger(`WARNING: Could not verify saved record - record not found or has no schemaData`);
|
|
3879
|
-
}
|
|
3880
|
-
// Update the context's id (schemaFileId) and conflict detection metadata
|
|
3881
|
-
try {
|
|
3882
|
-
const snapshot = this._service.getSnapshot();
|
|
3883
|
-
if (snapshot.status !== 'stopped') {
|
|
3884
|
-
// Use finalSchemaFileId which matches the schema.id
|
|
3885
|
-
// Also update conflict detection metadata after successful save
|
|
3886
|
-
this._service.send({
|
|
3887
|
-
type: 'updateContext',
|
|
3888
|
-
id: finalSchemaFileId, // id is now the schemaFileId (string)
|
|
3889
|
-
_dbUpdatedAt: new Date(currentSchema.metadata.updatedAt).getTime(),
|
|
3890
|
-
_dbVersion: currentSchema.version,
|
|
3891
|
-
});
|
|
3892
|
-
}
|
|
3893
|
-
}
|
|
3894
|
-
catch (error) {
|
|
3895
|
-
// Service might be stopped, ignore
|
|
3896
|
-
logger(`Could not update id (schemaFileId) in context: ${error instanceof Error ? error.message : String(error)}`);
|
|
3897
|
-
}
|
|
3898
|
-
logger(`Successfully updated schema name from "${oldName}" to "${finalNewName}" in database`);
|
|
3899
|
-
Schema.savingSchemas.delete(saveKey);
|
|
3900
|
-
return;
|
|
3901
|
-
}
|
|
3902
|
-
else {
|
|
3903
|
-
// Name changed but we didn't find the existing record - try harder to find it
|
|
3904
|
-
logger(`WARNING: Name changed from "${oldName}" to "${finalNewName}" but existing record not found. Trying additional lookups...`);
|
|
3905
|
-
// Try to find by looking for ANY draft with the old name (even if it doesn't match exactly)
|
|
3906
|
-
const allDrafts = await db
|
|
3907
|
-
.select()
|
|
3908
|
-
.from(schemas)
|
|
3909
|
-
.where(eq(schemas.name, oldName))
|
|
3910
|
-
.limit(1);
|
|
3911
|
-
if (allDrafts.length > 0 && allDrafts[0].id) {
|
|
3912
|
-
const foundRecord = allDrafts[0];
|
|
3913
|
-
logger(`Found existing record by old name "${oldName}" (id: ${foundRecord.id}), updating to new name "${finalNewName}"`);
|
|
3914
|
-
// Update the existing record with the new name
|
|
3915
|
-
// CRITICAL: Ensure schemaFileId matches schema.id
|
|
3916
|
-
clearNameMatchLevels(); // the stored schema file changes (ADR 0009)
|
|
3917
|
-
await db
|
|
3918
|
-
.update(schemas)
|
|
3919
|
-
.set({
|
|
3920
|
-
name: finalNewName,
|
|
3921
|
-
schemaData: JSON.stringify(currentSchema, null, 2),
|
|
3922
|
-
schemaFileId: finalSchemaFileId, // Always set to match schema.id
|
|
3923
|
-
version: currentSchema.version,
|
|
3924
|
-
updatedAt: new Date(currentSchema.metadata.updatedAt).getTime(),
|
|
3925
|
-
isDraft: true, // Ensure it's marked as a draft when saving via _saveDraftToDb
|
|
3926
|
-
})
|
|
3927
|
-
.where(eq(schemas.id, foundRecord.id));
|
|
3928
|
-
// Update context with id (schemaFileId) and conflict detection metadata
|
|
3929
|
-
try {
|
|
3930
|
-
const snapshot = this._service.getSnapshot();
|
|
3931
|
-
if (snapshot.status !== 'stopped' && finalSchemaFileId) {
|
|
3932
|
-
this._service.send({
|
|
3933
|
-
type: 'updateContext',
|
|
3934
|
-
id: finalSchemaFileId, // id is now the schemaFileId (string)
|
|
3935
|
-
_dbUpdatedAt: new Date(currentSchema.metadata.updatedAt).getTime(),
|
|
3936
|
-
_dbVersion: currentSchema.version,
|
|
3937
|
-
});
|
|
3938
|
-
}
|
|
3939
|
-
}
|
|
3940
|
-
catch {
|
|
3941
|
-
// Service might be stopped, ignore
|
|
3942
|
-
}
|
|
3943
|
-
logger(`Successfully updated schema name from "${oldName}" to "${finalNewName}" in database (found by old name)`);
|
|
3944
|
-
Schema.savingSchemas.delete(saveKey);
|
|
3945
|
-
return;
|
|
3946
|
-
}
|
|
3947
|
-
// If we still can't find it, this is an error - don't create a duplicate
|
|
3948
|
-
logger(`ERROR: Could not find existing schema record with name "${oldName}". Cannot update name to "${finalNewName}" without creating a duplicate.`);
|
|
3949
|
-
Schema.savingSchemas.delete(saveKey);
|
|
3950
|
-
throw new Error(`Cannot update schema name: existing schema with name "${oldName}" not found in database`);
|
|
3951
|
-
}
|
|
3952
|
-
}
|
|
3953
|
-
// If name changed, we should have already updated the record above and returned
|
|
3954
|
-
// Only call addSchemaToDb if name didn't change (normal save scenario)
|
|
3955
|
-
if (oldName && oldName !== finalNewName) {
|
|
3956
|
-
// This should never happen - we should have updated above and returned
|
|
3957
|
-
// But if we get here, it means the update path didn't work, so we need to handle it
|
|
3958
|
-
logger(`ERROR: Name changed from "${oldName}" to "${finalNewName}" but update path didn't complete. This should not occur.`);
|
|
3959
|
-
Schema.savingSchemas.delete(saveKey);
|
|
3960
|
-
throw new Error(`Failed to update schema name: existing record not found or update failed`);
|
|
3961
|
-
}
|
|
3962
|
-
// Otherwise, use addSchemaToDb which will handle create/update logic (for normal saves, not name changes)
|
|
3963
|
-
await addSchemaToDb({
|
|
3964
|
-
name: finalNewName,
|
|
3965
|
-
version: currentSchema.version,
|
|
3966
|
-
createdAt: new Date(currentSchema.metadata.createdAt).getTime(),
|
|
3967
|
-
updatedAt: new Date(currentSchema.metadata.updatedAt).getTime(),
|
|
3968
|
-
}, currentSchema.id, // schemaFileId
|
|
3969
|
-
JSON.stringify(currentSchema, null, 2), // schemaData
|
|
3970
|
-
true);
|
|
3971
|
-
// Update conflict detection metadata after successful save
|
|
3972
|
-
try {
|
|
3973
|
-
const snapshot = this._service.getSnapshot();
|
|
3974
|
-
if (snapshot.status !== 'stopped') {
|
|
3975
|
-
this._service.send({
|
|
3976
|
-
type: 'updateContext',
|
|
3977
|
-
_dbUpdatedAt: new Date(currentSchema.metadata.updatedAt).getTime(),
|
|
3978
|
-
_dbVersion: currentSchema.version,
|
|
3979
|
-
});
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
catch {
|
|
3983
|
-
// Service might be stopped, ignore
|
|
3984
|
-
}
|
|
3985
|
-
saveDraftLogger(`Successfully saved draft schema "${finalNewName}" to database (key: ${saveKey})`);
|
|
3986
|
-
logger(`Saved draft schema ${finalNewName} to database`);
|
|
3987
|
-
}
|
|
3988
|
-
catch (error) {
|
|
3989
|
-
saveDraftLogger(`ERROR: Failed to save draft to database for schema "${schemaName}" (key: ${saveKey}): ${error instanceof Error ? error.message : String(error)}`);
|
|
3990
|
-
logger(`Failed to save draft to database: ${error instanceof Error ? error.message : String(error)}`);
|
|
3991
|
-
throw error;
|
|
3992
|
-
}
|
|
3993
|
-
finally {
|
|
3994
|
-
// Always remove from saving set, even if there was an error
|
|
3995
|
-
Schema.savingSchemas.delete(saveKey);
|
|
3996
|
-
saveDraftLogger(`Removed schema from saving set (key: ${saveKey})`);
|
|
3997
|
-
}
|
|
3998
|
-
}
|
|
3999
|
-
/**
|
|
4000
|
-
* Force-evict the cached Schema instance(s) named `schemaName`, ignoring refCounts: stop their live
|
|
4001
|
-
* query and service. Schema.destroy does the same for itself. For code that deletes a schema's rows
|
|
4002
|
-
* without destroying it (test cleanup): a live Schema keeps re-creating its models from its live
|
|
4003
|
-
* query while their rows are being deleted.
|
|
4004
|
-
*/
|
|
4005
|
-
static evict(schemaName) {
|
|
4006
|
-
const instances = new Set();
|
|
4007
|
-
for (const { instance } of Schema.instanceCacheById.values())
|
|
4008
|
-
instances.add(instance);
|
|
4009
|
-
for (const { instance } of Schema.instanceCacheByName.values())
|
|
4010
|
-
instances.add(instance);
|
|
4011
|
-
for (const instance of instances) {
|
|
4012
|
-
let context;
|
|
4013
|
-
try {
|
|
4014
|
-
context = instance._getSnapshotContext();
|
|
4015
|
-
}
|
|
4016
|
-
catch {
|
|
4017
|
-
continue;
|
|
4018
|
-
}
|
|
4019
|
-
if (context.schemaName !== schemaName)
|
|
4020
|
-
continue;
|
|
4021
|
-
clearDestroySubscriptions(instance, {
|
|
4022
|
-
instanceState: schemaInstanceState,
|
|
4023
|
-
onUnload: () => schemaInstanceState.delete(instance),
|
|
4024
|
-
});
|
|
4025
|
-
forceRemoveFromCaches(instance, {
|
|
4026
|
-
getCacheKeys: () => [context.id, context.schemaName].filter((k) => !!k),
|
|
4027
|
-
caches: [
|
|
4028
|
-
Schema.instanceCacheById,
|
|
4029
|
-
Schema.instanceCacheByName,
|
|
4030
|
-
],
|
|
4031
|
-
});
|
|
4032
|
-
try {
|
|
4033
|
-
instance._service.stop();
|
|
4034
|
-
}
|
|
4035
|
-
catch {
|
|
4036
|
-
// Service might already be stopped
|
|
4037
|
-
}
|
|
4038
|
-
}
|
|
4039
|
-
}
|
|
4040
|
-
unload() {
|
|
4041
|
-
try {
|
|
4042
|
-
const context = this._getSnapshotContext();
|
|
4043
|
-
const cacheKeys = [];
|
|
4044
|
-
if (context.id) {
|
|
4045
|
-
cacheKeys.push(context.id);
|
|
4046
|
-
}
|
|
4047
|
-
if (context.schemaName) {
|
|
4048
|
-
cacheKeys.push(context.schemaName);
|
|
4049
|
-
}
|
|
4050
|
-
unloadEntity(this, {
|
|
4051
|
-
getCacheKeys: () => cacheKeys,
|
|
4052
|
-
caches: [Schema.instanceCacheById, Schema.instanceCacheByName],
|
|
4053
|
-
instanceState: schemaInstanceState,
|
|
4054
|
-
getService: (instance) => instance._service,
|
|
4055
|
-
onUnload: () => {
|
|
4056
|
-
// Clean up WeakMap entry
|
|
4057
|
-
schemaInstanceState.delete(this);
|
|
4058
|
-
if (context.schemaName)
|
|
4059
|
-
Model.releaseSchemaHolds(context.schemaName);
|
|
4060
|
-
},
|
|
4061
|
-
});
|
|
4062
|
-
}
|
|
4063
|
-
catch (error) {
|
|
4064
|
-
// Service might be stopped, can't get context - that's okay
|
|
4065
|
-
logger(`Could not get context during unload: ${error instanceof Error ? error.message : String(error)}`);
|
|
4066
|
-
// Still try to clean up what we can
|
|
4067
|
-
const instanceState = schemaInstanceState.get(this);
|
|
4068
|
-
if (instanceState?.liveQuerySubscription) {
|
|
4069
|
-
instanceState.liveQuerySubscription.unsubscribe();
|
|
4070
|
-
instanceState.liveQuerySubscription = null;
|
|
4071
|
-
}
|
|
4072
|
-
schemaInstanceState.delete(this);
|
|
4073
|
-
try {
|
|
4074
|
-
this._service.stop();
|
|
4075
|
-
}
|
|
4076
|
-
catch {
|
|
4077
|
-
// Service might already be stopped
|
|
4078
|
-
}
|
|
4079
|
-
}
|
|
4080
|
-
}
|
|
4081
|
-
/**
|
|
4082
|
-
* Destroy the schema instance completely: remove from caches, delete from database (cascade),
|
|
4083
|
-
* and stop the service. Uses shared destroy helpers.
|
|
4084
|
-
*/
|
|
4085
|
-
async destroy() {
|
|
4086
|
-
const context = this._getSnapshotContext();
|
|
4087
|
-
const schemaFileId = context.id;
|
|
4088
|
-
const schemaName = context.schemaName;
|
|
4089
|
-
clearDestroySubscriptions(this, {
|
|
4090
|
-
instanceState: schemaInstanceState,
|
|
4091
|
-
onUnload: () => schemaInstanceState.delete(this),
|
|
4092
|
-
});
|
|
4093
|
-
const cacheKeys = [];
|
|
4094
|
-
if (schemaFileId)
|
|
4095
|
-
cacheKeys.push(schemaFileId);
|
|
4096
|
-
if (schemaName)
|
|
4097
|
-
cacheKeys.push(schemaName);
|
|
4098
|
-
forceRemoveFromCaches(this, {
|
|
4099
|
-
getCacheKeys: () => cacheKeys,
|
|
4100
|
-
caches: [
|
|
4101
|
-
Schema.instanceCacheById,
|
|
4102
|
-
Schema.instanceCacheByName,
|
|
4103
|
-
],
|
|
4104
|
-
});
|
|
4105
|
-
// Until its rows are gone, nothing may re-create this schema's models. Its own actor stays live
|
|
4106
|
-
// till then, and its live query reacting to destroyStarted could read a model row the drained
|
|
4107
|
-
// write had just landed and re-create (and re-write) that model after the delete below.
|
|
4108
|
-
if (schemaName)
|
|
4109
|
-
recordSchemaDestroy(schemaName);
|
|
4110
|
-
try {
|
|
4111
|
-
await this._destroyRows(schemaFileId, schemaName, context.metadata?.name);
|
|
4112
|
-
}
|
|
4113
|
-
finally {
|
|
4114
|
-
if (schemaName)
|
|
4115
|
-
finishSchemaDestroy(schemaName);
|
|
4116
|
-
}
|
|
4117
|
-
}
|
|
4118
|
-
async _destroyRows(schemaFileId, schemaName, metadataName) {
|
|
4119
|
-
// The rows for this schema's models and properties are deleted below; evict their cached
|
|
4120
|
-
// instances too, or a re-import would get them back with dangling _dbIds.
|
|
4121
|
-
if (schemaName) {
|
|
4122
|
-
const evictedModelNames = Model.evictForSchema(schemaName);
|
|
4123
|
-
const { ModelProperty } = await import('./ModelProperty-DDD47KVm.js');
|
|
4124
|
-
ModelProperty.evictForModels(evictedModelNames, schemaName);
|
|
4125
|
-
// Evicting stops new writes but not ones already running; let those land before deleting their rows
|
|
4126
|
-
const _mod_10 = await import('./errors-CS3nDpUU.js');
|
|
4127
|
-
const _ns_10 = _mod_10.h;
|
|
4128
|
-
const { waitForInFlightWrites } = _ns_10;
|
|
4129
|
-
await waitForInFlightWrites();
|
|
4130
|
-
}
|
|
4131
|
-
await runDestroyLifecycle(this, {
|
|
4132
|
-
getService: (instance) => instance._service,
|
|
4133
|
-
doDestroy: async () => {
|
|
4134
|
-
const db = BaseDb.getAppDb();
|
|
4135
|
-
if (!db || !schemaFileId)
|
|
4136
|
-
return;
|
|
4137
|
-
const schemaRecords = await db
|
|
4138
|
-
.select({ id: schemas.id })
|
|
4139
|
-
.from(schemas)
|
|
4140
|
-
.where(eq(schemas.schemaFileId, schemaFileId));
|
|
4141
|
-
if (schemaRecords.length === 0)
|
|
4142
|
-
return;
|
|
4143
|
-
const schemaIds = schemaRecords
|
|
4144
|
-
.map((r) => r.id)
|
|
4145
|
-
.filter((id) => id != null);
|
|
4146
|
-
if (schemaIds.length === 0)
|
|
4147
|
-
return;
|
|
4148
|
-
const drizzleMod = await import('drizzle-orm');
|
|
4149
|
-
const { inArray } = drizzleMod;
|
|
4150
|
-
const joinRows = await db
|
|
4151
|
-
.select({ modelId: modelSchemas.modelId })
|
|
4152
|
-
.from(modelSchemas)
|
|
4153
|
-
.where(inArray(modelSchemas.schemaId, schemaIds));
|
|
4154
|
-
const modelIds = [
|
|
4155
|
-
...new Set(joinRows
|
|
4156
|
-
.map((r) => r.modelId)
|
|
4157
|
-
.filter((id) => id != null)),
|
|
4158
|
-
];
|
|
4159
|
-
const { detachModelRows } = await import('./detachRowsBeforeDelete-Di1enVUF.js');
|
|
4160
|
-
await detachModelRows(db, modelIds);
|
|
4161
|
-
await db.delete(modelSchemas).where(inArray(modelSchemas.schemaId, schemaIds));
|
|
4162
|
-
for (const modelId of modelIds) {
|
|
4163
|
-
await db.delete(properties).where(eq(properties.modelId, modelId));
|
|
4164
|
-
}
|
|
4165
|
-
for (const modelId of modelIds) {
|
|
4166
|
-
await db.delete(models).where(eq(models.id, modelId));
|
|
4167
|
-
}
|
|
4168
|
-
await db.delete(schemas).where(eq(schemas.schemaFileId, schemaFileId));
|
|
4169
|
-
// Delete the SDK's own copies of this schema's files (all versions) in the working dir.
|
|
4170
|
-
// loadAllSchemasFromDb imports any schema file without a DB row, so leaving them brought
|
|
4171
|
-
// the destroyed schema back on the next Schema.all().
|
|
4172
|
-
// Match on name as well as id: versions imported from complete schema files can each
|
|
4173
|
-
// have their own id, and any version left behind would bring the schema back.
|
|
4174
|
-
const _mod_11 = await import('./ClientManager-DqL0q_eu.js');
|
|
4175
|
-
const _ns_11 = _mod_11.S;
|
|
4176
|
-
const { listCompleteSchemaFiles } = _ns_11;
|
|
4177
|
-
const files = await listCompleteSchemaFiles().catch(() => []);
|
|
4178
|
-
const fs = await BaseFileManager.getFs();
|
|
4179
|
-
const names = new Set([schemaName, metadataName].filter(Boolean));
|
|
4180
|
-
for (const file of files) {
|
|
4181
|
-
if (file.schemaFileId === schemaFileId || names.has(file.name)) {
|
|
4182
|
-
await fs.promises.unlink(file.filePath).catch(() => { });
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
},
|
|
4186
|
-
});
|
|
4187
|
-
}
|
|
4188
|
-
/**
|
|
4189
|
-
* Set up liveQuery subscription to watch for model changes in the database
|
|
4190
|
-
* This enables cross-instance synchronization (e.g., changes in other tabs/windows)
|
|
4191
|
-
*/
|
|
4192
|
-
_setupLiveQuerySubscription() {
|
|
4193
|
-
// Only set up in browser environment where liveQuery is available
|
|
4194
|
-
if (typeof window === 'undefined') {
|
|
4195
|
-
return;
|
|
4196
|
-
}
|
|
4197
|
-
setupEntityLiveQuery(this, {
|
|
4198
|
-
getEntityId: async (schema) => {
|
|
4199
|
-
const db = BaseDb.getAppDb();
|
|
4200
|
-
if (!db) {
|
|
4201
|
-
return undefined;
|
|
4202
|
-
}
|
|
4203
|
-
const context = schema._getSnapshotContext();
|
|
4204
|
-
const schemaName = context.metadata?.name;
|
|
4205
|
-
if (!schemaName) {
|
|
4206
|
-
return undefined;
|
|
4207
|
-
}
|
|
4208
|
-
const schemaRecords = await db
|
|
4209
|
-
.select()
|
|
4210
|
-
.from(schemas)
|
|
4211
|
-
.where(eq(schemas.name, schemaName))
|
|
4212
|
-
.limit(1);
|
|
4213
|
-
if (schemaRecords.length === 0 || !schemaRecords[0].id) {
|
|
4214
|
-
return undefined;
|
|
4215
|
-
}
|
|
4216
|
-
return schemaRecords[0].id;
|
|
4217
|
-
},
|
|
4218
|
-
buildQuery: async (schemaId) => {
|
|
4219
|
-
const db = BaseDb.getAppDb();
|
|
4220
|
-
if (!db) {
|
|
4221
|
-
throw new Error('Database not available');
|
|
4222
|
-
}
|
|
4223
|
-
return BaseDb.liveQuery(db
|
|
4224
|
-
.select({
|
|
4225
|
-
modelId: modelSchemas.modelId,
|
|
4226
|
-
modelName: models.name,
|
|
4227
|
-
modelFileId: models.schemaFileId,
|
|
4228
|
-
})
|
|
4229
|
-
.from(modelSchemas)
|
|
4230
|
-
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
4231
|
-
.where(eq(modelSchemas.schemaId, typeof schemaId === 'string' ? parseInt(schemaId, 10) : schemaId)));
|
|
4232
|
-
},
|
|
4233
|
-
extractEntityIds: (rows) => rows.map(row => row.modelFileId).filter(Boolean),
|
|
4234
|
-
updateContext: (schema, ids) => {
|
|
4235
|
-
// Helper function to send updateContext event with model IDs
|
|
4236
|
-
const sendUpdateContext = () => {
|
|
4237
|
-
// Double-check instance state still exists
|
|
4238
|
-
const verifyInstanceState = schemaInstanceState.get(schema);
|
|
4239
|
-
if (!verifyInstanceState) {
|
|
4240
|
-
logger(`[Schema._setupLiveQuerySubscription] Instance state cleaned up before sending updateContext`);
|
|
4241
|
-
return;
|
|
4242
|
-
}
|
|
4243
|
-
try {
|
|
4244
|
-
const snapshot = schema._service.getSnapshot();
|
|
4245
|
-
if (snapshot.status === 'stopped') {
|
|
4246
|
-
logger(`[Schema._setupLiveQuerySubscription] Service stopped before sending, skipping`);
|
|
4247
|
-
return;
|
|
4248
|
-
}
|
|
4249
|
-
// Send updateContext with liveQueryModelIds in service context for reactive updates
|
|
4250
|
-
schema._service.send({
|
|
4251
|
-
type: 'updateContext',
|
|
4252
|
-
_liveQueryModelIds: ids, // Store in service context for reactivity
|
|
4253
|
-
_modelsUpdated: Date.now(), // Internal field for tracking
|
|
4254
|
-
});
|
|
4255
|
-
logger(`[Schema._setupLiveQuerySubscription] Sent updateContext event with ${ids.length} model IDs`);
|
|
4256
|
-
}
|
|
4257
|
-
catch (error) {
|
|
4258
|
-
logger(`[Schema._setupLiveQuerySubscription] Error sending updateContext: ${error}`);
|
|
4259
|
-
}
|
|
4260
|
-
};
|
|
4261
|
-
// Check if service is stopped before sending events
|
|
4262
|
-
const snapshot = schema._service.getSnapshot();
|
|
4263
|
-
const isServiceStopped = snapshot.status === 'stopped';
|
|
4264
|
-
if (isServiceStopped) {
|
|
4265
|
-
logger(`[Schema._setupLiveQuerySubscription] Service is stopped, restarting before sending updateContext`);
|
|
4266
|
-
// Restart the service first
|
|
4267
|
-
schema._service.start();
|
|
4268
|
-
// Wait for service to be ready (idle state) before sending event
|
|
4269
|
-
setTimeout(() => {
|
|
4270
|
-
const delayedInstanceState = schemaInstanceState.get(schema);
|
|
4271
|
-
if (!delayedInstanceState) {
|
|
4272
|
-
logger(`[Schema._setupLiveQuerySubscription] Instance state cleaned up during restart delay`);
|
|
4273
|
-
return;
|
|
4274
|
-
}
|
|
4275
|
-
const newSnapshot = schema._service.getSnapshot();
|
|
4276
|
-
if (newSnapshot.status !== 'stopped') {
|
|
4277
|
-
sendUpdateContext();
|
|
4278
|
-
}
|
|
4279
|
-
else {
|
|
4280
|
-
logger(`[Schema._setupLiveQuerySubscription] Service still stopped after restart attempt`);
|
|
4281
|
-
}
|
|
4282
|
-
}, 10);
|
|
4283
|
-
}
|
|
4284
|
-
else {
|
|
4285
|
-
// Service is running, send immediately
|
|
4286
|
-
sendUpdateContext();
|
|
4287
|
-
}
|
|
4288
|
-
},
|
|
4289
|
-
createChildInstances: async (ids) => {
|
|
4290
|
-
// One at a time: an eviction of this schema part way through stops the rest
|
|
4291
|
-
const evictionEpoch = currentEvictionEpoch();
|
|
4292
|
-
for (const id of ids) {
|
|
4293
|
-
await Model.createById(id, { evictionEpoch, hold: false });
|
|
4294
|
-
}
|
|
4295
|
-
},
|
|
4296
|
-
queryInitialData: async (schemaId) => {
|
|
4297
|
-
const db = BaseDb.getAppDb();
|
|
4298
|
-
if (!db) {
|
|
4299
|
-
return [];
|
|
4300
|
-
}
|
|
4301
|
-
// Retry logic for initial query
|
|
4302
|
-
const queryInitialModels = async (retries = 3) => {
|
|
4303
|
-
try {
|
|
4304
|
-
const initialModels = await db
|
|
4305
|
-
.select({
|
|
4306
|
-
modelId: modelSchemas.modelId,
|
|
4307
|
-
modelName: models.name,
|
|
4308
|
-
modelFileId: models.schemaFileId,
|
|
4309
|
-
})
|
|
4310
|
-
.from(modelSchemas)
|
|
4311
|
-
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
4312
|
-
.where(eq(modelSchemas.schemaId, typeof schemaId === 'string' ? parseInt(schemaId, 10) : schemaId));
|
|
4313
|
-
logger(`[Schema._setupLiveQuerySubscription] Initial query found ${initialModels.length} models`);
|
|
4314
|
-
if (initialModels.length > 0) {
|
|
4315
|
-
return initialModels.map((row) => ({
|
|
4316
|
-
modelId: row.modelId,
|
|
4317
|
-
modelName: row.modelName,
|
|
4318
|
-
modelFileId: row.modelFileId || '',
|
|
4319
|
-
}));
|
|
4320
|
-
}
|
|
4321
|
-
else if (retries > 0) {
|
|
4322
|
-
logger(`[Schema._setupLiveQuerySubscription] No models found, retrying... (${retries} retries left)`);
|
|
4323
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4324
|
-
return queryInitialModels(retries - 1);
|
|
4325
|
-
}
|
|
4326
|
-
else {
|
|
4327
|
-
logger(`[Schema._setupLiveQuerySubscription] No models found in initial query after retries`);
|
|
4328
|
-
return [];
|
|
4329
|
-
}
|
|
4330
|
-
}
|
|
4331
|
-
catch (error) {
|
|
4332
|
-
if (retries > 0) {
|
|
4333
|
-
logger(`[Schema._setupLiveQuerySubscription] Error querying initial models, retrying... (${retries} retries left): ${error}`);
|
|
4334
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4335
|
-
return queryInitialModels(retries - 1);
|
|
4336
|
-
}
|
|
4337
|
-
else {
|
|
4338
|
-
logger(`[Schema._setupLiveQuerySubscription] Error querying initial models after retries: ${error}`);
|
|
4339
|
-
return [];
|
|
4340
|
-
}
|
|
4341
|
-
}
|
|
4342
|
-
};
|
|
4343
|
-
return queryInitialModels();
|
|
4344
|
-
},
|
|
4345
|
-
instanceState: schemaInstanceState,
|
|
4346
|
-
loggerName: 'seedSdk:schema:liveQuery',
|
|
4347
|
-
isReady: (schema) => {
|
|
4348
|
-
const snapshot = schema._service.getSnapshot();
|
|
4349
|
-
return snapshot.value === 'idle' && !!snapshot.context.metadata?.name;
|
|
4350
|
-
},
|
|
4351
|
-
});
|
|
4352
|
-
}
|
|
4353
|
-
}
|
|
4354
|
-
// Cache by schemaFileId (primary key - doesn't change when name changes)
|
|
4355
|
-
Schema.instanceCacheById = new Map();
|
|
4356
|
-
// Temporary cache by schemaName (used until schemaFileId is available)
|
|
4357
|
-
Schema.instanceCacheByName = new Map();
|
|
4358
|
-
// Deduplicate concurrent Schema.create(schemaName) calls - prevents many parallel loads for same schema
|
|
4359
|
-
Schema.pendingSchemaByName = new Map();
|
|
4360
|
-
// Track which schemas are currently being saved to prevent concurrent saves
|
|
4361
|
-
Schema.savingSchemas = new Set();
|
|
4362
|
-
|
|
4363
|
-
var Schema$1 = /*#__PURE__*/Object.freeze({
|
|
4364
|
-
__proto__: null,
|
|
4365
|
-
Schema: Schema,
|
|
4366
|
-
schemaInstanceState: schemaInstanceState
|
|
4367
|
-
});
|
|
4368
|
-
|
|
4369
|
-
export { Schema as S, Schema$1 as a, renameModelProperty as r, schemaMachine as s, updateModelProperties as u };
|
|
4370
|
-
//# sourceMappingURL=Schema-BS8oR7QV.js.map
|