@seedprotocol/sdk 0.4.32 → 0.5.1
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/BasePathResolver-BDgc7c4T.js +485 -0
- package/dist/BasePathResolver-BDgc7c4T.js.map +1 -0
- package/dist/{ModelProperty-e_hXSFvf.js → ModelProperty-D-ysg8zA.js} +37 -43
- package/dist/ModelProperty-D-ysg8zA.js.map +1 -0
- package/dist/{Schema-DonRkUOJ.js → Schema-CyCftnKI.js} +40 -40
- package/dist/{Schema-DonRkUOJ.js.map → Schema-CyCftnKI.js.map} +1 -1
- package/dist/{SchemaValidationService-DcmfhQae.js → SchemaValidationService-POM6jHbJ.js} +9 -5
- package/dist/{SchemaValidationService-DcmfhQae.js.map → SchemaValidationService-POM6jHbJ.js.map} +1 -1
- package/dist/{backfillMetadataPropertyIds-CKTfvFgQ.js → backfillMetadataPropertyIds-DzZw57iJ.js} +3 -11
- package/dist/{backfillMetadataPropertyIds-CKTfvFgQ.js.map → backfillMetadataPropertyIds-DzZw57iJ.js.map} +1 -1
- package/dist/cjs/{ModelProperty-BQYU5hPQ.js → ModelProperty-CmuGJvyi.js} +25 -26
- package/dist/cjs/{ModelProperty-BQYU5hPQ.js.map → ModelProperty-CmuGJvyi.js.map} +1 -1
- package/dist/cjs/{Schema-BcRHcQ0W.js → Schema-gpFnZMhB.js} +28 -29
- package/dist/cjs/{Schema-BcRHcQ0W.js.map → Schema-gpFnZMhB.js.map} +1 -1
- package/dist/cjs/{SchemaValidationService-CQNDgyZl.js → SchemaValidationService-E3xTSRai.js} +9 -10
- package/dist/cjs/{SchemaValidationService-CQNDgyZl.js.map → SchemaValidationService-E3xTSRai.js.map} +1 -1
- package/dist/cjs/{backfillMetadataPropertyIds-Dn4oVmEv.js → backfillMetadataPropertyIds-_bivrvRs.js} +11 -12
- package/dist/cjs/{backfillMetadataPropertyIds-Dn4oVmEv.js.map → backfillMetadataPropertyIds-_bivrvRs.js.map} +1 -1
- package/dist/cjs/eas-BwDKJmlb.js +112 -0
- package/dist/cjs/eas-BwDKJmlb.js.map +1 -0
- package/dist/cjs/{getItem-DpnPrzl3.js → getItem-MShQw3sz.js} +11 -12
- package/dist/cjs/{getItem-DpnPrzl3.js.map → getItem-MShQw3sz.js.map} +1 -1
- package/dist/cjs/{getPublishPayload-CGKMEL3H.js → getPublishPayload-C1nOOXp_.js} +93 -83
- package/dist/cjs/getPublishPayload-C1nOOXp_.js.map +1 -0
- package/dist/cjs/{getPublishUploads-DeI0f2UN.js → getPublishUploads-BjgxgrKD.js} +17 -18
- package/dist/cjs/{getPublishUploads-DeI0f2UN.js.map → getPublishUploads-BjgxgrKD.js.map} +1 -1
- package/dist/cjs/{getSegmentedItemProperties-BgS_FhAx.js → getSegmentedItemProperties-Cuw7wcxb.js} +64 -15
- package/dist/cjs/getSegmentedItemProperties-Cuw7wcxb.js.map +1 -0
- package/dist/cjs/{index-BnAqr2le.js → index-9Z2FOvLD.js} +12 -13
- package/dist/cjs/index-9Z2FOvLD.js.map +1 -0
- package/dist/cjs/{index-ByozbTn_.js → index-vk2DeF2D.js} +1237 -3235
- package/dist/cjs/index-vk2DeF2D.js.map +1 -0
- package/dist/cjs/index.browser-BSf8HpnU.js +22 -0
- package/dist/cjs/index.browser-BSf8HpnU.js.map +1 -0
- package/dist/cjs/{ownership-CsWuaNfH.js → ownership-DhWda0J9.js} +11 -25
- package/dist/cjs/{ownership-CsWuaNfH.js.map → ownership-DhWda0J9.js.map} +1 -1
- package/dist/cjs/{property-CeR45LMx.js → property-D3Ww2_Qh.js} +13 -14
- package/dist/cjs/{property-CeR45LMx.js.map → property-D3Ww2_Qh.js.map} +1 -1
- package/dist/cjs/{syncDbWithEas-CA2Lum5V.js → syncDbWithEas-Dht3_7UD.js} +26 -39
- package/dist/cjs/syncDbWithEas-Dht3_7UD.js.map +1 -0
- package/dist/configurePlatform-R5neRbQ9.js +27 -0
- package/dist/configurePlatform-R5neRbQ9.js.map +1 -0
- package/dist/constants-B5S5JPDK.js +660 -0
- package/dist/constants-B5S5JPDK.js.map +1 -0
- package/dist/constants-DMHTYMgJ.js +120 -0
- package/dist/constants-DMHTYMgJ.js.map +1 -0
- package/dist/db-Cvh84NY7.js +1972 -0
- package/dist/db-Cvh84NY7.js.map +1 -0
- package/dist/eas-D9b7z2Wn.js +68 -0
- package/dist/eas-D9b7z2Wn.js.map +1 -0
- package/dist/{getItem-GOXWo_Iy.js → getItem-B81V3m4S.js} +10 -6
- package/dist/{getItem-GOXWo_Iy.js.map → getItem-B81V3m4S.js.map} +1 -1
- package/dist/{getPublishPayload-D16enahU.js → getPublishPayload-CNYsn8-6.js} +87 -74
- package/dist/getPublishPayload-CNYsn8-6.js.map +1 -0
- package/dist/{getPublishUploads-wGqH19UY.js → getPublishUploads-D9rPHkB7.js} +14 -10
- package/dist/{getPublishUploads-wGqH19UY.js.map → getPublishUploads-D9rPHkB7.js.map} +1 -1
- package/dist/{getSegmentedItemProperties-Be3kYWY3.js → getSegmentedItemProperties-Bg3_b-b1.js} +18 -15
- package/dist/getSegmentedItemProperties-Bg3_b-b1.js.map +1 -0
- package/dist/{index-BcXKZQpX.js → index--F3Wolt5.js} +1171 -5452
- package/dist/index--F3Wolt5.js.map +1 -0
- package/dist/{index-BFyMxc7e.js → index-QMrBK-L3.js} +2 -2
- package/dist/index-QMrBK-L3.js.map +1 -0
- package/dist/index-oi4roUVf.js +22 -0
- package/dist/index-oi4roUVf.js.map +1 -0
- package/dist/index.node-Bow8kHew.js +503 -0
- package/dist/index.node-Bow8kHew.js.map +1 -0
- package/dist/main.cjs +16 -15
- package/dist/main.cjs.map +1 -1
- package/dist/main.js +31 -22
- package/dist/main.js.map +1 -1
- package/dist/node.js +37 -363
- package/dist/node.js.map +1 -1
- package/dist/{ownership-C17YhE7J.js → ownership-BF_ZUKIp.js} +13 -9
- package/dist/{ownership-C17YhE7J.js.map → ownership-BF_ZUKIp.js.map} +1 -1
- package/dist/platform.browser.js +3499 -0
- package/dist/platform.browser.js.map +1 -0
- package/dist/platform.node.js +19 -0
- package/dist/platform.node.js.map +1 -0
- package/dist/{property-CRAau-Q-.js → property-BdyDfcSc.js} +14 -11
- package/dist/property-BdyDfcSc.js.map +1 -0
- package/dist/src/Item/Item.d.ts.map +1 -1
- package/dist/src/Item/service/actors/fetchDataFromEas.d.ts.map +1 -1
- package/dist/src/Model/service/actors/createModelProperties.d.ts.map +1 -1
- package/dist/src/Model/service/modelMachine.d.ts +1 -1
- package/dist/src/browser/db/Db.d.ts +17 -18
- package/dist/src/browser/db/Db.d.ts.map +1 -1
- package/dist/src/browser/helpers/ArweaveClient.d.ts +10 -27
- package/dist/src/browser/helpers/ArweaveClient.d.ts.map +1 -1
- package/dist/src/browser/helpers/EasClient.d.ts +6 -4
- package/dist/src/browser/helpers/EasClient.d.ts.map +1 -1
- package/dist/src/browser/helpers/FileManager.d.ts +27 -26
- package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
- package/dist/src/browser/helpers/PathResolver.d.ts +6 -5
- package/dist/src/browser/helpers/PathResolver.d.ts.map +1 -1
- package/dist/src/browser/helpers/QueryClient.d.ts +6 -5
- package/dist/src/browser/helpers/QueryClient.d.ts.map +1 -1
- package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
- package/dist/src/client/actors/platformClassesInit.d.ts.map +1 -1
- package/dist/src/client/actors/processSchemaFiles.d.ts.map +1 -1
- package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
- package/dist/src/db/Db/BaseDb.d.ts +6 -26
- package/dist/src/db/Db/BaseDb.d.ts.map +1 -1
- package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
- package/dist/src/db/write/createMetadata.d.ts.map +1 -1
- package/dist/src/db/write/updateMetadata.d.ts.map +1 -1
- package/dist/src/eas.d.ts +4 -37
- package/dist/src/eas.d.ts.map +1 -1
- package/dist/src/helpers/ArweaveClient/BaseArweaveClient.d.ts +1 -101
- package/dist/src/helpers/ArweaveClient/BaseArweaveClient.d.ts.map +1 -1
- package/dist/src/helpers/ArweaveClient/arweaveL1UploadApi.d.ts +1 -51
- package/dist/src/helpers/ArweaveClient/arweaveL1UploadApi.d.ts.map +1 -1
- package/dist/src/helpers/ArweaveClient/index.d.ts +2 -2
- package/dist/src/helpers/ArweaveClient/index.d.ts.map +1 -1
- package/dist/src/helpers/ArweaveClient/selectReadGateway.d.ts +1 -15
- package/dist/src/helpers/ArweaveClient/selectReadGateway.d.ts.map +1 -1
- package/dist/src/helpers/ArweaveClient/uploadApiVerification.d.ts +1 -22
- package/dist/src/helpers/ArweaveClient/uploadApiVerification.d.ts.map +1 -1
- package/dist/src/helpers/EasClient/BaseEasClient.d.ts +1 -9
- package/dist/src/helpers/EasClient/BaseEasClient.d.ts.map +1 -1
- package/dist/src/helpers/FileManager/BaseFileManager.d.ts +4 -2
- package/dist/src/helpers/FileManager/BaseFileManager.d.ts.map +1 -1
- package/dist/src/helpers/FileManager/IFileManager.d.ts +44 -0
- package/dist/src/helpers/FileManager/IFileManager.d.ts.map +1 -0
- package/dist/src/helpers/PathResolver/BasePathResolver.d.ts +16 -12
- package/dist/src/helpers/PathResolver/BasePathResolver.d.ts.map +1 -1
- package/dist/src/helpers/PathResolver/IPathResolver.d.ts +16 -0
- package/dist/src/helpers/PathResolver/IPathResolver.d.ts.map +1 -0
- package/dist/src/helpers/QueryClient/BaseQueryClient.d.ts +1 -6
- package/dist/src/helpers/QueryClient/BaseQueryClient.d.ts.map +1 -1
- package/dist/src/helpers/constants.d.ts +4 -22
- package/dist/src/helpers/constants.d.ts.map +1 -1
- package/dist/src/helpers/db.d.ts.map +1 -1
- package/dist/src/helpers/easPropertyCanonical.d.ts +1 -14
- package/dist/src/helpers/easPropertyCanonical.d.ts.map +1 -1
- package/dist/src/helpers/easRevokedFilter.d.ts +1 -9
- package/dist/src/helpers/easRevokedFilter.d.ts.map +1 -1
- package/dist/src/helpers/easUid.d.ts +1 -12
- package/dist/src/helpers/easUid.d.ts.map +1 -1
- package/dist/src/helpers/ethereumUtils.d.ts +5 -0
- package/dist/src/helpers/ethereumUtils.d.ts.map +1 -0
- package/dist/src/helpers/file/fetchAll/index.d.ts +1 -1
- package/dist/src/helpers/file/fetchAll/index.d.ts.map +1 -1
- package/dist/src/helpers/gateway/gatewayState.d.ts +2 -0
- package/dist/src/helpers/gateway/gatewayState.d.ts.map +1 -0
- package/dist/src/helpers/gateway/resolveSeedGatewayEndpoints.d.ts +2 -0
- package/dist/src/helpers/gateway/resolveSeedGatewayEndpoints.d.ts.map +1 -0
- package/dist/src/helpers/gateway/seedGatewayConfig.d.ts +2 -0
- package/dist/src/helpers/gateway/seedGatewayConfig.d.ts.map +1 -0
- package/dist/src/helpers/generateId.d.ts +2 -0
- package/dist/src/helpers/generateId.d.ts.map +1 -0
- package/dist/src/helpers/getSchemaForItemProperty.d.ts +1 -1
- package/dist/src/helpers/getSchemaForItemProperty.d.ts.map +1 -1
- package/dist/src/helpers/getSegmentedItemProperties.d.ts.map +1 -1
- package/dist/src/helpers/index.d.ts +15 -10
- package/dist/src/helpers/index.d.ts.map +1 -1
- package/dist/src/helpers/isSqliteUniqueConstraintError.d.ts +7 -0
- package/dist/src/helpers/isSqliteUniqueConstraintError.d.ts.map +1 -0
- package/dist/src/helpers/mediaRef.d.ts.map +1 -1
- package/dist/src/helpers/property/index.d.ts.map +1 -1
- package/dist/src/index.d.ts +9 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/interfaces/IDb.d.ts +11 -0
- package/dist/src/interfaces/IDb.d.ts.map +1 -1
- package/dist/src/node/db/Db.d.ts +22 -37
- package/dist/src/node/db/Db.d.ts.map +1 -1
- package/dist/src/node/helpers/ArweaveClient.d.ts +4 -30
- package/dist/src/node/helpers/ArweaveClient.d.ts.map +1 -1
- package/dist/src/node/helpers/EasClient.d.ts +4 -6
- package/dist/src/node/helpers/EasClient.d.ts.map +1 -1
- package/dist/src/node/helpers/FileManager.d.ts +26 -25
- package/dist/src/node/helpers/FileManager.d.ts.map +1 -1
- package/dist/src/node/helpers/PathResolver.d.ts +6 -5
- package/dist/src/node/helpers/PathResolver.d.ts.map +1 -1
- package/dist/src/node/helpers/QueryClient.d.ts +4 -6
- package/dist/src/node/helpers/QueryClient.d.ts.map +1 -1
- package/dist/src/node/helpers/scripts.d.ts +5 -7
- package/dist/src/node/helpers/scripts.d.ts.map +1 -1
- package/dist/src/node/index.d.ts +2 -9
- package/dist/src/node/index.d.ts.map +1 -1
- package/dist/src/node/webpack/index.d.ts +1 -1
- package/dist/src/node/webpack/index.d.ts.map +1 -1
- package/dist/src/platform/configurePlatform.d.ts +6 -0
- package/dist/src/platform/configurePlatform.d.ts.map +1 -0
- package/dist/src/platform/index.browser.d.ts +3 -0
- package/dist/src/platform/index.browser.d.ts.map +1 -0
- package/dist/src/platform/index.d.ts +9 -0
- package/dist/src/platform/index.d.ts.map +1 -0
- package/dist/src/platform/index.node.d.ts +3 -0
- package/dist/src/platform/index.node.d.ts.map +1 -0
- package/dist/src/platform/types.d.ts +14 -0
- package/dist/src/platform/types.d.ts.map +1 -0
- package/dist/src/stores/eas.d.ts +1 -18
- package/dist/src/stores/eas.d.ts.map +1 -1
- package/dist/src/testing/platformTestHandle.d.ts +8 -0
- package/dist/src/testing/platformTestHandle.d.ts.map +1 -0
- package/dist/src/types/gateway.d.ts +2 -0
- package/dist/src/types/gateway.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +5 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/item.d.ts +1 -1
- package/dist/src/types/item.d.ts.map +1 -1
- package/dist/src/types/machines.d.ts +10 -0
- package/dist/src/types/machines.d.ts.map +1 -1
- package/dist/src/types/seedProtocol.d.ts +1 -1
- package/dist/src/types/seedProtocol.d.ts.map +1 -1
- package/dist/src/vite/index.d.ts +2 -47
- package/dist/src/vite/index.d.ts.map +1 -1
- package/dist/{syncDbWithEas-CB5aXPtf.js → syncDbWithEas-DMOJHtkP.js} +20 -15
- package/dist/syncDbWithEas-DMOJHtkP.js.map +1 -0
- package/dist/{updateSeedRevokedAt-BIu5Q4Po.js → updateSeedRevokedAt-Ba0OhDQj.js} +2 -2
- package/dist/{updateSeedRevokedAt-BIu5Q4Po.js.map → updateSeedRevokedAt-Ba0OhDQj.js.map} +1 -1
- package/dist/vite.cjs +5 -481
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.js +1 -483
- package/dist/vite.js.map +1 -1
- package/package.json +42 -37
- package/dist/ArweaveClient-CZpZgmR2.js +0 -204
- package/dist/ArweaveClient-CZpZgmR2.js.map +0 -1
- package/dist/ArweaveClient-Cs7AL9dp.js +0 -204
- package/dist/ArweaveClient-Cs7AL9dp.js.map +0 -1
- package/dist/Db-CF6R7HIy.js +0 -2098
- package/dist/Db-CF6R7HIy.js.map +0 -1
- package/dist/Db-CZkONsdT.js +0 -248
- package/dist/Db-CZkONsdT.js.map +0 -1
- package/dist/EasClient-PXOzSzPG.js +0 -29
- package/dist/EasClient-PXOzSzPG.js.map +0 -1
- package/dist/EasClient-qfTiM2we.js +0 -29
- package/dist/EasClient-qfTiM2we.js.map +0 -1
- package/dist/FileManager-5GkHtxF7.js +0 -1256
- package/dist/FileManager-5GkHtxF7.js.map +0 -1
- package/dist/FileManager-jORQ8Bb6.js +0 -177
- package/dist/FileManager-jORQ8Bb6.js.map +0 -1
- package/dist/ModelProperty-e_hXSFvf.js.map +0 -1
- package/dist/PathResolver-B4_X69Rd.js +0 -204
- package/dist/PathResolver-B4_X69Rd.js.map +0 -1
- package/dist/PathResolver-Dz0RVGIz.js +0 -78
- package/dist/PathResolver-Dz0RVGIz.js.map +0 -1
- package/dist/QueryClient-CXHZ239d.js +0 -51
- package/dist/QueryClient-CXHZ239d.js.map +0 -1
- package/dist/QueryClient-D_UK4YLm.js +0 -35
- package/dist/QueryClient-D_UK4YLm.js.map +0 -1
- package/dist/arweave-default-shim.js +0 -6
- package/dist/cjs/getPublishPayload-CGKMEL3H.js.map +0 -1
- package/dist/cjs/getSegmentedItemProperties-BgS_FhAx.js.map +0 -1
- package/dist/cjs/index-BnAqr2le.js.map +0 -1
- package/dist/cjs/index-ByozbTn_.js.map +0 -1
- package/dist/cjs/syncDbWithEas-CA2Lum5V.js.map +0 -1
- package/dist/debug-default-shim.js +0 -27
- package/dist/getPublishPayload-D16enahU.js.map +0 -1
- package/dist/getSegmentedItemProperties-Be3kYWY3.js.map +0 -1
- package/dist/index-BFyMxc7e.js.map +0 -1
- package/dist/index-BcXKZQpX.js.map +0 -1
- package/dist/index-C9p-skT3.js +0 -18
- package/dist/index-C9p-skT3.js.map +0 -1
- package/dist/property-CRAau-Q-.js.map +0 -1
- package/dist/queries-Ct7CcQ3g.js +0 -16
- package/dist/queries-Ct7CcQ3g.js.map +0 -1
- package/dist/src/Item/queries.d.ts +0 -27
- package/dist/src/Item/queries.d.ts.map +0 -1
- package/dist/src/graphql/fragments/Attestation.d.ts +0 -2
- package/dist/src/graphql/fragments/Attestation.d.ts.map +0 -1
- package/dist/src/graphql/fragments/Schema.d.ts +0 -2
- package/dist/src/graphql/fragments/Schema.d.ts.map +0 -1
- package/dist/src/graphql/fragments/index.d.ts +0 -3
- package/dist/src/graphql/fragments/index.d.ts.map +0 -1
- package/dist/src/graphql/gql/fragment-masking.d.ts +0 -20
- package/dist/src/graphql/gql/fragment-masking.d.ts.map +0 -1
- package/dist/src/graphql/gql/gql.d.ts +0 -107
- package/dist/src/graphql/gql/gql.d.ts.map +0 -1
- package/dist/src/graphql/gql/graphql.d.ts +0 -2969
- package/dist/src/graphql/gql/graphql.d.ts.map +0 -1
- package/dist/src/graphql/gql/index.d.ts +0 -3
- package/dist/src/graphql/gql/index.d.ts.map +0 -1
- package/dist/src/helpers/ArweaveClient/queries.d.ts +0 -13
- package/dist/src/helpers/ArweaveClient/queries.d.ts.map +0 -1
- package/dist/src/helpers/file/queries.d.ts +0 -9
- package/dist/src/helpers/file/queries.d.ts.map +0 -1
- package/dist/src/helpers/image/queries.d.ts +0 -7
- package/dist/src/helpers/image/queries.d.ts.map +0 -1
- package/dist/src/node/codegen/drizzle.d.ts +0 -5
- package/dist/src/node/codegen/drizzle.d.ts.map +0 -1
- package/dist/src/node/codegen/index.d.ts +0 -2
- package/dist/src/node/codegen/index.d.ts.map +0 -1
- package/dist/src/node/codegen/ts-to-proto.d.ts +0 -9
- package/dist/src/node/codegen/ts-to-proto.d.ts.map +0 -1
- package/dist/src/node/db/node.app.db.config.d.ts +0 -4
- package/dist/src/node/db/node.app.db.config.d.ts.map +0 -1
- package/dist/src/node/helpers/index.d.ts +0 -3
- package/dist/src/node/helpers/index.d.ts.map +0 -1
- package/dist/src/vite/arweave-default-shim.d.ts +0 -4
- package/dist/src/vite/arweave-default-shim.d.ts.map +0 -1
- package/dist/src/vite/debug-default-shim.d.ts +0 -3
- package/dist/src/vite/debug-default-shim.d.ts.map +0 -1
- package/dist/src/vite/easFix.d.ts +0 -2
- package/dist/src/vite/easFix.d.ts.map +0 -1
- package/dist/syncDbWithEas-CB5aXPtf.js.map +0 -1
- /package/dist/db/drizzle/{drizzle/0000_married_malice.sql → 0000_married_malice.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0001_sweet_bruce_banner.sql → 0001_sweet_bruce_banner.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0002_bitter_proudstar.sql → 0002_bitter_proudstar.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0003_cultured_senator_kelly.sql → 0003_cultured_senator_kelly.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0004_add_publisher_to_seeds.sql → 0004_add_publisher_to_seeds.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0005_bright_lily_hollister.sql → 0005_bright_lily_hollister.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0006_add_publisher_to_versions_and_metadata.sql → 0006_add_publisher_to_versions_and_metadata.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0007_add_required_to_properties.sql → 0007_add_required_to_properties.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0008_add_revoked_at_to_seeds.sql → 0008_add_revoked_at_to_seeds.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0009_happy_namor.sql → 0009_happy_namor.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0010_add_arweave_l1_finalize_jobs.sql → 0010_add_arweave_l1_finalize_jobs.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0011_normalize_placeholder_uids.sql → 0011_normalize_placeholder_uids.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0012_html_embedded_image_co_publish.sql → 0012_html_embedded_image_co_publish.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/0013_eas_sync_processes.sql → 0013_eas_sync_processes.sql} +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0000_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0001_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0002_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0003_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0004_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0005_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0006_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0007_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0008_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0009_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0010_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/0013_snapshot.json +0 -0
- /package/dist/db/drizzle/{drizzle/meta → meta}/_journal.json +0 -0
|
@@ -0,0 +1,1972 @@
|
|
|
1
|
+
import { B as BaseDb, m as models, i as modelSchemas, r as schemas, p as properties, b as appState, f as metadata } from './BasePathResolver-BDgc7c4T.js';
|
|
2
|
+
import { toSnakeCase } from 'drizzle-orm/casing';
|
|
3
|
+
import { eq, and, isNull } from 'drizzle-orm';
|
|
4
|
+
import { upperFirst, camelCase } from 'lodash-es';
|
|
5
|
+
import debug from 'debug';
|
|
6
|
+
import 'drizzle-orm/sqlite-core';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect SQLite UNIQUE constraint failures across better-sqlite3 and drizzle/wasm wrappers.
|
|
10
|
+
* Drizzle often surfaces `Failed query: insert into...` without "UNIQUE constraint" on message,
|
|
11
|
+
* with the real SQLITE_CONSTRAINT_UNIQUE on cause / nested error.
|
|
12
|
+
*/
|
|
13
|
+
function isSqliteUniqueConstraintError(error) {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const queue = [error];
|
|
16
|
+
while (queue.length > 0) {
|
|
17
|
+
const current = queue.shift();
|
|
18
|
+
if (current == null || seen.has(current))
|
|
19
|
+
continue;
|
|
20
|
+
seen.add(current);
|
|
21
|
+
if (typeof current === 'string') {
|
|
22
|
+
if (matchesUniqueConstraintText(current))
|
|
23
|
+
return true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (typeof current !== 'object')
|
|
27
|
+
continue;
|
|
28
|
+
const err = current;
|
|
29
|
+
const code = err.code;
|
|
30
|
+
if (code === 'SQLITE_CONSTRAINT_UNIQUE' ||
|
|
31
|
+
code === 2067 ||
|
|
32
|
+
code === '2067') {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
for (const key of ['message', 'msg', 'stack']) {
|
|
36
|
+
const value = err[key];
|
|
37
|
+
if (typeof value === 'string' && matchesUniqueConstraintText(value)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Drizzle / wasm often nest the real SQLite error
|
|
42
|
+
for (const key of ['cause', 'reason', 'error', 'originalError', 'err']) {
|
|
43
|
+
if (err[key] != null)
|
|
44
|
+
queue.push(err[key]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
function matchesUniqueConstraintText(text) {
|
|
50
|
+
const lower = text.toLowerCase();
|
|
51
|
+
return (lower.includes('unique constraint') ||
|
|
52
|
+
lower.includes('sqlite_constraint_unique') ||
|
|
53
|
+
(lower.includes('constraint failed') && lower.includes('unique')) ||
|
|
54
|
+
/\brc\s*=\s*2067\b/i.test(text) ||
|
|
55
|
+
(text.includes('2067') && lower.includes('constraint')));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const logger = debug('seedSdk:helpers:db');
|
|
59
|
+
/**
|
|
60
|
+
* Resolve property_id from model name/type and property name.
|
|
61
|
+
* Handles property name variants (e.g. htmlId -> html, avatarImageIds -> avatarImages).
|
|
62
|
+
* @param modelNameOrType - Model name (PascalCase) or model type (snake_case)
|
|
63
|
+
* @param propertyName - Property name as stored in metadata (may have Id/Ids suffix)
|
|
64
|
+
* @returns properties.id or null if not found
|
|
65
|
+
*/
|
|
66
|
+
async function getPropertyIdForModelAndName(modelNameOrType, propertyName) {
|
|
67
|
+
const db = BaseDb.getAppDb();
|
|
68
|
+
if (!db)
|
|
69
|
+
return null;
|
|
70
|
+
if (!modelNameOrType || !propertyName)
|
|
71
|
+
return null;
|
|
72
|
+
const normalizedModelName = upperFirst(camelCase(modelNameOrType));
|
|
73
|
+
const propertyNamesToTry = [propertyName];
|
|
74
|
+
if (propertyName.endsWith('Ids')) {
|
|
75
|
+
propertyNamesToTry.push(propertyName.slice(0, -3));
|
|
76
|
+
}
|
|
77
|
+
if (propertyName.endsWith('Id')) {
|
|
78
|
+
propertyNamesToTry.push(propertyName.slice(0, -2));
|
|
79
|
+
}
|
|
80
|
+
for (const pName of propertyNamesToTry) {
|
|
81
|
+
const rows = await db
|
|
82
|
+
.select({ id: properties.id })
|
|
83
|
+
.from(properties)
|
|
84
|
+
.innerJoin(models, eq(properties.modelId, models.id))
|
|
85
|
+
.where(and(eq(models.name, normalizedModelName), eq(properties.name, pName)))
|
|
86
|
+
.limit(1);
|
|
87
|
+
if (rows.length > 0 && rows[0].id != null) {
|
|
88
|
+
return rows[0].id;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Migrate metadata rows when a property is renamed.
|
|
95
|
+
* Updates metadata.propertyName from old to new for all rows that reference the renamed property.
|
|
96
|
+
* Called from renameModelProperty to prevent duplicate ItemProperties.
|
|
97
|
+
* @param modelName - Model name (PascalCase)
|
|
98
|
+
* @param oldPropertyName - Current property name before rename
|
|
99
|
+
* @param newPropertyName - New property name after rename
|
|
100
|
+
* @param propertySchemaFileId - Optional schema file ID for the property (most reliable lookup)
|
|
101
|
+
* @returns Count of metadata rows updated
|
|
102
|
+
*/
|
|
103
|
+
async function migrateMetadataForPropertyRename(modelName, oldPropertyName, newPropertyName, propertySchemaFileId) {
|
|
104
|
+
const db = BaseDb.getAppDb();
|
|
105
|
+
if (!db) {
|
|
106
|
+
logger('[migrateMetadataForPropertyRename] Database not available, skipping');
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
const normalizedModelName = upperFirst(camelCase(modelName));
|
|
110
|
+
// Find the property row by schemaFileId (preferred) or by name
|
|
111
|
+
let propertyId = null;
|
|
112
|
+
if (propertySchemaFileId) {
|
|
113
|
+
const rows = await db
|
|
114
|
+
.select({ id: properties.id })
|
|
115
|
+
.from(properties)
|
|
116
|
+
.innerJoin(models, eq(properties.modelId, models.id))
|
|
117
|
+
.where(and(eq(models.name, normalizedModelName), eq(properties.schemaFileId, propertySchemaFileId)))
|
|
118
|
+
.limit(1);
|
|
119
|
+
if (rows.length > 0 && rows[0].id != null) {
|
|
120
|
+
propertyId = rows[0].id;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (propertyId == null) {
|
|
124
|
+
propertyId = await getPropertyIdForModelAndName(modelName, oldPropertyName);
|
|
125
|
+
}
|
|
126
|
+
if (propertyId == null) {
|
|
127
|
+
logger(`[migrateMetadataForPropertyRename] Property not found: ${modelName}.${oldPropertyName}, skipping`);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
// Primary path: update metadata by propertyId
|
|
131
|
+
const toUpdateByPropertyId = await db
|
|
132
|
+
.select({ localId: metadata.localId })
|
|
133
|
+
.from(metadata)
|
|
134
|
+
.where(eq(metadata.propertyId, propertyId));
|
|
135
|
+
const updatedByPropertyId = toUpdateByPropertyId.length;
|
|
136
|
+
if (updatedByPropertyId > 0) {
|
|
137
|
+
await db
|
|
138
|
+
.update(metadata)
|
|
139
|
+
.set({ propertyName: newPropertyName })
|
|
140
|
+
.where(eq(metadata.propertyId, propertyId));
|
|
141
|
+
}
|
|
142
|
+
// Fallback: update metadata with null propertyId (legacy rows) by propertyName + modelType
|
|
143
|
+
const modelType = toSnakeCase(modelName);
|
|
144
|
+
const toUpdateByFallback = await db
|
|
145
|
+
.select({ localId: metadata.localId })
|
|
146
|
+
.from(metadata)
|
|
147
|
+
.where(and(eq(metadata.propertyName, oldPropertyName), eq(metadata.modelType, modelType), isNull(metadata.propertyId)));
|
|
148
|
+
const updatedByFallback = toUpdateByFallback.length;
|
|
149
|
+
if (updatedByFallback > 0) {
|
|
150
|
+
await db
|
|
151
|
+
.update(metadata)
|
|
152
|
+
.set({ propertyName: newPropertyName })
|
|
153
|
+
.where(and(eq(metadata.propertyName, oldPropertyName), eq(metadata.modelType, modelType), isNull(metadata.propertyId)));
|
|
154
|
+
}
|
|
155
|
+
if (updatedByFallback > 0) {
|
|
156
|
+
logger(`[migrateMetadataForPropertyRename] Fallback updated ${updatedByFallback} rows (propertyId was null)`);
|
|
157
|
+
}
|
|
158
|
+
const total = updatedByPropertyId + updatedByFallback;
|
|
159
|
+
if (total > 0) {
|
|
160
|
+
logger(`[migrateMetadataForPropertyRename] Migrated ${total} metadata rows for ${modelName}.${oldPropertyName} -> ${newPropertyName}`);
|
|
161
|
+
}
|
|
162
|
+
return total;
|
|
163
|
+
}
|
|
164
|
+
const escapeSqliteString = (value) => {
|
|
165
|
+
if (typeof value !== 'string') {
|
|
166
|
+
throw new Error(`Value must be a string, instead got: ${JSON.stringify(value)}`);
|
|
167
|
+
}
|
|
168
|
+
return value.replace(/'/g, "''");
|
|
169
|
+
};
|
|
170
|
+
const getObjectForRow = (row) => {
|
|
171
|
+
const obj = {};
|
|
172
|
+
row.columnNames.forEach((colName, index) => {
|
|
173
|
+
const value = row.row[index];
|
|
174
|
+
if (typeof value !== 'string') {
|
|
175
|
+
obj[colName] = row.row[index];
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Try to parse the value as JSON
|
|
179
|
+
try {
|
|
180
|
+
obj[colName] = JSON.parse(value);
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
// If it fails, just set the value as a string
|
|
184
|
+
obj[colName] = value;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
return obj;
|
|
188
|
+
};
|
|
189
|
+
const getSqlResultObject = (queryResult) => {
|
|
190
|
+
if (!queryResult || !queryResult.rows || queryResult.rows.length === 0) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
let obj;
|
|
194
|
+
if (queryResult.rows.length === 1) {
|
|
195
|
+
obj = getObjectForRow(queryResult.rows[0]);
|
|
196
|
+
}
|
|
197
|
+
if (queryResult.rows.length > 1) {
|
|
198
|
+
obj = queryResult.rows.reduce((acc, row) => {
|
|
199
|
+
const rowObj = getObjectForRow(row);
|
|
200
|
+
acc.push(rowObj);
|
|
201
|
+
return acc;
|
|
202
|
+
}, []);
|
|
203
|
+
}
|
|
204
|
+
return obj;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Find or create a models row by schemaFileId (preferred) or name.
|
|
208
|
+
* Reuses an existing same-name row (especially schemaFileId=null stubs created via
|
|
209
|
+
* ref resolution) instead of inserting a second row — models.name is not unique,
|
|
210
|
+
* and duplicate names break createOrUpdate lookups (e.g. Resource.tags → Tag).
|
|
211
|
+
*/
|
|
212
|
+
const findOrCreateModelRecord = async (db, modelName, modelFileId) => {
|
|
213
|
+
if (modelFileId) {
|
|
214
|
+
const byFileId = await db
|
|
215
|
+
.select()
|
|
216
|
+
.from(models)
|
|
217
|
+
.where(eq(models.schemaFileId, modelFileId))
|
|
218
|
+
.limit(1);
|
|
219
|
+
if (byFileId.length > 0) {
|
|
220
|
+
const record = byFileId[0];
|
|
221
|
+
if (record.name !== modelName) {
|
|
222
|
+
await db
|
|
223
|
+
.update(models)
|
|
224
|
+
.set({ name: modelName })
|
|
225
|
+
.where(eq(models.id, record.id));
|
|
226
|
+
return { ...record, name: modelName };
|
|
227
|
+
}
|
|
228
|
+
return record;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const byName = (await db
|
|
232
|
+
.select()
|
|
233
|
+
.from(models)
|
|
234
|
+
.where(eq(models.name, modelName)));
|
|
235
|
+
if (byName.length > 0) {
|
|
236
|
+
// Prefer exact file-id match, then adoptable null schemaFileId stub, then any single row.
|
|
237
|
+
let chosen = (modelFileId
|
|
238
|
+
? byName.find((r) => r.schemaFileId === modelFileId)
|
|
239
|
+
: undefined) ||
|
|
240
|
+
byName.find((r) => !r.schemaFileId) ||
|
|
241
|
+
(byName.length === 1 ? byName[0] : undefined) ||
|
|
242
|
+
byName.find((r) => !!r.schemaFileId) ||
|
|
243
|
+
byName[0];
|
|
244
|
+
if (modelFileId && chosen.schemaFileId !== modelFileId) {
|
|
245
|
+
const conflict = await db
|
|
246
|
+
.select()
|
|
247
|
+
.from(models)
|
|
248
|
+
.where(eq(models.schemaFileId, modelFileId))
|
|
249
|
+
.limit(1);
|
|
250
|
+
if (conflict.length > 0) {
|
|
251
|
+
return conflict[0];
|
|
252
|
+
}
|
|
253
|
+
// Adopt stub (null schemaFileId) or sole same-name row by attaching this file id.
|
|
254
|
+
if (!chosen.schemaFileId || byName.length === 1) {
|
|
255
|
+
await db
|
|
256
|
+
.update(models)
|
|
257
|
+
.set({ schemaFileId: modelFileId, isEdited: false })
|
|
258
|
+
.where(eq(models.id, chosen.id));
|
|
259
|
+
chosen = { ...chosen, schemaFileId: modelFileId, isEdited: false };
|
|
260
|
+
logger(`Adopted existing model "${modelName}" (id: ${chosen.id}) with schemaFileId "${modelFileId}"`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return chosen;
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const inserted = await db
|
|
267
|
+
.insert(models)
|
|
268
|
+
.values({
|
|
269
|
+
name: modelName,
|
|
270
|
+
schemaFileId: modelFileId || null,
|
|
271
|
+
isEdited: false,
|
|
272
|
+
})
|
|
273
|
+
.returning();
|
|
274
|
+
return inserted[0];
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (isSqliteUniqueConstraintError(error) && modelFileId) {
|
|
278
|
+
const existing = await db
|
|
279
|
+
.select()
|
|
280
|
+
.from(models)
|
|
281
|
+
.where(eq(models.schemaFileId, modelFileId))
|
|
282
|
+
.limit(1);
|
|
283
|
+
if (existing.length > 0) {
|
|
284
|
+
return existing[0];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Race: another writer inserted by name — reuse rather than throw on duplicates.
|
|
288
|
+
const raced = (await db
|
|
289
|
+
.select()
|
|
290
|
+
.from(models)
|
|
291
|
+
.where(eq(models.name, modelName)));
|
|
292
|
+
if (raced.length > 0) {
|
|
293
|
+
return ((modelFileId
|
|
294
|
+
? raced.find((r) => r.schemaFileId === modelFileId)
|
|
295
|
+
: undefined) ||
|
|
296
|
+
raced.find((r) => !r.schemaFileId) ||
|
|
297
|
+
raced[0]);
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
const createOrUpdate = async (db, table, values) => {
|
|
303
|
+
const valueFilters = [];
|
|
304
|
+
const propertiesToExcludeFromDb = ['ref'];
|
|
305
|
+
const safeValues = Object.keys(values).reduce((acc, key) => {
|
|
306
|
+
if (!propertiesToExcludeFromDb.includes(key)) {
|
|
307
|
+
acc[key] = values[key];
|
|
308
|
+
}
|
|
309
|
+
return acc;
|
|
310
|
+
}, {});
|
|
311
|
+
for (const [key, value] of Object.entries(safeValues)) {
|
|
312
|
+
let finalValue = value;
|
|
313
|
+
if (key === 'TObject') {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (typeof value === 'object') {
|
|
317
|
+
finalValue = JSON.stringify(value);
|
|
318
|
+
}
|
|
319
|
+
const column = table[key];
|
|
320
|
+
if (!column) {
|
|
321
|
+
throw new Error(`Column not found for ${key}`);
|
|
322
|
+
}
|
|
323
|
+
if (typeof finalValue === 'undefined') {
|
|
324
|
+
finalValue = null;
|
|
325
|
+
}
|
|
326
|
+
if (finalValue === null) {
|
|
327
|
+
valueFilters.push(isNull(table[key]));
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
valueFilters.push(eq(table[key], finalValue));
|
|
331
|
+
}
|
|
332
|
+
// console.log('valueFilters:', valueFilters)
|
|
333
|
+
// for ( const filter of valueFilters ) {
|
|
334
|
+
// console.log('filter:', Object.keys(filter))
|
|
335
|
+
// }
|
|
336
|
+
// Build a query to find the record based on properties
|
|
337
|
+
const existingRecords = await db
|
|
338
|
+
.select()
|
|
339
|
+
.from(table)
|
|
340
|
+
.where(and(...valueFilters));
|
|
341
|
+
if (existingRecords.length > 1) {
|
|
342
|
+
throw new Error('Multiple records found');
|
|
343
|
+
}
|
|
344
|
+
if (existingRecords.length > 0) {
|
|
345
|
+
// If record exists, update it
|
|
346
|
+
await db
|
|
347
|
+
.update(table)
|
|
348
|
+
.set(safeValues)
|
|
349
|
+
.where(and(...valueFilters));
|
|
350
|
+
return existingRecords[0];
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
// If no record exists, create a new one
|
|
354
|
+
const newRecord = await db.insert(table).values(safeValues).returning();
|
|
355
|
+
return newRecord[0];
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
/**
|
|
359
|
+
* Searches the database for an existing schema by schemaFileId (preferred) or name and creates it if it doesn't exist
|
|
360
|
+
* @param schema - The schema to add to the database
|
|
361
|
+
* @param schemaFileId - Optional schemaFileId from JSON file for change tracking
|
|
362
|
+
* @param schemaData - Optional full schema content as JSON string (SchemaFileFormat)
|
|
363
|
+
* @param isDraft - Optional flag indicating if schema is in draft state (default: false)
|
|
364
|
+
* @param isEdited - Optional flag indicating if schema has been edited locally (default: false)
|
|
365
|
+
* @returns The schema record (either existing or newly created)
|
|
366
|
+
*/
|
|
367
|
+
const addSchemaToDb = async (schema, schemaFileId, schemaData, isDraft, isEdited) => {
|
|
368
|
+
const db = BaseDb.getAppDb();
|
|
369
|
+
if (!db) {
|
|
370
|
+
throw new Error('Database not found');
|
|
371
|
+
}
|
|
372
|
+
// First, try to find existing schema by schemaFileId (preferred for change tracking)
|
|
373
|
+
if (schemaFileId) {
|
|
374
|
+
const existingByFileId = await db
|
|
375
|
+
.select()
|
|
376
|
+
.from(schemas)
|
|
377
|
+
.where(eq(schemas.schemaFileId, schemaFileId))
|
|
378
|
+
.limit(1);
|
|
379
|
+
if (existingByFileId.length > 0) {
|
|
380
|
+
// Update fields if provided
|
|
381
|
+
const updates = {};
|
|
382
|
+
if (schemaFileId && !existingByFileId[0].schemaFileId) {
|
|
383
|
+
updates.schemaFileId = schemaFileId;
|
|
384
|
+
}
|
|
385
|
+
// Always update name if it's different (handles name changes)
|
|
386
|
+
if (schema.name && existingByFileId[0].name !== schema.name) {
|
|
387
|
+
updates.name = schema.name;
|
|
388
|
+
}
|
|
389
|
+
if (schemaData !== undefined) {
|
|
390
|
+
updates.schemaData = schemaData;
|
|
391
|
+
}
|
|
392
|
+
if (schema.version !== undefined && existingByFileId[0].version !== schema.version) {
|
|
393
|
+
updates.version = schema.version;
|
|
394
|
+
}
|
|
395
|
+
if (isDraft !== undefined && existingByFileId[0].isDraft !== isDraft) {
|
|
396
|
+
updates.isDraft = isDraft;
|
|
397
|
+
}
|
|
398
|
+
if (isEdited !== undefined && existingByFileId[0].isEdited !== isEdited) {
|
|
399
|
+
updates.isEdited = isEdited;
|
|
400
|
+
}
|
|
401
|
+
if (schema.updatedAt && existingByFileId[0].updatedAt !== schema.updatedAt) {
|
|
402
|
+
updates.updatedAt = schema.updatedAt;
|
|
403
|
+
}
|
|
404
|
+
if (Object.keys(updates).length > 0) {
|
|
405
|
+
await db
|
|
406
|
+
.update(schemas)
|
|
407
|
+
.set(updates)
|
|
408
|
+
.where(eq(schemas.id, existingByFileId[0].id));
|
|
409
|
+
return { ...existingByFileId[0], ...updates };
|
|
410
|
+
}
|
|
411
|
+
return existingByFileId[0];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// For drafts, check if there's an existing draft by name (not by schemaFileId)
|
|
415
|
+
if (isDraft === true) {
|
|
416
|
+
const existingDrafts = await db
|
|
417
|
+
.select()
|
|
418
|
+
.from(schemas)
|
|
419
|
+
.where(eq(schemas.name, schema.name))
|
|
420
|
+
.limit(1);
|
|
421
|
+
if (existingDrafts.length > 0 && existingDrafts[0].isDraft === true) {
|
|
422
|
+
// Update existing draft
|
|
423
|
+
const updates = {};
|
|
424
|
+
// Update schemaFileId if provided and different from existing
|
|
425
|
+
if (schemaFileId && existingDrafts[0].schemaFileId !== schemaFileId) {
|
|
426
|
+
updates.schemaFileId = schemaFileId;
|
|
427
|
+
}
|
|
428
|
+
if (schemaData !== undefined) {
|
|
429
|
+
updates.schemaData = schemaData;
|
|
430
|
+
}
|
|
431
|
+
if (schema.version !== undefined) {
|
|
432
|
+
updates.version = schema.version;
|
|
433
|
+
}
|
|
434
|
+
updates.updatedAt = schema.updatedAt || Date.now();
|
|
435
|
+
if (Object.keys(updates).length > 0) {
|
|
436
|
+
await db
|
|
437
|
+
.update(schemas)
|
|
438
|
+
.set(updates)
|
|
439
|
+
.where(eq(schemas.id, existingDrafts[0].id));
|
|
440
|
+
return { ...existingDrafts[0], ...updates };
|
|
441
|
+
}
|
|
442
|
+
return existingDrafts[0];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// Fallback: Search for existing schema by name
|
|
446
|
+
// BUT: If we have a schemaFileId, also check if any record with that schemaFileId exists
|
|
447
|
+
// (in case the name changed but we're looking up by the new name)
|
|
448
|
+
if (schemaFileId) {
|
|
449
|
+
// Double-check by schemaFileId - maybe the name changed and we're looking up by new name
|
|
450
|
+
const doubleCheckByFileId = await db
|
|
451
|
+
.select()
|
|
452
|
+
.from(schemas)
|
|
453
|
+
.where(eq(schemas.schemaFileId, schemaFileId))
|
|
454
|
+
.limit(1);
|
|
455
|
+
if (doubleCheckByFileId.length > 0) {
|
|
456
|
+
// Found by schemaFileId - update it (name might have changed)
|
|
457
|
+
const updates = {};
|
|
458
|
+
// Always update name if it's different (handles name changes)
|
|
459
|
+
if (schema.name && doubleCheckByFileId[0].name !== schema.name) {
|
|
460
|
+
updates.name = schema.name;
|
|
461
|
+
}
|
|
462
|
+
if (schemaData !== undefined) {
|
|
463
|
+
updates.schemaData = schemaData;
|
|
464
|
+
}
|
|
465
|
+
if (schema.version !== undefined && doubleCheckByFileId[0].version !== schema.version) {
|
|
466
|
+
updates.version = schema.version;
|
|
467
|
+
}
|
|
468
|
+
if (isDraft !== undefined && doubleCheckByFileId[0].isDraft !== isDraft) {
|
|
469
|
+
updates.isDraft = isDraft;
|
|
470
|
+
}
|
|
471
|
+
if (isEdited !== undefined && doubleCheckByFileId[0].isEdited !== isEdited) {
|
|
472
|
+
updates.isEdited = isEdited;
|
|
473
|
+
}
|
|
474
|
+
if (schema.updatedAt && doubleCheckByFileId[0].updatedAt !== schema.updatedAt) {
|
|
475
|
+
updates.updatedAt = schema.updatedAt;
|
|
476
|
+
}
|
|
477
|
+
if (Object.keys(updates).length > 0) {
|
|
478
|
+
await db
|
|
479
|
+
.update(schemas)
|
|
480
|
+
.set(updates)
|
|
481
|
+
.where(eq(schemas.id, doubleCheckByFileId[0].id));
|
|
482
|
+
return { ...doubleCheckByFileId[0], ...updates };
|
|
483
|
+
}
|
|
484
|
+
return doubleCheckByFileId[0];
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Final fallback: Search by name (only if no schemaFileId or schemaFileId lookup failed)
|
|
488
|
+
// If isDraft is explicitly false, first try to find non-draft, but also check for drafts
|
|
489
|
+
// to handle the case where a draft exists and we're loading a non-draft version
|
|
490
|
+
let existingSchemas = await db
|
|
491
|
+
.select()
|
|
492
|
+
.from(schemas)
|
|
493
|
+
.where(isDraft === false
|
|
494
|
+
? and(eq(schemas.name, schema.name), eq(schemas.isDraft, false))
|
|
495
|
+
: eq(schemas.name, schema.name))
|
|
496
|
+
.limit(1);
|
|
497
|
+
// If isDraft is false and we didn't find a non-draft, check for a draft with the same name
|
|
498
|
+
// This handles the case where a draft was created first and we're now loading the non-draft version
|
|
499
|
+
if (isDraft === false && existingSchemas.length === 0) {
|
|
500
|
+
const draftSchemas = await db
|
|
501
|
+
.select()
|
|
502
|
+
.from(schemas)
|
|
503
|
+
.where(and(eq(schemas.name, schema.name), eq(schemas.isDraft, true)))
|
|
504
|
+
.limit(1);
|
|
505
|
+
if (draftSchemas.length > 0) {
|
|
506
|
+
// Found a draft - we'll update it to non-draft below
|
|
507
|
+
logger(`Found draft schema "${schema.name}" with schemaFileId "${draftSchemas[0].schemaFileId}", will update to non-draft with schemaFileId "${schemaFileId}"`);
|
|
508
|
+
existingSchemas = draftSchemas;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (existingSchemas.length > 0) {
|
|
512
|
+
// Update fields if provided
|
|
513
|
+
const updates = {};
|
|
514
|
+
// Update schemaFileId if provided and different from existing (or if existing doesn't have one or is null)
|
|
515
|
+
// This is important for preserving IDs from schema files when drafts exist
|
|
516
|
+
// BUT: Don't overwrite a non-draft schema's schemaFileId with a new generated one
|
|
517
|
+
// This prevents loadOrCreateSchema from overwriting correct IDs from fixture files
|
|
518
|
+
if (schemaFileId && existingSchemas[0].schemaFileId !== schemaFileId) {
|
|
519
|
+
// Only update if:
|
|
520
|
+
// 1. Existing schema is a draft (can be updated)
|
|
521
|
+
// 2. Existing schema has no schemaFileId (should be set)
|
|
522
|
+
// 3. We're explicitly setting isDraft to false (updating from draft to non-draft)
|
|
523
|
+
const shouldUpdate = existingSchemas[0].isDraft === true ||
|
|
524
|
+
!existingSchemas[0].schemaFileId ||
|
|
525
|
+
(isDraft === false && existingSchemas[0].isDraft === true);
|
|
526
|
+
if (shouldUpdate) {
|
|
527
|
+
logger(`Updating schemaFileId from "${existingSchemas[0].schemaFileId}" to "${schemaFileId}" for schema "${schema.name}"`);
|
|
528
|
+
updates.schemaFileId = schemaFileId;
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
logger(`Preserving existing schemaFileId "${existingSchemas[0].schemaFileId}" for non-draft schema "${schema.name}" (new schemaFileId "${schemaFileId}" would overwrite it)`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Always update name if it's different (handles name changes)
|
|
535
|
+
if (schema.name && existingSchemas[0].name !== schema.name) {
|
|
536
|
+
updates.name = schema.name;
|
|
537
|
+
}
|
|
538
|
+
if (schemaData !== undefined) {
|
|
539
|
+
updates.schemaData = schemaData;
|
|
540
|
+
}
|
|
541
|
+
if (schema.version !== undefined && existingSchemas[0].version !== schema.version) {
|
|
542
|
+
updates.version = schema.version;
|
|
543
|
+
}
|
|
544
|
+
// If isDraft is explicitly false and we found a draft, update it to non-draft
|
|
545
|
+
if (isDraft === false && existingSchemas[0].isDraft === true) {
|
|
546
|
+
updates.isDraft = false;
|
|
547
|
+
}
|
|
548
|
+
else if (isDraft !== undefined && existingSchemas[0].isDraft !== isDraft) {
|
|
549
|
+
updates.isDraft = isDraft;
|
|
550
|
+
}
|
|
551
|
+
if (isEdited !== undefined && existingSchemas[0].isEdited !== isEdited) {
|
|
552
|
+
updates.isEdited = isEdited;
|
|
553
|
+
}
|
|
554
|
+
if (schema.updatedAt && existingSchemas[0].updatedAt !== schema.updatedAt) {
|
|
555
|
+
updates.updatedAt = schema.updatedAt;
|
|
556
|
+
}
|
|
557
|
+
if (Object.keys(updates).length > 0) {
|
|
558
|
+
logger(`Applying updates to schema "${schema.name}" (id: ${existingSchemas[0].id}):`, Object.keys(updates));
|
|
559
|
+
await db
|
|
560
|
+
.update(schemas)
|
|
561
|
+
.set(updates)
|
|
562
|
+
.where(eq(schemas.id, existingSchemas[0].id));
|
|
563
|
+
const updated = { ...existingSchemas[0], ...updates };
|
|
564
|
+
logger(`Schema "${schema.name}" updated. New schemaFileId: "${updated.schemaFileId}", isDraft: ${updated.isDraft}`);
|
|
565
|
+
return updated;
|
|
566
|
+
}
|
|
567
|
+
logger(`No updates needed for schema "${schema.name}" (id: ${existingSchemas[0].id})`);
|
|
568
|
+
return existingSchemas[0];
|
|
569
|
+
}
|
|
570
|
+
const newSchema = await db.insert(schemas).values({
|
|
571
|
+
name: schema.name,
|
|
572
|
+
version: schema.version,
|
|
573
|
+
schemaFileId: schemaFileId || null,
|
|
574
|
+
schemaData: schemaData || null,
|
|
575
|
+
isDraft: isDraft ?? false,
|
|
576
|
+
isEdited: isEdited ?? false,
|
|
577
|
+
createdAt: schema.createdAt,
|
|
578
|
+
updatedAt: schema.updatedAt,
|
|
579
|
+
}).returning();
|
|
580
|
+
// Notify React useSchemas so it can invalidate; live query often doesn't re-run when schemas table is inserted.
|
|
581
|
+
if (typeof BroadcastChannel !== 'undefined') {
|
|
582
|
+
try {
|
|
583
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
584
|
+
new BroadcastChannel('seed-schemas-invalidate').postMessage({});
|
|
585
|
+
}
|
|
586
|
+
catch (_) { }
|
|
587
|
+
}
|
|
588
|
+
return newSchema[0];
|
|
589
|
+
};
|
|
590
|
+
/**
|
|
591
|
+
* Rename a model in the database
|
|
592
|
+
* Updates the model name and all properties that reference it
|
|
593
|
+
* @param oldName - The current model name
|
|
594
|
+
* @param newName - The new model name
|
|
595
|
+
* @param schemaNameOrId - Schema name or schema ID to scope the rename (required when multiple schemas have models with the same name)
|
|
596
|
+
* @returns The updated model record
|
|
597
|
+
*/
|
|
598
|
+
const renameModelInDb = async (oldName, newName, schemaNameOrId) => {
|
|
599
|
+
const db = BaseDb.getAppDb();
|
|
600
|
+
if (!db) {
|
|
601
|
+
throw new Error('Database not found');
|
|
602
|
+
}
|
|
603
|
+
const _mod_0 = await import('./BasePathResolver-BDgc7c4T.js');
|
|
604
|
+
const _ns_0 = _mod_0.S;
|
|
605
|
+
const { schemas: schemasTable } = _ns_0;
|
|
606
|
+
// Find the model by old name, optionally scoped by schema
|
|
607
|
+
let existingModels;
|
|
608
|
+
if (schemaNameOrId !== undefined) {
|
|
609
|
+
if (typeof schemaNameOrId === 'number') {
|
|
610
|
+
const rows = await db
|
|
611
|
+
.select({
|
|
612
|
+
id: models.id,
|
|
613
|
+
name: models.name,
|
|
614
|
+
schemaFileId: models.schemaFileId,
|
|
615
|
+
isEdited: models.isEdited,
|
|
616
|
+
})
|
|
617
|
+
.from(models)
|
|
618
|
+
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
619
|
+
.where(and(eq(models.name, oldName), eq(modelSchemas.schemaId, schemaNameOrId)))
|
|
620
|
+
.limit(1);
|
|
621
|
+
existingModels = rows;
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
const rows = await db
|
|
625
|
+
.select({
|
|
626
|
+
id: models.id,
|
|
627
|
+
name: models.name,
|
|
628
|
+
schemaFileId: models.schemaFileId,
|
|
629
|
+
isEdited: models.isEdited,
|
|
630
|
+
})
|
|
631
|
+
.from(models)
|
|
632
|
+
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
633
|
+
.innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
|
|
634
|
+
.where(and(eq(models.name, oldName), eq(schemasTable.name, schemaNameOrId)))
|
|
635
|
+
.limit(1);
|
|
636
|
+
existingModels = rows;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
existingModels = await db
|
|
641
|
+
.select({
|
|
642
|
+
id: models.id,
|
|
643
|
+
name: models.name,
|
|
644
|
+
schemaFileId: models.schemaFileId,
|
|
645
|
+
isEdited: models.isEdited,
|
|
646
|
+
})
|
|
647
|
+
.from(models)
|
|
648
|
+
.where(eq(models.name, oldName))
|
|
649
|
+
.limit(1);
|
|
650
|
+
}
|
|
651
|
+
if (existingModels.length === 0) {
|
|
652
|
+
if (schemaNameOrId !== undefined) {
|
|
653
|
+
throw new Error(`Model "${oldName}" not found in schema "${schemaNameOrId}"`);
|
|
654
|
+
}
|
|
655
|
+
// Backward compat: model doesn't exist, create it with new name
|
|
656
|
+
const newModel = await db.insert(models).values({ name: newName }).returning();
|
|
657
|
+
return newModel[0];
|
|
658
|
+
}
|
|
659
|
+
const modelToRename = existingModels[0];
|
|
660
|
+
// Check if a model with the new name already exists in the same schema
|
|
661
|
+
let existingWithNewName;
|
|
662
|
+
if (schemaNameOrId !== undefined) {
|
|
663
|
+
if (typeof schemaNameOrId === 'number') {
|
|
664
|
+
existingWithNewName = await db
|
|
665
|
+
.select({
|
|
666
|
+
id: models.id,
|
|
667
|
+
name: models.name,
|
|
668
|
+
schemaFileId: models.schemaFileId,
|
|
669
|
+
isEdited: models.isEdited,
|
|
670
|
+
})
|
|
671
|
+
.from(models)
|
|
672
|
+
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
673
|
+
.where(and(eq(models.name, newName), eq(modelSchemas.schemaId, schemaNameOrId)))
|
|
674
|
+
.limit(1);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
existingWithNewName = await db
|
|
678
|
+
.select({
|
|
679
|
+
id: models.id,
|
|
680
|
+
name: models.name,
|
|
681
|
+
schemaFileId: models.schemaFileId,
|
|
682
|
+
isEdited: models.isEdited,
|
|
683
|
+
})
|
|
684
|
+
.from(models)
|
|
685
|
+
.innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
|
|
686
|
+
.innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
|
|
687
|
+
.where(and(eq(models.name, newName), eq(schemasTable.name, schemaNameOrId)))
|
|
688
|
+
.limit(1);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
existingWithNewName = await db
|
|
693
|
+
.select({
|
|
694
|
+
id: models.id,
|
|
695
|
+
name: models.name,
|
|
696
|
+
schemaFileId: models.schemaFileId,
|
|
697
|
+
isEdited: models.isEdited,
|
|
698
|
+
})
|
|
699
|
+
.from(models)
|
|
700
|
+
.where(eq(models.name, newName))
|
|
701
|
+
.limit(1);
|
|
702
|
+
}
|
|
703
|
+
if (existingWithNewName.length > 0) {
|
|
704
|
+
// New name already exists in this schema, return it
|
|
705
|
+
return existingWithNewName[0];
|
|
706
|
+
}
|
|
707
|
+
// Update the model name
|
|
708
|
+
await db
|
|
709
|
+
.update(models)
|
|
710
|
+
.set({ name: newName })
|
|
711
|
+
.where(eq(models.id, modelToRename.id));
|
|
712
|
+
return { ...modelToRename, name: newName };
|
|
713
|
+
};
|
|
714
|
+
/**
|
|
715
|
+
* Check if a property has been locally edited (has unsaved changes)
|
|
716
|
+
* This checks both the in-memory cache (for current session) and the database
|
|
717
|
+
* (for persistence across reloads) by comparing database values with schema file values.
|
|
718
|
+
* @param modelName - The name of the model
|
|
719
|
+
* @param propertyName - The name of the property
|
|
720
|
+
* @param schemaFileValue - Optional schema file value to compare against database
|
|
721
|
+
* @returns true if the property is marked as edited, false otherwise
|
|
722
|
+
*/
|
|
723
|
+
async function checkIfPropertyIsEdited(modelName, propertyName, schemaFileValue) {
|
|
724
|
+
try {
|
|
725
|
+
// When schemaFileValue is provided (schema sync path), do database check FIRST.
|
|
726
|
+
// This avoids the ModelProperty dynamic import which can hang due to circular
|
|
727
|
+
// dependency (ModelProperty -> helpers/db) when called from addModelsToDb during init.
|
|
728
|
+
if (schemaFileValue) {
|
|
729
|
+
const db = BaseDb.getAppDb();
|
|
730
|
+
if (db) {
|
|
731
|
+
// Find the model
|
|
732
|
+
const modelRecords = await db
|
|
733
|
+
.select()
|
|
734
|
+
.from(models)
|
|
735
|
+
.where(eq(models.name, modelName))
|
|
736
|
+
.limit(1);
|
|
737
|
+
if (modelRecords.length > 0) {
|
|
738
|
+
const modelRecord = modelRecords[0];
|
|
739
|
+
// Find the property in the database
|
|
740
|
+
const propertyRecords = await db
|
|
741
|
+
.select()
|
|
742
|
+
.from(properties)
|
|
743
|
+
.where(and(eq(properties.name, propertyName), eq(properties.modelId, modelRecord.id)))
|
|
744
|
+
.limit(1);
|
|
745
|
+
if (propertyRecords.length > 0) {
|
|
746
|
+
const dbProperty = propertyRecords[0];
|
|
747
|
+
// Compare database value with schema file value
|
|
748
|
+
// If they differ, the property has been edited
|
|
749
|
+
if (dbProperty.dataType !== schemaFileValue.dataType) {
|
|
750
|
+
logger(`Property ${modelName}:${propertyName} has been edited (dataType differs: DB=${dbProperty.dataType}, Schema=${schemaFileValue.dataType})`);
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
// Check refModelId if it's a relation
|
|
754
|
+
if (schemaFileValue.ref) {
|
|
755
|
+
const refModelRecords = await db
|
|
756
|
+
.select()
|
|
757
|
+
.from(models)
|
|
758
|
+
.where(eq(models.name, schemaFileValue.ref))
|
|
759
|
+
.limit(1);
|
|
760
|
+
if (refModelRecords.length > 0) {
|
|
761
|
+
const expectedRefModelId = refModelRecords[0].id;
|
|
762
|
+
if (dbProperty.refModelId !== expectedRefModelId) {
|
|
763
|
+
logger(`Property ${modelName}:${propertyName} has been edited (refModelId differs)`);
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
else if (dbProperty.refModelId !== null) {
|
|
769
|
+
// Schema file has no ref, but DB has one
|
|
770
|
+
logger(`Property ${modelName}:${propertyName} has been edited (refModelId differs)`);
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
// Check refValueType
|
|
774
|
+
if (dbProperty.refValueType !== (schemaFileValue.refValueType || null)) {
|
|
775
|
+
logger(`Property ${modelName}:${propertyName} has been edited (refValueType differs)`);
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
// Check required (for Relation/Image properties)
|
|
779
|
+
if (schemaFileValue.required !== undefined) {
|
|
780
|
+
const schemaRequired = !!schemaFileValue.required;
|
|
781
|
+
const dbRequired = dbProperty.required === true;
|
|
782
|
+
if (dbRequired !== schemaRequired) {
|
|
783
|
+
logger(`Property ${modelName}:${propertyName} has been edited (required differs: DB=${dbRequired}, Schema=${schemaRequired})`);
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
// If we can't check, assume it's not edited to be safe
|
|
795
|
+
logger('Error checking if property is edited:', error);
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Adds models and their properties to the database.
|
|
801
|
+
* Optionally connects models to a schema via join records.
|
|
802
|
+
* @param models - The model definitions to add
|
|
803
|
+
* @param schema - Optional schema to connect models to
|
|
804
|
+
* @param modelRenames - Optional map of old model names to new model names for handling renames
|
|
805
|
+
* @param schemaFileData - Optional object containing schemaFileId mappings from JSON file: { schemaFileId, modelFileIds: Map<modelName, id>, propertyFileIds: Map<modelName, Map<propertyName, id>> }
|
|
806
|
+
*/
|
|
807
|
+
const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) => {
|
|
808
|
+
const db = BaseDb.getAppDb();
|
|
809
|
+
if (!db) {
|
|
810
|
+
throw new Error('Database not found');
|
|
811
|
+
}
|
|
812
|
+
// If schema is provided, add it to the database
|
|
813
|
+
// Note: schema might already be a database record (with id) if passed from importJsonSchema
|
|
814
|
+
let schemaRecord;
|
|
815
|
+
if (schema) {
|
|
816
|
+
// Check if schema already has an id (it's already a database record)
|
|
817
|
+
// SchemaType includes id, so check if it's set
|
|
818
|
+
if (schema.id !== null && schema.id !== undefined) {
|
|
819
|
+
schemaRecord = schema;
|
|
820
|
+
logger(`Using existing schema record with id: ${schemaRecord.id}`);
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
schemaRecord = await addSchemaToDb(schema, schemaFileData?.schemaFileId);
|
|
824
|
+
logger(`Created/found schema record with id: ${schemaRecord.id}`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
// Handle model renames first if provided
|
|
828
|
+
if (modelRenames && schemaRecord) {
|
|
829
|
+
const schemaNameOrId = schemaRecord.name ?? schemaRecord.id;
|
|
830
|
+
for (const [oldName, newName] of modelRenames.entries()) {
|
|
831
|
+
await renameModelInDb(oldName, newName, schemaNameOrId);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
// Search for all models by schemaFileId (preferred) or name and create them if they don't exist
|
|
835
|
+
const modelRecords = new Map();
|
|
836
|
+
const modelProcessingErrors = [];
|
|
837
|
+
for (const [modelName, modelClass] of Object.entries(models$1)) {
|
|
838
|
+
try {
|
|
839
|
+
const modelFileId = schemaFileData?.modelFileIds?.get(modelName);
|
|
840
|
+
let modelRecord = await findOrCreateModelRecord(db, modelName, modelFileId);
|
|
841
|
+
// Keep schemaFileId aligned when findOrCreate returned a row that still needs adoption
|
|
842
|
+
// (e.g. sole same-name row that already had a different non-null id — rare).
|
|
843
|
+
if (modelFileId && modelRecord.schemaFileId !== modelFileId) {
|
|
844
|
+
const existingWithFileId = await db
|
|
845
|
+
.select()
|
|
846
|
+
.from(models)
|
|
847
|
+
.where(eq(models.schemaFileId, modelFileId))
|
|
848
|
+
.limit(1);
|
|
849
|
+
if (existingWithFileId.length > 0 && existingWithFileId[0].id !== modelRecord.id) {
|
|
850
|
+
logger(`WARNING: Model "${modelName}" (id: ${modelRecord.id}) conflicts with existing model "${existingWithFileId[0].name}" (id: ${existingWithFileId[0].id}) both trying to use schemaFileId "${modelFileId}"`);
|
|
851
|
+
modelRecord = existingWithFileId[0];
|
|
852
|
+
}
|
|
853
|
+
else if (!modelRecord.schemaFileId) {
|
|
854
|
+
await db
|
|
855
|
+
.update(models)
|
|
856
|
+
.set({ schemaFileId: modelFileId })
|
|
857
|
+
.where(eq(models.id, modelRecord.id));
|
|
858
|
+
modelRecord = { ...modelRecord, schemaFileId: modelFileId };
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
modelRecords.set(modelName, modelRecord);
|
|
862
|
+
// Get all existing properties for this model upfront to handle renamed properties
|
|
863
|
+
const allDbProperties = await db
|
|
864
|
+
.select()
|
|
865
|
+
.from(properties)
|
|
866
|
+
.where(eq(properties.modelId, modelRecord.id));
|
|
867
|
+
// Track which DB properties have been matched to schema properties
|
|
868
|
+
const matchedDbPropertyIds = new Set();
|
|
869
|
+
// Get properties from Model context (where they're stored as an object when Model is created)
|
|
870
|
+
// modelClass.properties returns ModelProperty instances from DB, but we need the context properties
|
|
871
|
+
// which are stored as an object when Model.create() is called with properties
|
|
872
|
+
const modelContext = modelClass._getSnapshotContext?.() || {};
|
|
873
|
+
const contextProperties = modelContext.properties || {};
|
|
874
|
+
// When models are created from JSON, properties are stored as _pendingPropertyDefinitions
|
|
875
|
+
const pendingProperties = modelContext._pendingPropertyDefinitions || {};
|
|
876
|
+
// If context properties are empty, try modelClass.properties (for backward compatibility)
|
|
877
|
+
// but convert ModelProperty instances to object format
|
|
878
|
+
let schema = {};
|
|
879
|
+
if (Object.keys(contextProperties).length > 0) {
|
|
880
|
+
schema = contextProperties;
|
|
881
|
+
}
|
|
882
|
+
else if (Object.keys(pendingProperties).length > 0) {
|
|
883
|
+
// Use pending property definitions (from JSON import)
|
|
884
|
+
schema = pendingProperties;
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
const _mod_1 = await import('./index--F3Wolt5.js');
|
|
888
|
+
const _ns_1 = _mod_1.ac;
|
|
889
|
+
const modelHelpersMod = _ns_1;
|
|
890
|
+
const { modelPropertiesToObject } = modelHelpersMod;
|
|
891
|
+
const modelProperties = modelClass.properties || [];
|
|
892
|
+
schema = modelPropertiesToObject(modelProperties);
|
|
893
|
+
}
|
|
894
|
+
const schemaPropertyNames = new Set(Object.keys(schema));
|
|
895
|
+
// Search for all properties and create them if they don't exist
|
|
896
|
+
// Properties are unique by name + modelId, so we search using both
|
|
897
|
+
const schemaEntries = Object.entries(schema);
|
|
898
|
+
for (let index = 0; index < schemaEntries.length; index++) {
|
|
899
|
+
const [propertyName, propertyValues] = schemaEntries[index];
|
|
900
|
+
if (!propertyValues) {
|
|
901
|
+
throw new Error(`Property values not found for ${propertyName}`);
|
|
902
|
+
}
|
|
903
|
+
// Get property schemaFileId if available
|
|
904
|
+
let propertyFileId = schemaFileData?.propertyFileIds?.get(modelName)?.get(propertyName);
|
|
905
|
+
// If no propertyFileId from map, generate a random ID
|
|
906
|
+
// IDs should be generated in the import process before calling addModelsToDb
|
|
907
|
+
if (!propertyFileId) {
|
|
908
|
+
const _mod_2 = await import('./index--F3Wolt5.js');
|
|
909
|
+
const _ns_2 = _mod_2.ab;
|
|
910
|
+
const { generateId } = _ns_2;
|
|
911
|
+
propertyFileId = generateId();
|
|
912
|
+
logger(`Generated propertyFileId "${propertyFileId}" for property "${modelName}:${propertyName}" (not found in propertyFileIds map)`);
|
|
913
|
+
}
|
|
914
|
+
// First, try to find by schemaFileId (preferred for change tracking)
|
|
915
|
+
let existingProperty;
|
|
916
|
+
if (propertyFileId) {
|
|
917
|
+
existingProperty = allDbProperties.find((p) => p.schemaFileId === propertyFileId && !matchedDbPropertyIds.has(p.id));
|
|
918
|
+
}
|
|
919
|
+
// Fallback: check if property exists by name + modelId
|
|
920
|
+
if (!existingProperty) {
|
|
921
|
+
existingProperty = allDbProperties.find((p) => p.name === propertyName && !matchedDbPropertyIds.has(p.id));
|
|
922
|
+
}
|
|
923
|
+
// Prepare property values
|
|
924
|
+
const propertyData = {
|
|
925
|
+
name: propertyName,
|
|
926
|
+
modelId: modelRecord.id,
|
|
927
|
+
dataType: propertyValues.dataType,
|
|
928
|
+
schemaFileId: propertyFileId || null,
|
|
929
|
+
required: propertyValues.required ?? false,
|
|
930
|
+
};
|
|
931
|
+
// Handle ref property - create ref model if needed
|
|
932
|
+
// Check both ref and refModelName (refModelName is set by createModelFromJson)
|
|
933
|
+
let expectedRefModelId = null;
|
|
934
|
+
const refModelName = propertyValues.ref || propertyValues.refModelName;
|
|
935
|
+
if (refModelName) {
|
|
936
|
+
// When ref model was already processed in this batch, use it (avoids db round-trip)
|
|
937
|
+
const cachedRef = modelRecords.get(refModelName);
|
|
938
|
+
const refModelFileId = schemaFileData?.modelFileIds?.get(refModelName);
|
|
939
|
+
const refModel = cachedRef
|
|
940
|
+
? cachedRef
|
|
941
|
+
: await findOrCreateModelRecord(db, refModelName, refModelFileId);
|
|
942
|
+
if (!cachedRef) {
|
|
943
|
+
modelRecords.set(refModelName, refModel);
|
|
944
|
+
}
|
|
945
|
+
propertyData.refModelId = refModel.id ?? null;
|
|
946
|
+
expectedRefModelId = refModel.id ?? null;
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
// If it's not a Relation type, ensure refModelId is null
|
|
950
|
+
propertyData.refModelId = null;
|
|
951
|
+
}
|
|
952
|
+
if (propertyValues.refValueType) {
|
|
953
|
+
propertyData.refValueType = propertyValues.refValueType;
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
// If refValueType is not set, ensure it's null
|
|
957
|
+
propertyData.refValueType = null;
|
|
958
|
+
}
|
|
959
|
+
// If not found by name, try to find by position/characteristics (for renamed properties)
|
|
960
|
+
if (!existingProperty) {
|
|
961
|
+
// Look for unmatched DB properties that could be this renamed property
|
|
962
|
+
// Match by: same dataType, same refModelId (if applicable), and not matching any schema property name
|
|
963
|
+
const potentialMatches = allDbProperties.filter((p) => !matchedDbPropertyIds.has(p.id) &&
|
|
964
|
+
!schemaPropertyNames.has(p.name) && // This DB property doesn't match any schema property name
|
|
965
|
+
p.dataType === propertyValues.dataType &&
|
|
966
|
+
(expectedRefModelId === null
|
|
967
|
+
? p.refModelId === null
|
|
968
|
+
: p.refModelId === expectedRefModelId) &&
|
|
969
|
+
(propertyValues.refValueType
|
|
970
|
+
? p.refValueType === propertyValues.refValueType
|
|
971
|
+
: p.refValueType === null));
|
|
972
|
+
// If we find exactly one match, or if we're at the same position index, use it
|
|
973
|
+
// For now, use the first match (could be enhanced to use position more precisely)
|
|
974
|
+
if (potentialMatches.length > 0) {
|
|
975
|
+
const matchedProperty = potentialMatches[0];
|
|
976
|
+
existingProperty = matchedProperty;
|
|
977
|
+
logger(`Found renamed property: ${modelName}:${matchedProperty.name} -> ${propertyName} (matched by characteristics)`);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (existingProperty) {
|
|
981
|
+
// Mark this DB property as matched
|
|
982
|
+
matchedDbPropertyIds.add(existingProperty.id);
|
|
983
|
+
// When syncing from schema (schemaFileData present), skip the expensive checkIfPropertyIsEdited.
|
|
984
|
+
// It can hang for minutes in browser due to circular deps / SQLite contention during init.
|
|
985
|
+
// Use DB isEdited column as a fast check: if already marked edited, preserve.
|
|
986
|
+
const skipEditCheck = !!schemaFileData;
|
|
987
|
+
const isPropertyEdited = skipEditCheck
|
|
988
|
+
? existingProperty.isEdited === true
|
|
989
|
+
: await checkIfPropertyIsEdited(modelName, existingProperty.name, {
|
|
990
|
+
dataType: propertyValues.dataType,
|
|
991
|
+
ref: propertyValues.ref,
|
|
992
|
+
refValueType: propertyValues.refValueType,
|
|
993
|
+
required: propertyValues.required,
|
|
994
|
+
});
|
|
995
|
+
if (isPropertyEdited) {
|
|
996
|
+
// Property has been locally edited - skip update to preserve local changes
|
|
997
|
+
logger(`Skipping update for property ${modelName}:${existingProperty.name} -> ${propertyName} - it has been locally edited and will be preserved`);
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
// Property exists (possibly with different name) - update it with new values from schema file
|
|
1001
|
+
// This handles both regular updates and renames
|
|
1002
|
+
// Ensure schemaFileId is updated if we have it
|
|
1003
|
+
// Preserve existing isEdited value if it was true (local edits take precedence)
|
|
1004
|
+
const updateData = { ...propertyData };
|
|
1005
|
+
if (existingProperty.isEdited === true) {
|
|
1006
|
+
// Preserve isEdited = true if property was locally edited
|
|
1007
|
+
delete updateData.isEdited;
|
|
1008
|
+
}
|
|
1009
|
+
else {
|
|
1010
|
+
updateData.isEdited = false;
|
|
1011
|
+
}
|
|
1012
|
+
// Skip db.update when data unchanged (avoids slow OPFS writes in browser)
|
|
1013
|
+
const unchanged = existingProperty.name === updateData.name &&
|
|
1014
|
+
existingProperty.dataType === updateData.dataType &&
|
|
1015
|
+
existingProperty.schemaFileId === (updateData.schemaFileId ?? null) &&
|
|
1016
|
+
existingProperty.required === (updateData.required ?? false) &&
|
|
1017
|
+
existingProperty.refModelId === (updateData.refModelId ?? null) &&
|
|
1018
|
+
existingProperty.refValueType === (updateData.refValueType ?? null) &&
|
|
1019
|
+
existingProperty.isEdited === (updateData.isEdited ?? false);
|
|
1020
|
+
if (!unchanged) {
|
|
1021
|
+
await db
|
|
1022
|
+
.update(properties)
|
|
1023
|
+
.set(updateData)
|
|
1024
|
+
.where(eq(properties.id, existingProperty.id));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
// Property doesn't exist, create it with schemaFileId
|
|
1029
|
+
// Set isEdited = false when loading from schema file
|
|
1030
|
+
const propertyDataWithIsEdited = {
|
|
1031
|
+
...propertyData,
|
|
1032
|
+
isEdited: false,
|
|
1033
|
+
};
|
|
1034
|
+
// Global schemaFileId lookup (unique across all models) before insert — avoids
|
|
1035
|
+
// UNIQUE races when the property already exists on another model row or from a prior pass.
|
|
1036
|
+
if (propertyFileId) {
|
|
1037
|
+
const existingByFileId = await db
|
|
1038
|
+
.select()
|
|
1039
|
+
.from(properties)
|
|
1040
|
+
.where(eq(properties.schemaFileId, propertyFileId))
|
|
1041
|
+
.limit(1);
|
|
1042
|
+
if (existingByFileId.length > 0) {
|
|
1043
|
+
const found = existingByFileId[0];
|
|
1044
|
+
matchedDbPropertyIds.add(found.id);
|
|
1045
|
+
logger(`Property ${modelName}:${propertyName} already exists by schemaFileId "${propertyFileId}" (id: ${found.id}), skipping insert`);
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
logger(`Creating new property ${modelName}:${propertyName} with schemaFileId: ${propertyData.schemaFileId}`);
|
|
1050
|
+
try {
|
|
1051
|
+
await db.insert(properties).values(propertyDataWithIsEdited);
|
|
1052
|
+
}
|
|
1053
|
+
catch (insertError) {
|
|
1054
|
+
// Treat UNIQUE constraint as success - property already exists (e.g. from concurrent addModelsToDb call)
|
|
1055
|
+
if (isSqliteUniqueConstraintError(insertError)) {
|
|
1056
|
+
logger(`Property ${modelName}:${propertyName} already exists (UNIQUE constraint), treating as success`);
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
throw insertError;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
catch (error) {
|
|
1066
|
+
const errorMessage = error?.message || String(error);
|
|
1067
|
+
logger(`Error processing model "${modelName}": ${errorMessage}`);
|
|
1068
|
+
modelProcessingErrors.push({ modelName, error: errorMessage });
|
|
1069
|
+
// Continue processing other models even if one fails
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
if (modelProcessingErrors.length > 0) {
|
|
1073
|
+
console.warn(`[addModelsToDb] Errors occurred while processing ${modelProcessingErrors.length} model(s):`, modelProcessingErrors.map(e => `${e.modelName}: ${e.error}`).join('; '));
|
|
1074
|
+
logger(`Errors processing models: ${modelProcessingErrors.map(e => e.modelName).join(', ')}`);
|
|
1075
|
+
}
|
|
1076
|
+
// If schema was provided, create modelSchema join records to connect models to the schema
|
|
1077
|
+
if (schemaRecord && schemaRecord.id) {
|
|
1078
|
+
const modelIds = [...modelRecords.entries()]
|
|
1079
|
+
.filter(([, r]) => r.id)
|
|
1080
|
+
.map(([name, r]) => ({ modelName: name, modelId: r.id }));
|
|
1081
|
+
if (modelIds.length === 0) {
|
|
1082
|
+
logger('No model IDs to link, skipping schema linking');
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
// Single batch query: get all existing joins for this schema
|
|
1086
|
+
const existingJoins = await db
|
|
1087
|
+
.select({ modelId: modelSchemas.modelId })
|
|
1088
|
+
.from(modelSchemas)
|
|
1089
|
+
.where(eq(modelSchemas.schemaId, schemaRecord.id));
|
|
1090
|
+
const existingSet = new Set(existingJoins.map((r) => r.modelId));
|
|
1091
|
+
const toInsert = modelIds.filter(({ modelId }) => !existingSet.has(modelId));
|
|
1092
|
+
if (toInsert.length > 0) {
|
|
1093
|
+
for (const { modelName, modelId } of toInsert) {
|
|
1094
|
+
await db.insert(modelSchemas).values({
|
|
1095
|
+
modelId,
|
|
1096
|
+
schemaId: schemaRecord.id,
|
|
1097
|
+
});
|
|
1098
|
+
logger(`Created join table entry for model ${modelName} (id: ${modelId}) to schema (id: ${schemaRecord.id})`);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
else if (schemaRecord && !schemaRecord.id) {
|
|
1104
|
+
logger(`Warning: schemaRecord provided but has no id, cannot create join table entries`);
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
/**
|
|
1108
|
+
* Loads models from the database for a given schema by querying the model_schemas join table.
|
|
1109
|
+
* This ensures that models added to the database (via model_schemas) are included even if
|
|
1110
|
+
* they're not in the schemaData JSON.
|
|
1111
|
+
* @param schemaId - The ID of the schema record in the database
|
|
1112
|
+
* @returns A map of model names to model data (compatible with SchemaFileFormat.models)
|
|
1113
|
+
*/
|
|
1114
|
+
const loadModelsFromDbForSchema = async (schemaId) => {
|
|
1115
|
+
const db = BaseDb.getAppDb();
|
|
1116
|
+
if (!db) {
|
|
1117
|
+
logger('Database not found, cannot load models from DB');
|
|
1118
|
+
return {};
|
|
1119
|
+
}
|
|
1120
|
+
try {
|
|
1121
|
+
// Query model_schemas join table to find all models linked to this schema
|
|
1122
|
+
const modelSchemaRecords = await db
|
|
1123
|
+
.select({
|
|
1124
|
+
modelId: modelSchemas.modelId,
|
|
1125
|
+
modelName: models.name,
|
|
1126
|
+
schemaFileId: models.schemaFileId,
|
|
1127
|
+
})
|
|
1128
|
+
.from(modelSchemas)
|
|
1129
|
+
.innerJoin(models, eq(modelSchemas.modelId, models.id))
|
|
1130
|
+
.where(eq(modelSchemas.schemaId, schemaId));
|
|
1131
|
+
const models$1 = {};
|
|
1132
|
+
// For each model, load its properties
|
|
1133
|
+
for (const { modelId, modelName, schemaFileId } of modelSchemaRecords) {
|
|
1134
|
+
if (!modelId || !modelName)
|
|
1135
|
+
continue;
|
|
1136
|
+
// Get all properties for this model
|
|
1137
|
+
const propertyRecords = await db
|
|
1138
|
+
.select()
|
|
1139
|
+
.from(properties)
|
|
1140
|
+
.where(eq(properties.modelId, modelId));
|
|
1141
|
+
// Reconstruct properties object
|
|
1142
|
+
const modelProperties = {};
|
|
1143
|
+
for (const prop of propertyRecords) {
|
|
1144
|
+
// Build a basic property structure from database fields
|
|
1145
|
+
// Note: This is a simplified reconstruction - full property schemas
|
|
1146
|
+
// (like Relation details, List configs, etc.) should come from schemaData
|
|
1147
|
+
const propertyData = {
|
|
1148
|
+
dataType: prop.dataType,
|
|
1149
|
+
};
|
|
1150
|
+
if (prop.schemaFileId) {
|
|
1151
|
+
propertyData.schemaFileId = prop.schemaFileId;
|
|
1152
|
+
}
|
|
1153
|
+
// Add ref information if it's a relation
|
|
1154
|
+
if (prop.refModelId) {
|
|
1155
|
+
// Get the referenced model name
|
|
1156
|
+
const refModelRecords = await db
|
|
1157
|
+
.select({ name: models.name })
|
|
1158
|
+
.from(models)
|
|
1159
|
+
.where(eq(models.id, prop.refModelId))
|
|
1160
|
+
.limit(1);
|
|
1161
|
+
if (refModelRecords.length > 0) {
|
|
1162
|
+
propertyData.ref = refModelRecords[0].name;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (prop.refValueType) {
|
|
1166
|
+
propertyData.refValueType = prop.refValueType;
|
|
1167
|
+
}
|
|
1168
|
+
modelProperties[prop.name] = propertyData;
|
|
1169
|
+
}
|
|
1170
|
+
// Create model structure. Include id (schemaFileId) so processSchemaFiles can populate
|
|
1171
|
+
// modelFileIds and Model.create can skip duplicate-name logic, preventing model
|
|
1172
|
+
// duplication on reload (Post 1 1, Post 2 1, etc.).
|
|
1173
|
+
models$1[modelName] = {
|
|
1174
|
+
properties: modelProperties,
|
|
1175
|
+
...(schemaFileId ? { id: schemaFileId } : {}),
|
|
1176
|
+
// Note: description would need to be stored separately
|
|
1177
|
+
// or reconstructed from schemaData if available
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
logger(`Loaded ${Object.keys(models$1).length} models from database for schema ${schemaId}`);
|
|
1181
|
+
return models$1;
|
|
1182
|
+
}
|
|
1183
|
+
catch (error) {
|
|
1184
|
+
logger(`Error loading models from database for schema ${schemaId}:`, error);
|
|
1185
|
+
return {};
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
/**
|
|
1189
|
+
* Returns model name by database model ID.
|
|
1190
|
+
*/
|
|
1191
|
+
async function getModelNameByModelId(modelId) {
|
|
1192
|
+
const db = BaseDb.getAppDb();
|
|
1193
|
+
if (!db)
|
|
1194
|
+
return undefined;
|
|
1195
|
+
try {
|
|
1196
|
+
const rows = await db
|
|
1197
|
+
.select({ name: models.name })
|
|
1198
|
+
.from(models)
|
|
1199
|
+
.where(eq(models.id, modelId))
|
|
1200
|
+
.limit(1);
|
|
1201
|
+
return rows.length > 0 ? rows[0].name : undefined;
|
|
1202
|
+
}
|
|
1203
|
+
catch {
|
|
1204
|
+
return undefined;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Resolves modelName and dataType for a property by its schemaFileId (e.g. context.id).
|
|
1209
|
+
* Used when machine context lacks these (e.g. just-created property renamed before full context is set).
|
|
1210
|
+
*/
|
|
1211
|
+
async function getPropertyModelNameAndDataType(schemaFileId) {
|
|
1212
|
+
const db = BaseDb.getAppDb();
|
|
1213
|
+
if (!db || !schemaFileId)
|
|
1214
|
+
return undefined;
|
|
1215
|
+
try {
|
|
1216
|
+
const rows = await db
|
|
1217
|
+
.select({
|
|
1218
|
+
dataType: properties.dataType,
|
|
1219
|
+
modelId: properties.modelId,
|
|
1220
|
+
})
|
|
1221
|
+
.from(properties)
|
|
1222
|
+
.where(eq(properties.schemaFileId, schemaFileId))
|
|
1223
|
+
.limit(1);
|
|
1224
|
+
if (rows.length === 0)
|
|
1225
|
+
return undefined;
|
|
1226
|
+
const modelRows = await db
|
|
1227
|
+
.select({ name: models.name })
|
|
1228
|
+
.from(models)
|
|
1229
|
+
.where(eq(models.id, rows[0].modelId))
|
|
1230
|
+
.limit(1);
|
|
1231
|
+
if (modelRows.length === 0)
|
|
1232
|
+
return undefined;
|
|
1233
|
+
return {
|
|
1234
|
+
modelName: modelRows[0].name,
|
|
1235
|
+
dataType: rows[0].dataType ?? '',
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
catch {
|
|
1239
|
+
return undefined;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Saves a property's changes to the database without updating the JSON schema file.
|
|
1244
|
+
* This is used when properties are edited but the schema hasn't been saved as a new version yet.
|
|
1245
|
+
* @param property - The ModelPropertyMachineContext with updated values
|
|
1246
|
+
*/
|
|
1247
|
+
const savePropertyToDb = async (property) => {
|
|
1248
|
+
const db = BaseDb.getAppDb();
|
|
1249
|
+
if (!db) {
|
|
1250
|
+
throw new Error('Database not found');
|
|
1251
|
+
}
|
|
1252
|
+
if (!property.name) {
|
|
1253
|
+
throw new Error('Property name is required');
|
|
1254
|
+
}
|
|
1255
|
+
// Find the model: prefer modelId (reliable), then modelName as name, then modelName as schemaFileId
|
|
1256
|
+
// (context.modelName can be a model schemaFileId when schema uses that as identifier)
|
|
1257
|
+
let modelRecord;
|
|
1258
|
+
if (property.modelId != null && typeof property.modelId === 'number') {
|
|
1259
|
+
const byId = await db
|
|
1260
|
+
.select()
|
|
1261
|
+
.from(models)
|
|
1262
|
+
.where(eq(models.id, property.modelId))
|
|
1263
|
+
.limit(1);
|
|
1264
|
+
modelRecord = byId[0];
|
|
1265
|
+
}
|
|
1266
|
+
if (!modelRecord && property.modelName) {
|
|
1267
|
+
const byName = await db
|
|
1268
|
+
.select()
|
|
1269
|
+
.from(models)
|
|
1270
|
+
.where(eq(models.name, property.modelName))
|
|
1271
|
+
.limit(1);
|
|
1272
|
+
modelRecord = byName[0];
|
|
1273
|
+
}
|
|
1274
|
+
if (!modelRecord && property.modelName) {
|
|
1275
|
+
const bySchemaFileId = await db
|
|
1276
|
+
.select()
|
|
1277
|
+
.from(models)
|
|
1278
|
+
.where(eq(models.schemaFileId, property.modelName))
|
|
1279
|
+
.limit(1);
|
|
1280
|
+
modelRecord = bySchemaFileId[0];
|
|
1281
|
+
}
|
|
1282
|
+
// Fallback: resolve model from existing property row (by schemaFileId) when modelName is wrong
|
|
1283
|
+
const schemaFileId = property._propertyFileId || (typeof property.id === 'string' ? property.id : undefined);
|
|
1284
|
+
if (!modelRecord && schemaFileId) {
|
|
1285
|
+
const propRows = await db
|
|
1286
|
+
.select({ modelId: properties.modelId })
|
|
1287
|
+
.from(properties)
|
|
1288
|
+
.where(eq(properties.schemaFileId, schemaFileId))
|
|
1289
|
+
.limit(1);
|
|
1290
|
+
if (propRows.length > 0 && propRows[0].modelId) {
|
|
1291
|
+
const byPropModelId = await db
|
|
1292
|
+
.select()
|
|
1293
|
+
.from(models)
|
|
1294
|
+
.where(eq(models.id, propRows[0].modelId))
|
|
1295
|
+
.limit(1);
|
|
1296
|
+
modelRecord = byPropModelId[0];
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (!modelRecord) {
|
|
1300
|
+
throw new Error(`Model not found in database (modelId=${property.modelId}, modelName=${property.modelName})`);
|
|
1301
|
+
}
|
|
1302
|
+
// Find existing property - try multiple strategies to handle name changes
|
|
1303
|
+
// 1. First try by schemaFileId (most reliable - doesn't change when name changes)
|
|
1304
|
+
let existingProperties = [];
|
|
1305
|
+
logger(`[savePropertyToDb] Looking for property ${property.modelName}:${property.name} (schemaFileId: ${schemaFileId}, originalName: ${property._originalValues?.name})`);
|
|
1306
|
+
if (schemaFileId) {
|
|
1307
|
+
existingProperties = await db
|
|
1308
|
+
.select()
|
|
1309
|
+
.from(properties)
|
|
1310
|
+
.where(and(eq(properties.schemaFileId, schemaFileId), eq(properties.modelId, modelRecord.id)))
|
|
1311
|
+
.limit(1);
|
|
1312
|
+
logger(`[savePropertyToDb] Found ${existingProperties.length} properties by schemaFileId`);
|
|
1313
|
+
}
|
|
1314
|
+
// 2. If not found by schemaFileId, try by original name (if name was changed)
|
|
1315
|
+
if (existingProperties.length === 0 && property._originalValues?.name && property._originalValues.name !== property.name) {
|
|
1316
|
+
logger(`[savePropertyToDb] Trying to find by original name: ${property._originalValues.name}`);
|
|
1317
|
+
existingProperties = await db
|
|
1318
|
+
.select()
|
|
1319
|
+
.from(properties)
|
|
1320
|
+
.where(and(eq(properties.name, property._originalValues.name), eq(properties.modelId, modelRecord.id)))
|
|
1321
|
+
.limit(1);
|
|
1322
|
+
logger(`[savePropertyToDb] Found ${existingProperties.length} properties by original name`);
|
|
1323
|
+
}
|
|
1324
|
+
// 3. Fallback: try by current name
|
|
1325
|
+
if (existingProperties.length === 0) {
|
|
1326
|
+
logger(`[savePropertyToDb] Trying to find by current name: ${property.name}`);
|
|
1327
|
+
existingProperties = await db
|
|
1328
|
+
.select()
|
|
1329
|
+
.from(properties)
|
|
1330
|
+
.where(and(eq(properties.name, property.name), eq(properties.modelId, modelRecord.id)))
|
|
1331
|
+
.limit(1);
|
|
1332
|
+
logger(`[savePropertyToDb] Found ${existingProperties.length} properties by current name`);
|
|
1333
|
+
}
|
|
1334
|
+
// Prepare property data
|
|
1335
|
+
const propertyData = {
|
|
1336
|
+
name: property.name,
|
|
1337
|
+
modelId: modelRecord.id,
|
|
1338
|
+
dataType: property.dataType || '',
|
|
1339
|
+
};
|
|
1340
|
+
// Preserve schemaFileId if we have it
|
|
1341
|
+
if (schemaFileId) {
|
|
1342
|
+
propertyData.schemaFileId = schemaFileId;
|
|
1343
|
+
}
|
|
1344
|
+
// Handle ref property - create ref model if needed
|
|
1345
|
+
if (property.refModelName) {
|
|
1346
|
+
const refModel = await createOrUpdate(db, models, {
|
|
1347
|
+
name: property.refModelName,
|
|
1348
|
+
});
|
|
1349
|
+
propertyData.refModelId = refModel.id;
|
|
1350
|
+
}
|
|
1351
|
+
else if (property.refModelId) {
|
|
1352
|
+
propertyData.refModelId = property.refModelId;
|
|
1353
|
+
}
|
|
1354
|
+
else {
|
|
1355
|
+
// If it's not a Relation type, ensure refModelId is null
|
|
1356
|
+
propertyData.refModelId = null;
|
|
1357
|
+
}
|
|
1358
|
+
if (property.refValueType) {
|
|
1359
|
+
propertyData.refValueType = property.refValueType;
|
|
1360
|
+
}
|
|
1361
|
+
else {
|
|
1362
|
+
// If refValueType is not set, ensure it's null
|
|
1363
|
+
propertyData.refValueType = null;
|
|
1364
|
+
}
|
|
1365
|
+
propertyData.required = property.required ?? false;
|
|
1366
|
+
if (existingProperties.length > 0) {
|
|
1367
|
+
// Property exists, update it with new values (including new name)
|
|
1368
|
+
// Set isEdited = true when property is edited
|
|
1369
|
+
const existingProperty = existingProperties[0];
|
|
1370
|
+
await db
|
|
1371
|
+
.update(properties)
|
|
1372
|
+
.set({
|
|
1373
|
+
...propertyData,
|
|
1374
|
+
isEdited: true, // Mark as edited when saving changes
|
|
1375
|
+
})
|
|
1376
|
+
.where(eq(properties.id, existingProperty.id));
|
|
1377
|
+
logger(`Updated property ${property.modelName}:${property._originalValues?.name || 'unknown'} -> ${property.name} in database`);
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
// Property doesn't exist, create it
|
|
1381
|
+
// Set isEdited = true for runtime-created properties
|
|
1382
|
+
try {
|
|
1383
|
+
await db.insert(properties).values({
|
|
1384
|
+
...propertyData,
|
|
1385
|
+
isEdited: true, // Runtime-created properties are edited
|
|
1386
|
+
});
|
|
1387
|
+
logger(`Created property ${property.modelName}:${property.name} in database`);
|
|
1388
|
+
}
|
|
1389
|
+
catch (insertError) {
|
|
1390
|
+
// Treat UNIQUE constraint as success - property already exists (e.g. from concurrent addModelsToDb or race)
|
|
1391
|
+
if (isSqliteUniqueConstraintError(insertError)) {
|
|
1392
|
+
logger(`Property ${property.modelName}:${property.name} already exists (UNIQUE constraint), treating as success`);
|
|
1393
|
+
}
|
|
1394
|
+
else {
|
|
1395
|
+
throw insertError;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
function parseAddressConfig(value) {
|
|
1401
|
+
if (!value)
|
|
1402
|
+
return null;
|
|
1403
|
+
try {
|
|
1404
|
+
const parsed = JSON.parse(value);
|
|
1405
|
+
if (Array.isArray(parsed)) {
|
|
1406
|
+
return { owned: parsed, watched: [] };
|
|
1407
|
+
}
|
|
1408
|
+
if (parsed && typeof parsed === 'object' && Array.isArray(parsed.owned)) {
|
|
1409
|
+
return {
|
|
1410
|
+
owned: parsed.owned,
|
|
1411
|
+
watched: Array.isArray(parsed.watched) ? parsed.watched : [],
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
catch {
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
const getOwnedAddressesFromDb = async () => {
|
|
1421
|
+
const config = await getAddressConfigFromDb();
|
|
1422
|
+
return config?.owned ?? [];
|
|
1423
|
+
};
|
|
1424
|
+
const getWatchedAddressesFromDb = async () => {
|
|
1425
|
+
const config = await getAddressConfigFromDb();
|
|
1426
|
+
return config?.watched ?? [];
|
|
1427
|
+
};
|
|
1428
|
+
/**
|
|
1429
|
+
* Returns addresses for useItems/getItemsData addressFilter.
|
|
1430
|
+
* Encapsulates owned (with additional sync addresses) and watched logic.
|
|
1431
|
+
*/
|
|
1432
|
+
const getAddressesForItemsFilter = async (addressFilter) => {
|
|
1433
|
+
if (addressFilter === 'owned') {
|
|
1434
|
+
const _mod_3 = await import('./index--F3Wolt5.js');
|
|
1435
|
+
const _ns_3 = _mod_3.aa;
|
|
1436
|
+
const { getGetAdditionalSyncAddresses } = _ns_3;
|
|
1437
|
+
let ownedAddresses = await getOwnedAddressesFromDb();
|
|
1438
|
+
const additionalGetter = getGetAdditionalSyncAddresses();
|
|
1439
|
+
if (additionalGetter) {
|
|
1440
|
+
const additional = await additionalGetter();
|
|
1441
|
+
if (additional?.length) {
|
|
1442
|
+
const seen = new Set(ownedAddresses.map((a) => a.toLowerCase()));
|
|
1443
|
+
for (const addr of additional) {
|
|
1444
|
+
if (addr && !seen.has(addr.toLowerCase())) {
|
|
1445
|
+
seen.add(addr.toLowerCase());
|
|
1446
|
+
ownedAddresses = [...ownedAddresses, addr];
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return ownedAddresses;
|
|
1452
|
+
}
|
|
1453
|
+
return getWatchedAddressesFromDb();
|
|
1454
|
+
};
|
|
1455
|
+
/**
|
|
1456
|
+
* Returns owned + watched addresses. Use for EAS sync and file download.
|
|
1457
|
+
*/
|
|
1458
|
+
const getAllAddressesFromDb = async () => {
|
|
1459
|
+
const config = await getAddressConfigFromDb();
|
|
1460
|
+
if (!config)
|
|
1461
|
+
return [];
|
|
1462
|
+
return [...config.owned, ...config.watched];
|
|
1463
|
+
};
|
|
1464
|
+
async function getAddressConfigFromDb() {
|
|
1465
|
+
const appDb = BaseDb.getAppDb();
|
|
1466
|
+
if (!appDb) {
|
|
1467
|
+
return new Promise((resolve) => {
|
|
1468
|
+
setTimeout(async () => {
|
|
1469
|
+
resolve(await getAddressConfigFromDb());
|
|
1470
|
+
}, 500);
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
const appStatesRecords = await appDb
|
|
1474
|
+
.select()
|
|
1475
|
+
.from(appState)
|
|
1476
|
+
.where(eq(appState.key, 'addresses'))
|
|
1477
|
+
.limit(1);
|
|
1478
|
+
if (!appStatesRecords || appStatesRecords.length === 0) {
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
const addressArrayString = appStatesRecords[0].value;
|
|
1482
|
+
return parseAddressConfig(addressArrayString);
|
|
1483
|
+
}
|
|
1484
|
+
const getAddressesFromDb = async () => {
|
|
1485
|
+
const config = await getAddressConfigFromDb();
|
|
1486
|
+
if (!config || config.owned.length === 0) {
|
|
1487
|
+
throw new Error('No addresses found');
|
|
1488
|
+
}
|
|
1489
|
+
return config.owned;
|
|
1490
|
+
};
|
|
1491
|
+
/**
|
|
1492
|
+
* Like getAddressesFromDb but returns [] instead of throwing when no addresses are configured.
|
|
1493
|
+
* Returns owned addresses. Use getAllAddressesFromDb for sync (owned + watched).
|
|
1494
|
+
*/
|
|
1495
|
+
const getAddressesFromDbOptional = async () => {
|
|
1496
|
+
const config = await getAddressConfigFromDb();
|
|
1497
|
+
return config?.owned ?? [];
|
|
1498
|
+
};
|
|
1499
|
+
/**
|
|
1500
|
+
* Write model to database and create model_schemas join entry
|
|
1501
|
+
* @param modelFileId - The model file ID (schema_file_id)
|
|
1502
|
+
* @param data - Model data including modelName, schemaId, and optional properties
|
|
1503
|
+
*/
|
|
1504
|
+
async function writeModelToDb(modelFileId, data) {
|
|
1505
|
+
const db = BaseDb.getAppDb();
|
|
1506
|
+
if (!db)
|
|
1507
|
+
throw new Error('Database not available');
|
|
1508
|
+
// Find or create model record
|
|
1509
|
+
let modelRecords = await db
|
|
1510
|
+
.select()
|
|
1511
|
+
.from(models)
|
|
1512
|
+
.where(eq(models.schemaFileId, modelFileId))
|
|
1513
|
+
.limit(1);
|
|
1514
|
+
let modelId;
|
|
1515
|
+
if (modelRecords.length === 0) {
|
|
1516
|
+
// Create new model record
|
|
1517
|
+
// Check again right before insert to avoid race conditions
|
|
1518
|
+
const doubleCheck = await db
|
|
1519
|
+
.select()
|
|
1520
|
+
.from(models)
|
|
1521
|
+
.where(eq(models.schemaFileId, modelFileId))
|
|
1522
|
+
.limit(1);
|
|
1523
|
+
if (doubleCheck.length > 0) {
|
|
1524
|
+
// Another process created it, use existing
|
|
1525
|
+
modelId = doubleCheck[0].id;
|
|
1526
|
+
logger(`Model with schemaFileId "${modelFileId}" was created by another process, using existing record (id: ${modelId})`);
|
|
1527
|
+
}
|
|
1528
|
+
else {
|
|
1529
|
+
try {
|
|
1530
|
+
const newModel = await db.insert(models).values({
|
|
1531
|
+
name: data.modelName,
|
|
1532
|
+
schemaFileId: modelFileId,
|
|
1533
|
+
isEdited: true, // Runtime-created models are edited
|
|
1534
|
+
}).returning();
|
|
1535
|
+
modelId = newModel[0].id;
|
|
1536
|
+
}
|
|
1537
|
+
catch (error) {
|
|
1538
|
+
// Handle unique constraint violation
|
|
1539
|
+
if (isSqliteUniqueConstraintError(error)) {
|
|
1540
|
+
logger(`Unique constraint violation for schemaFileId "${modelFileId}", attempting to find existing model`);
|
|
1541
|
+
const existing = await db
|
|
1542
|
+
.select()
|
|
1543
|
+
.from(models)
|
|
1544
|
+
.where(eq(models.schemaFileId, modelFileId))
|
|
1545
|
+
.limit(1);
|
|
1546
|
+
if (existing.length > 0) {
|
|
1547
|
+
modelId = existing[0].id;
|
|
1548
|
+
logger(`Found existing model with schemaFileId "${modelFileId}" (id: ${modelId}) after constraint violation`);
|
|
1549
|
+
}
|
|
1550
|
+
else {
|
|
1551
|
+
throw error;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
else {
|
|
1555
|
+
throw error;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
// Update existing model record
|
|
1562
|
+
modelId = modelRecords[0].id;
|
|
1563
|
+
const updates = {};
|
|
1564
|
+
if (data.modelName !== modelRecords[0].name) {
|
|
1565
|
+
updates.name = data.modelName;
|
|
1566
|
+
}
|
|
1567
|
+
if (Object.keys(updates).length > 0) {
|
|
1568
|
+
await db
|
|
1569
|
+
.update(models)
|
|
1570
|
+
.set(updates)
|
|
1571
|
+
.where(eq(models.id, modelId));
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
// Validate that modelId and schemaId are valid before creating join entry
|
|
1575
|
+
if (!modelId || !Number.isInteger(modelId) || modelId <= 0) {
|
|
1576
|
+
throw new Error(`Invalid modelId: ${modelId}. Model record must be created successfully before creating join entry.`);
|
|
1577
|
+
}
|
|
1578
|
+
if (!data.schemaId || !Number.isInteger(data.schemaId) || data.schemaId <= 0) {
|
|
1579
|
+
throw new Error(`Invalid schemaId: ${data.schemaId}. Schema record must exist before creating join entry.`);
|
|
1580
|
+
}
|
|
1581
|
+
// Create model_schemas join entry
|
|
1582
|
+
// CRITICAL: This must happen AFTER the model record is fully saved and committed
|
|
1583
|
+
// The modelId comes from .returning() which ensures the record is committed
|
|
1584
|
+
const existingJoin = await db
|
|
1585
|
+
.select()
|
|
1586
|
+
.from(modelSchemas)
|
|
1587
|
+
.where(and(eq(modelSchemas.modelId, modelId), eq(modelSchemas.schemaId, data.schemaId)))
|
|
1588
|
+
.limit(1);
|
|
1589
|
+
if (existingJoin.length === 0) {
|
|
1590
|
+
// Only provide modelId and schemaId - id is auto-increment and should not be included
|
|
1591
|
+
// Don't use type cast - let Drizzle infer the correct type without id
|
|
1592
|
+
// Verify both modelId and schemaId exist before inserting join record
|
|
1593
|
+
try {
|
|
1594
|
+
// Verify modelId exists
|
|
1595
|
+
const modelCheck = await db
|
|
1596
|
+
.select({ id: models.id })
|
|
1597
|
+
.from(models)
|
|
1598
|
+
.where(eq(models.id, modelId))
|
|
1599
|
+
.limit(1);
|
|
1600
|
+
if (modelCheck.length === 0) {
|
|
1601
|
+
throw new Error(`Model with id ${modelId} does not exist in database. Cannot create join record.`);
|
|
1602
|
+
}
|
|
1603
|
+
// Verify schemaId exists and get name/fileId for invalidation broadcast
|
|
1604
|
+
const _mod_4 = await import('./BasePathResolver-BDgc7c4T.js');
|
|
1605
|
+
const _ns_4 = _mod_4.S;
|
|
1606
|
+
const schemaSchemaMod = _ns_4;
|
|
1607
|
+
const { schemas: schemasTable } = schemaSchemaMod;
|
|
1608
|
+
const schemaCheck = await db
|
|
1609
|
+
.select({
|
|
1610
|
+
id: schemasTable.id,
|
|
1611
|
+
name: schemasTable.name,
|
|
1612
|
+
schemaFileId: schemasTable.schemaFileId,
|
|
1613
|
+
})
|
|
1614
|
+
.from(schemasTable)
|
|
1615
|
+
.where(eq(schemasTable.id, data.schemaId))
|
|
1616
|
+
.limit(1);
|
|
1617
|
+
if (schemaCheck.length === 0) {
|
|
1618
|
+
throw new Error(`Schema with id ${data.schemaId} does not exist in database. Cannot create join record.`);
|
|
1619
|
+
}
|
|
1620
|
+
logger(`Creating join record: modelId=${modelId}, schemaId=${data.schemaId} (both verified to exist)`);
|
|
1621
|
+
await db.insert(modelSchemas).values({
|
|
1622
|
+
modelId,
|
|
1623
|
+
schemaId: data.schemaId,
|
|
1624
|
+
});
|
|
1625
|
+
// Notify React useModels so it can invalidate; live query over join often doesn't re-run when model_schemas is inserted.
|
|
1626
|
+
// Yield so the insert is visible to the refetch that will run when the broadcast is received.
|
|
1627
|
+
if (typeof BroadcastChannel !== 'undefined') {
|
|
1628
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1629
|
+
const row = schemaCheck[0];
|
|
1630
|
+
try {
|
|
1631
|
+
new BroadcastChannel('seed-models-invalidate').postMessage({
|
|
1632
|
+
schemaName: row.name ?? undefined,
|
|
1633
|
+
schemaFileId: row.schemaFileId ?? undefined,
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
catch (_) { }
|
|
1637
|
+
}
|
|
1638
|
+
logger(`Successfully created join record for model ${data.modelName} (id: ${modelId}) to schema (id: ${data.schemaId})`);
|
|
1639
|
+
}
|
|
1640
|
+
catch (error) {
|
|
1641
|
+
if (error?.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
|
|
1642
|
+
logger(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}"`);
|
|
1643
|
+
logger(`modelId: ${modelId}, schemaId: ${data.schemaId}`);
|
|
1644
|
+
logger(`Error details:`, error);
|
|
1645
|
+
// Re-throw with more context
|
|
1646
|
+
throw new Error(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}" (modelId: ${modelId}, schemaId: ${data.schemaId}). ${error.message}`);
|
|
1647
|
+
}
|
|
1648
|
+
throw error;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
// Write properties if provided
|
|
1652
|
+
if (data.properties) {
|
|
1653
|
+
for (const [propName, propData] of Object.entries(data.properties)) {
|
|
1654
|
+
// Check if property already exists in database
|
|
1655
|
+
const existingProps = await db
|
|
1656
|
+
.select()
|
|
1657
|
+
.from(properties)
|
|
1658
|
+
.where(and(eq(properties.name, propName), eq(properties.modelId, modelId)))
|
|
1659
|
+
.limit(1);
|
|
1660
|
+
let propertyFileId;
|
|
1661
|
+
if (existingProps.length > 0 && existingProps[0].schemaFileId) {
|
|
1662
|
+
// Use existing property's schemaFileId
|
|
1663
|
+
propertyFileId = existingProps[0].schemaFileId;
|
|
1664
|
+
logger(`Using existing propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
|
|
1665
|
+
}
|
|
1666
|
+
else if (propData.id) {
|
|
1667
|
+
// Use provided ID
|
|
1668
|
+
propertyFileId = propData.id;
|
|
1669
|
+
}
|
|
1670
|
+
else {
|
|
1671
|
+
// Generate random propertyFileId
|
|
1672
|
+
// IDs should be generated in the import process before calling writeModelToDb
|
|
1673
|
+
const _mod_5 = await import('./index--F3Wolt5.js');
|
|
1674
|
+
const _ns_5 = _mod_5.ab;
|
|
1675
|
+
const { generateId } = _ns_5;
|
|
1676
|
+
propertyFileId = generateId();
|
|
1677
|
+
logger(`Generated propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
|
|
1678
|
+
}
|
|
1679
|
+
await writePropertyToDb(propertyFileId, {
|
|
1680
|
+
modelId,
|
|
1681
|
+
name: propName,
|
|
1682
|
+
...propData,
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
logger(`Wrote model ${data.modelName} (${modelFileId}) to database`);
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Write property to database
|
|
1690
|
+
* @param propertyFileId - The property file ID (schema_file_id)
|
|
1691
|
+
* @param data - Property data including modelId, name, dataType, and other property fields
|
|
1692
|
+
* @param isEdited - Optional flag indicating if property has been edited locally (default: false)
|
|
1693
|
+
*/
|
|
1694
|
+
async function writePropertyToDb(propertyFileId, data, isEdited = false) {
|
|
1695
|
+
const db = BaseDb.getAppDb();
|
|
1696
|
+
if (!db)
|
|
1697
|
+
throw new Error('Database not available');
|
|
1698
|
+
// Find existing property by schemaFileId first (preferred for uniqueness)
|
|
1699
|
+
let existingProperties = await db
|
|
1700
|
+
.select()
|
|
1701
|
+
.from(properties)
|
|
1702
|
+
.where(eq(properties.schemaFileId, propertyFileId))
|
|
1703
|
+
.limit(1);
|
|
1704
|
+
// Fallback: find by modelId and name if not found by schemaFileId
|
|
1705
|
+
if (existingProperties.length === 0) {
|
|
1706
|
+
existingProperties = await db
|
|
1707
|
+
.select()
|
|
1708
|
+
.from(properties)
|
|
1709
|
+
.where(and(eq(properties.name, data.name), eq(properties.modelId, data.modelId)))
|
|
1710
|
+
.limit(1);
|
|
1711
|
+
}
|
|
1712
|
+
// Prepare property data
|
|
1713
|
+
const propertyData = {
|
|
1714
|
+
name: data.name,
|
|
1715
|
+
modelId: data.modelId,
|
|
1716
|
+
dataType: data.dataType || '',
|
|
1717
|
+
schemaFileId: propertyFileId,
|
|
1718
|
+
isEdited: isEdited, // Persist isEdited flag
|
|
1719
|
+
};
|
|
1720
|
+
// Handle ref property - create ref model if needed
|
|
1721
|
+
// Check refModelName first, then ref (for backwards compatibility with schema files)
|
|
1722
|
+
const refModelName = data.refModelName || data.ref;
|
|
1723
|
+
if (refModelName) {
|
|
1724
|
+
const refModel = await createOrUpdate(db, models, {
|
|
1725
|
+
name: refModelName,
|
|
1726
|
+
});
|
|
1727
|
+
propertyData.refModelId = refModel.id;
|
|
1728
|
+
}
|
|
1729
|
+
else if (data.refModelId) {
|
|
1730
|
+
propertyData.refModelId = data.refModelId;
|
|
1731
|
+
}
|
|
1732
|
+
else {
|
|
1733
|
+
// If it's not a Relation type, ensure refModelId is null
|
|
1734
|
+
propertyData.refModelId = null;
|
|
1735
|
+
}
|
|
1736
|
+
if (data.refValueType) {
|
|
1737
|
+
propertyData.refValueType = data.refValueType;
|
|
1738
|
+
}
|
|
1739
|
+
else {
|
|
1740
|
+
// If refValueType is not set, ensure it's null
|
|
1741
|
+
propertyData.refValueType = null;
|
|
1742
|
+
}
|
|
1743
|
+
propertyData.required = data.required ?? false;
|
|
1744
|
+
// Note: Additional property fields like storageType, localStorageDir, filenameSuffix
|
|
1745
|
+
// are not stored in the properties table but may be in the schema JSON
|
|
1746
|
+
if (existingProperties.length > 0) {
|
|
1747
|
+
// Property exists, update it with new values.
|
|
1748
|
+
// Use existing row id for WHERE (not name+modelId) so renames work: the row
|
|
1749
|
+
// may have the old name but we're updating to the new one.
|
|
1750
|
+
const existing = existingProperties[0];
|
|
1751
|
+
await db
|
|
1752
|
+
.update(properties)
|
|
1753
|
+
.set(propertyData)
|
|
1754
|
+
.where(eq(properties.id, existing.id));
|
|
1755
|
+
logger(`Updated property ${data.name} (${propertyFileId}) in database`);
|
|
1756
|
+
}
|
|
1757
|
+
else {
|
|
1758
|
+
// Property doesn't exist, create it
|
|
1759
|
+
// Double-check before insert to avoid race conditions
|
|
1760
|
+
const doubleCheck = await db
|
|
1761
|
+
.select()
|
|
1762
|
+
.from(properties)
|
|
1763
|
+
.where(eq(properties.schemaFileId, propertyFileId))
|
|
1764
|
+
.limit(1);
|
|
1765
|
+
if (doubleCheck.length > 0) {
|
|
1766
|
+
// Another process created it, update it instead
|
|
1767
|
+
await db
|
|
1768
|
+
.update(properties)
|
|
1769
|
+
.set(propertyData)
|
|
1770
|
+
.where(eq(properties.id, doubleCheck[0].id));
|
|
1771
|
+
logger(`Property with schemaFileId "${propertyFileId}" was created by another process, updated existing record`);
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
try {
|
|
1775
|
+
await db.insert(properties).values(propertyData);
|
|
1776
|
+
logger(`Created property ${data.name} (${propertyFileId}) in database`);
|
|
1777
|
+
}
|
|
1778
|
+
catch (error) {
|
|
1779
|
+
// Handle unique constraint violation
|
|
1780
|
+
if (isSqliteUniqueConstraintError(error)) {
|
|
1781
|
+
logger(`Unique constraint violation for property schemaFileId "${propertyFileId}", attempting to find existing property`);
|
|
1782
|
+
const existing = await db
|
|
1783
|
+
.select()
|
|
1784
|
+
.from(properties)
|
|
1785
|
+
.where(eq(properties.schemaFileId, propertyFileId))
|
|
1786
|
+
.limit(1);
|
|
1787
|
+
if (existing.length > 0) {
|
|
1788
|
+
await db
|
|
1789
|
+
.update(properties)
|
|
1790
|
+
.set(propertyData)
|
|
1791
|
+
.where(eq(properties.id, existing[0].id));
|
|
1792
|
+
logger(`Found existing property with schemaFileId "${propertyFileId}" (id: ${existing[0].id}) after constraint violation, updated it`);
|
|
1793
|
+
}
|
|
1794
|
+
else {
|
|
1795
|
+
throw new Error(`Failed to create or find property "${data.name}" with schemaFileId "${propertyFileId}": ${error.message}`);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
else {
|
|
1799
|
+
throw error;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Get schema database ID from schema name or schemaFileId
|
|
1807
|
+
* @param schemaNameOrFileId - Schema name (string) or schemaFileId (string)
|
|
1808
|
+
* @returns Schema database ID
|
|
1809
|
+
* @throws Error if schema not found
|
|
1810
|
+
*/
|
|
1811
|
+
async function getSchemaId(schemaNameOrFileId) {
|
|
1812
|
+
const db = BaseDb.getAppDb();
|
|
1813
|
+
if (!db) {
|
|
1814
|
+
throw new Error('Database not available');
|
|
1815
|
+
}
|
|
1816
|
+
const _mod_6 = await import('./BasePathResolver-BDgc7c4T.js');
|
|
1817
|
+
const _ns_6 = _mod_6.S;
|
|
1818
|
+
const schemaSchemaMod = _ns_6;
|
|
1819
|
+
const { schemas: schemasTable } = schemaSchemaMod;
|
|
1820
|
+
const drizzleMod = await import('drizzle-orm');
|
|
1821
|
+
const { eq, desc } = drizzleMod;
|
|
1822
|
+
// Try to find by schemaFileId first (more reliable)
|
|
1823
|
+
let records = await db
|
|
1824
|
+
.select()
|
|
1825
|
+
.from(schemasTable)
|
|
1826
|
+
.where(eq(schemasTable.schemaFileId, schemaNameOrFileId))
|
|
1827
|
+
.orderBy(desc(schemasTable.version))
|
|
1828
|
+
.limit(1);
|
|
1829
|
+
// If not found by schemaFileId, try by name
|
|
1830
|
+
if (records.length === 0) {
|
|
1831
|
+
records = await db
|
|
1832
|
+
.select()
|
|
1833
|
+
.from(schemasTable)
|
|
1834
|
+
.where(eq(schemasTable.name, schemaNameOrFileId))
|
|
1835
|
+
.orderBy(desc(schemasTable.version))
|
|
1836
|
+
.limit(1);
|
|
1837
|
+
}
|
|
1838
|
+
if (records.length === 0) {
|
|
1839
|
+
throw new Error(`Schema "${schemaNameOrFileId}" not found in database`);
|
|
1840
|
+
}
|
|
1841
|
+
return records[0].id;
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Get schema database ID from schemaFileId
|
|
1845
|
+
* @param schemaFileId - The schema file ID
|
|
1846
|
+
* @returns Schema database ID
|
|
1847
|
+
* @throws Error if schema not found
|
|
1848
|
+
*/
|
|
1849
|
+
async function getSchemaIdByFileId(schemaFileId) {
|
|
1850
|
+
const db = BaseDb.getAppDb();
|
|
1851
|
+
if (!db) {
|
|
1852
|
+
throw new Error('Database not available');
|
|
1853
|
+
}
|
|
1854
|
+
const _mod_7 = await import('./BasePathResolver-BDgc7c4T.js');
|
|
1855
|
+
const _ns_7 = _mod_7.S;
|
|
1856
|
+
const schemaSchemaMod = _ns_7;
|
|
1857
|
+
const { schemas: schemasTable } = schemaSchemaMod;
|
|
1858
|
+
const drizzleMod = await import('drizzle-orm');
|
|
1859
|
+
const { eq, desc } = drizzleMod;
|
|
1860
|
+
const records = await db
|
|
1861
|
+
.select()
|
|
1862
|
+
.from(schemasTable)
|
|
1863
|
+
.where(eq(schemasTable.schemaFileId, schemaFileId))
|
|
1864
|
+
.orderBy(desc(schemasTable.version))
|
|
1865
|
+
.limit(1);
|
|
1866
|
+
if (records.length === 0) {
|
|
1867
|
+
throw new Error(`Schema with file ID "${schemaFileId}" not found in database`);
|
|
1868
|
+
}
|
|
1869
|
+
return records[0].id;
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Get model database ID from model name or modelFileId
|
|
1873
|
+
* @param modelNameOrFileId - Model name (string) or modelFileId (string)
|
|
1874
|
+
* @param schemaNameOrId - Optional schema name or ID to narrow search
|
|
1875
|
+
* @returns Model database ID
|
|
1876
|
+
* @throws Error if model not found
|
|
1877
|
+
*/
|
|
1878
|
+
async function getModelId(modelNameOrFileId, schemaNameOrId) {
|
|
1879
|
+
const db = BaseDb.getAppDb();
|
|
1880
|
+
if (!db) {
|
|
1881
|
+
throw new Error('Database not available');
|
|
1882
|
+
}
|
|
1883
|
+
const _mod_8 = await import('./BasePathResolver-BDgc7c4T.js');
|
|
1884
|
+
const _ns_8 = _mod_8.M;
|
|
1885
|
+
const modelSchemaMod = _ns_8;
|
|
1886
|
+
const { models: modelsTable } = modelSchemaMod;
|
|
1887
|
+
const drizzleMod = await import('drizzle-orm');
|
|
1888
|
+
const { eq, and, or } = drizzleMod;
|
|
1889
|
+
// Try to find by modelFileId first (more reliable)
|
|
1890
|
+
let records = await db
|
|
1891
|
+
.select()
|
|
1892
|
+
.from(modelsTable)
|
|
1893
|
+
.where(eq(modelsTable.schemaFileId, modelNameOrFileId))
|
|
1894
|
+
.limit(1);
|
|
1895
|
+
// If not found by modelFileId, try by name
|
|
1896
|
+
if (records.length === 0) {
|
|
1897
|
+
[eq(modelsTable.name, modelNameOrFileId)];
|
|
1898
|
+
// If schema is provided, narrow the search
|
|
1899
|
+
if (schemaNameOrId !== undefined) {
|
|
1900
|
+
const _mod_9 = await import('./BasePathResolver-BDgc7c4T.js');
|
|
1901
|
+
const _ns_9 = _mod_9.x;
|
|
1902
|
+
const modelSchemaSchemaMod = _ns_9;
|
|
1903
|
+
const { modelSchemas } = modelSchemaSchemaMod;
|
|
1904
|
+
const _mod_10 = await import('./BasePathResolver-BDgc7c4T.js');
|
|
1905
|
+
const _ns_10 = _mod_10.S;
|
|
1906
|
+
const schemaSchemaMod = _ns_10;
|
|
1907
|
+
const { schemas: schemasTable } = schemaSchemaMod;
|
|
1908
|
+
if (typeof schemaNameOrId === 'number') {
|
|
1909
|
+
// schemaNameOrId is schemaId
|
|
1910
|
+
records = await db
|
|
1911
|
+
.select({ id: modelsTable.id })
|
|
1912
|
+
.from(modelsTable)
|
|
1913
|
+
.innerJoin(modelSchemas, eq(modelsTable.id, modelSchemas.modelId))
|
|
1914
|
+
.where(and(eq(modelsTable.name, modelNameOrFileId), eq(modelSchemas.schemaId, schemaNameOrId)))
|
|
1915
|
+
.limit(1);
|
|
1916
|
+
}
|
|
1917
|
+
else {
|
|
1918
|
+
// schemaNameOrId is schemaName
|
|
1919
|
+
records = await db
|
|
1920
|
+
.select({ id: modelsTable.id })
|
|
1921
|
+
.from(modelsTable)
|
|
1922
|
+
.innerJoin(modelSchemas, eq(modelsTable.id, modelSchemas.modelId))
|
|
1923
|
+
.innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
|
|
1924
|
+
.where(and(eq(modelsTable.name, modelNameOrFileId), eq(schemasTable.name, schemaNameOrId)))
|
|
1925
|
+
.limit(1);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
else {
|
|
1929
|
+
// No schema filter, just search by name
|
|
1930
|
+
records = await db
|
|
1931
|
+
.select()
|
|
1932
|
+
.from(modelsTable)
|
|
1933
|
+
.where(eq(modelsTable.name, modelNameOrFileId))
|
|
1934
|
+
.limit(1);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
if (records.length === 0) {
|
|
1938
|
+
const schemaInfo = schemaNameOrId ? ` in schema "${schemaNameOrId}"` : '';
|
|
1939
|
+
throw new Error(`Model "${modelNameOrFileId}"${schemaInfo} not found in database`);
|
|
1940
|
+
}
|
|
1941
|
+
return records[0].id;
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Get model database ID from modelFileId
|
|
1945
|
+
* @param modelFileId - The model file ID (schema_file_id)
|
|
1946
|
+
* @returns Model database ID
|
|
1947
|
+
* @throws Error if model not found
|
|
1948
|
+
*/
|
|
1949
|
+
async function getModelIdByFileId(modelFileId) {
|
|
1950
|
+
const db = BaseDb.getAppDb();
|
|
1951
|
+
if (!db) {
|
|
1952
|
+
throw new Error('Database not available');
|
|
1953
|
+
}
|
|
1954
|
+
const _mod_11 = await import('./BasePathResolver-BDgc7c4T.js');
|
|
1955
|
+
const _ns_11 = _mod_11.M;
|
|
1956
|
+
const modelSchemaMod = _ns_11;
|
|
1957
|
+
const { models: modelsTable } = modelSchemaMod;
|
|
1958
|
+
const drizzleMod = await import('drizzle-orm');
|
|
1959
|
+
const { eq } = drizzleMod;
|
|
1960
|
+
const records = await db
|
|
1961
|
+
.select()
|
|
1962
|
+
.from(modelsTable)
|
|
1963
|
+
.where(eq(modelsTable.schemaFileId, modelFileId))
|
|
1964
|
+
.limit(1);
|
|
1965
|
+
if (records.length === 0) {
|
|
1966
|
+
throw new Error(`Model with file ID "${modelFileId}" not found in database`);
|
|
1967
|
+
}
|
|
1968
|
+
return records[0].id;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
export { addModelsToDb, addSchemaToDb, createOrUpdate, escapeSqliteString, getAddressesForItemsFilter, getAddressesFromDb, getAddressesFromDbOptional, getAllAddressesFromDb, getModelId, getModelIdByFileId, getModelNameByModelId, getObjectForRow, getOwnedAddressesFromDb, getPropertyIdForModelAndName, getPropertyModelNameAndDataType, getSchemaId, getSchemaIdByFileId, getSqlResultObject, getWatchedAddressesFromDb, loadModelsFromDbForSchema, migrateMetadataForPropertyRename, renameModelInDb, savePropertyToDb, writeModelToDb, writePropertyToDb };
|
|
1972
|
+
//# sourceMappingURL=db-Cvh84NY7.js.map
|