@seedprotocol/sdk 0.7.3-next.613.g79a8dfd → 0.7.4-next.711.gce2b766

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.
Files changed (239) hide show
  1. package/dist/{db-CQt-gcAl.js → BasePathResolver-Cm4apKRb.js} +2041 -127
  2. package/dist/BasePathResolver-Cm4apKRb.js.map +1 -0
  3. package/dist/{ClientManager-DDA9956Y.js → ClientManager-i3nJASkg.js} +664 -277
  4. package/dist/ClientManager-i3nJASkg.js.map +1 -0
  5. package/dist/{Item-D4FigtYS.js → Item-DBFchXpC.js} +495 -219
  6. package/dist/Item-DBFchXpC.js.map +1 -0
  7. package/dist/{ModelProperty-BDt6AU2h.js → ModelProperty-BS49g01Y.js} +167 -47
  8. package/dist/ModelProperty-BS49g01Y.js.map +1 -0
  9. package/dist/{Schema-B8xqLWkL.js → Schema-CpMNEnUO.js} +53 -42
  10. package/dist/Schema-CpMNEnUO.js.map +1 -0
  11. package/dist/{SchemaValidationService-BPxkfDLU.js → SchemaValidationService-DieV4KL-.js} +13 -14
  12. package/dist/{SchemaValidationService-BPxkfDLU.js.map → SchemaValidationService-DieV4KL-.js.map} +1 -1
  13. package/dist/{backfillMetadataPropertyIds-DuTPcv8t.js → backfillMetadataPropertyIds-DSoOoTnX.js} +14 -7
  14. package/dist/{backfillMetadataPropertyIds-DuTPcv8t.js.map → backfillMetadataPropertyIds-DSoOoTnX.js.map} +1 -1
  15. package/dist/{configurePlatform-YcOiDQBZ.js → configurePlatform-D0lQGAMQ.js} +8 -7
  16. package/dist/{configurePlatform-YcOiDQBZ.js.map → configurePlatform-D0lQGAMQ.js.map} +1 -1
  17. package/dist/detachRowsBeforeDelete-S0q-DJPT.js +50 -0
  18. package/dist/detachRowsBeforeDelete-S0q-DJPT.js.map +1 -0
  19. package/dist/{eas-CpOD74Ke.js → eas-6pjU-OuX.js} +9 -8
  20. package/dist/eas-6pjU-OuX.js.map +1 -0
  21. package/dist/{getItem-B_OoFkd7.js → getItem-CxBNZ-WL.js} +13 -11
  22. package/dist/{getItem-B_OoFkd7.js.map → getItem-CxBNZ-WL.js.map} +1 -1
  23. package/dist/{getPublishPayload-Xl3sRkR-.js → getPublishPayload-CTCvRUoU.js} +50 -18
  24. package/dist/getPublishPayload-CTCvRUoU.js.map +1 -0
  25. package/dist/getPublishUploads-SFHCbf_y.js +648 -0
  26. package/dist/getPublishUploads-SFHCbf_y.js.map +1 -0
  27. package/dist/{index-Xl1_4Lmb.js → index-8O3Uow-Y.js} +125 -57
  28. package/dist/index-8O3Uow-Y.js.map +1 -0
  29. package/dist/{index-CSOQFAOn.js → index-AVFTVp1m.js} +2 -2
  30. package/dist/index-AVFTVp1m.js.map +1 -0
  31. package/dist/index-Cvt5UBz4.js +30 -0
  32. package/dist/index-Cvt5UBz4.js.map +1 -0
  33. package/dist/{index.browser-CjTe0vt1.js → index.browser-DBhxRoe8.js} +554 -165
  34. package/dist/index.browser-DBhxRoe8.js.map +1 -0
  35. package/dist/{index.node-Cfa7npS6.js → index.node-CJhlcNHM.js} +25 -13
  36. package/dist/index.node-CJhlcNHM.js.map +1 -0
  37. package/dist/main.js +30 -47
  38. package/dist/main.js.map +1 -1
  39. package/dist/mimeTypes-DHAHC4eZ.js +204 -0
  40. package/dist/mimeTypes-DHAHC4eZ.js.map +1 -0
  41. package/dist/node.js +19 -21
  42. package/dist/node.js.map +1 -1
  43. package/dist/platform.browser.js +13 -11
  44. package/dist/platform.browser.js.map +1 -1
  45. package/dist/platform.node.js +18 -5
  46. package/dist/platform.node.js.map +1 -1
  47. package/dist/{property-BrXkY1Rg.js → property-DwPD283A.js} +14 -12
  48. package/dist/property-DwPD283A.js.map +1 -0
  49. package/dist/{publishDraftGraph-X0Wr9txE.js → publishDraftGraph-BOn3_Occ.js} +6 -9
  50. package/dist/{publishDraftGraph-X0Wr9txE.js.map → publishDraftGraph-BOn3_Occ.js.map} +1 -1
  51. package/dist/{resetAmbiguousLegacyItemData-Ci6PM_vC.js → resetAmbiguousLegacyItemData-BkYmBeJc.js} +14 -3
  52. package/dist/{resetAmbiguousLegacyItemData-Ci6PM_vC.js.map → resetAmbiguousLegacyItemData-BkYmBeJc.js.map} +1 -1
  53. package/dist/{resolveModelForSyncedSeed-DFVdnYq8.js → resolveModelForSyncedSeed-BL24nLa7.js} +2 -2
  54. package/dist/{resolveModelForSyncedSeed-DFVdnYq8.js.map → resolveModelForSyncedSeed-BL24nLa7.js.map} +1 -1
  55. package/dist/src/Item/Item.d.ts +21 -3
  56. package/dist/src/Item/Item.d.ts.map +1 -1
  57. package/dist/src/Item/service/actors/runPublish.d.ts +11 -2
  58. package/dist/src/Item/service/actors/runPublish.d.ts.map +1 -1
  59. package/dist/src/Item/service/itemMachineSingle.d.ts +12 -2
  60. package/dist/src/Item/service/itemMachineSingle.d.ts.map +1 -1
  61. package/dist/src/ItemProperty/ItemProperty.d.ts +5 -3
  62. package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
  63. package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts.map +1 -1
  64. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts.map +1 -1
  65. package/dist/src/ItemProperty/service/propertyMachine.d.ts.map +1 -1
  66. package/dist/src/Model/Model.d.ts +10 -4
  67. package/dist/src/Model/Model.d.ts.map +1 -1
  68. package/dist/src/Model/index.d.ts +2 -2
  69. package/dist/src/ModelProperty/ModelProperty.d.ts +10 -2
  70. package/dist/src/ModelProperty/ModelProperty.d.ts.map +1 -1
  71. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +3 -0
  72. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts.map +1 -1
  73. package/dist/src/Schema/Schema.d.ts +2 -2
  74. package/dist/src/Schema/Schema.d.ts.map +1 -1
  75. package/dist/src/Schema/index.d.ts +0 -1
  76. package/dist/src/Schema/index.d.ts.map +1 -1
  77. package/dist/src/browser/helpers/FileManager.d.ts +4 -1
  78. package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
  79. package/dist/src/browser/helpers/tolerantWebAccess.d.ts +79 -4
  80. package/dist/src/browser/helpers/tolerantWebAccess.d.ts.map +1 -1
  81. package/dist/src/browser/workers/FileDownloader.d.ts +19 -4
  82. package/dist/src/browser/workers/FileDownloader.d.ts.map +1 -1
  83. package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
  84. package/dist/src/client/ClientManager.d.ts +30 -11
  85. package/dist/src/client/ClientManager.d.ts.map +1 -1
  86. package/dist/src/client/actors/platformClassesInit.d.ts +6 -0
  87. package/dist/src/client/actors/platformClassesInit.d.ts.map +1 -1
  88. package/dist/src/client/actors/processSchemaFiles.d.ts.map +1 -1
  89. package/dist/src/client/actors/saveConfig.d.ts +13 -6
  90. package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
  91. package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
  92. package/dist/src/db/migrateLegacyItemStorageRows.d.ts +57 -0
  93. package/dist/src/db/migrateLegacyItemStorageRows.d.ts.map +1 -0
  94. package/dist/src/db/read/getCompositeChildPaths.d.ts +13 -0
  95. package/dist/src/db/read/getCompositeChildPaths.d.ts.map +1 -0
  96. package/dist/src/db/read/getItems.d.ts.map +1 -1
  97. package/dist/src/db/read/getPublishPayload.d.ts +1 -1
  98. package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
  99. package/dist/src/db/read/getPublishUploads.d.ts +32 -3
  100. package/dist/src/db/read/getPublishUploads.d.ts.map +1 -1
  101. package/dist/src/db/read/summarizePublishWork.d.ts.map +1 -1
  102. package/dist/src/db/write/claimUnpublishedDrafts.d.ts +2 -0
  103. package/dist/src/db/write/claimUnpublishedDrafts.d.ts.map +1 -1
  104. package/dist/src/db/write/convertVerbatimItemStorageRow.d.ts +44 -0
  105. package/dist/src/db/write/convertVerbatimItemStorageRow.d.ts.map +1 -0
  106. package/dist/src/db/write/createNewItem.d.ts.map +1 -1
  107. package/dist/src/eventBus.d.ts +5 -0
  108. package/dist/src/eventBus.d.ts.map +1 -1
  109. package/dist/src/events/files/download.d.ts.map +1 -1
  110. package/dist/src/helpers/FileManager/BaseFileManager.d.ts +3 -3
  111. package/dist/src/helpers/FileManager/BaseFileManager.d.ts.map +1 -1
  112. package/dist/src/helpers/FileManager/IFileManager.d.ts +25 -1
  113. package/dist/src/helpers/FileManager/IFileManager.d.ts.map +1 -1
  114. package/dist/src/helpers/arweaveContentTags.d.ts +33 -0
  115. package/dist/src/helpers/arweaveContentTags.d.ts.map +1 -0
  116. package/dist/src/helpers/cachedPropertyRefresh.d.ts +12 -0
  117. package/dist/src/helpers/cachedPropertyRefresh.d.ts.map +1 -0
  118. package/dist/src/helpers/compositeUpload.d.ts +28 -0
  119. package/dist/src/helpers/compositeUpload.d.ts.map +1 -0
  120. package/dist/src/helpers/db.d.ts +3 -0
  121. package/dist/src/helpers/db.d.ts.map +1 -1
  122. package/dist/src/helpers/easSdk.d.ts +16 -0
  123. package/dist/src/helpers/easSdk.d.ts.map +1 -0
  124. package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts +16 -0
  125. package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts.map +1 -0
  126. package/dist/src/helpers/entity/entityDestroy.d.ts +2 -0
  127. package/dist/src/helpers/entity/entityDestroy.d.ts.map +1 -1
  128. package/dist/src/helpers/itemStorageFile.d.ts +57 -0
  129. package/dist/src/helpers/itemStorageFile.d.ts.map +1 -0
  130. package/dist/src/helpers/mapWithConcurrency.d.ts +7 -0
  131. package/dist/src/helpers/mapWithConcurrency.d.ts.map +1 -0
  132. package/dist/src/helpers/mimeTypes.d.ts +43 -0
  133. package/dist/src/helpers/mimeTypes.d.ts.map +1 -0
  134. package/dist/src/helpers/ownership.d.ts +28 -4
  135. package/dist/src/helpers/ownership.d.ts.map +1 -1
  136. package/dist/src/helpers/property/index.d.ts +37 -51
  137. package/dist/src/helpers/property/index.d.ts.map +1 -1
  138. package/dist/src/helpers/publishConfig.d.ts +27 -8
  139. package/dist/src/helpers/publishConfig.d.ts.map +1 -1
  140. package/dist/src/helpers/storageDir.d.ts +16 -0
  141. package/dist/src/helpers/storageDir.d.ts.map +1 -0
  142. package/dist/src/helpers/tabCoordinator.d.ts +5 -1
  143. package/dist/src/helpers/tabCoordinator.d.ts.map +1 -1
  144. package/dist/src/helpers/tabEvents.d.ts +23 -2
  145. package/dist/src/helpers/tabEvents.d.ts.map +1 -1
  146. package/dist/src/helpers/updateSchema.d.ts +5 -0
  147. package/dist/src/helpers/updateSchema.d.ts.map +1 -1
  148. package/dist/src/helpers/waitForEntityIdle.d.ts +5 -0
  149. package/dist/src/helpers/waitForEntityIdle.d.ts.map +1 -1
  150. package/dist/src/imports/json.d.ts +9 -0
  151. package/dist/src/imports/json.d.ts.map +1 -1
  152. package/dist/src/index.d.ts +11 -7
  153. package/dist/src/index.d.ts.map +1 -1
  154. package/dist/src/interfaces/IItem.d.ts +7 -1
  155. package/dist/src/interfaces/IItem.d.ts.map +1 -1
  156. package/dist/src/node/db/Db.d.ts.map +1 -1
  157. package/dist/src/node/helpers/PathResolver.d.ts.map +1 -1
  158. package/dist/src/node/index.d.ts +8 -1
  159. package/dist/src/node/index.d.ts.map +1 -1
  160. package/dist/src/platform/index.browser.d.ts +2 -0
  161. package/dist/src/platform/index.browser.d.ts.map +1 -1
  162. package/dist/src/platform/index.node.d.ts +2 -0
  163. package/dist/src/platform/index.node.d.ts.map +1 -1
  164. package/dist/src/types/fileManager.d.ts +3 -14
  165. package/dist/src/types/fileManager.d.ts.map +1 -1
  166. package/dist/src/types/index.d.ts +12 -1
  167. package/dist/src/types/index.d.ts.map +1 -1
  168. package/dist/src/types/machines.d.ts +2 -0
  169. package/dist/src/types/machines.d.ts.map +1 -1
  170. package/dist/src/types/property.d.ts +8 -0
  171. package/dist/src/types/property.d.ts.map +1 -1
  172. package/dist/src/types/publish.d.ts +14 -0
  173. package/dist/src/types/publish.d.ts.map +1 -1
  174. package/dist/{syncDbWithEas-BU-jZ5bJ.js → syncDbWithEas-Bv2Fe38J.js} +17 -17
  175. package/dist/{syncDbWithEas-BU-jZ5bJ.js.map → syncDbWithEas-Bv2Fe38J.js.map} +1 -1
  176. package/dist/tabEvents-9s2WCYw-.js +335 -0
  177. package/dist/tabEvents-9s2WCYw-.js.map +1 -0
  178. package/dist/tolerantWebAccess-iUu80pAP.js +551 -0
  179. package/dist/tolerantWebAccess-iUu80pAP.js.map +1 -0
  180. package/dist/{updateSeedRevokedAt-iLpzae1i.js → updateSeedRevokedAt-zBfi931j.js} +3 -3
  181. package/dist/{updateSeedRevokedAt-iLpzae1i.js.map → updateSeedRevokedAt-zBfi931j.js.map} +1 -1
  182. package/package.json +11 -12
  183. package/src/node/easLodashRegister.js +4 -0
  184. package/dist/BasePathResolver-BdxCSIzn.js +0 -679
  185. package/dist/BasePathResolver-BdxCSIzn.js.map +0 -1
  186. package/dist/ClientManager-DDA9956Y.js.map +0 -1
  187. package/dist/Item-D4FigtYS.js.map +0 -1
  188. package/dist/ModelProperty-BDt6AU2h.js.map +0 -1
  189. package/dist/Schema-B8xqLWkL.js.map +0 -1
  190. package/dist/cjs/ModelProperty-B-cZm1yU.js +0 -1977
  191. package/dist/cjs/ModelProperty-B-cZm1yU.js.map +0 -1
  192. package/dist/cjs/Schema-STP972Xw.js +0 -5256
  193. package/dist/cjs/Schema-STP972Xw.js.map +0 -1
  194. package/dist/cjs/SchemaValidationService-CKNW1bKg.js +0 -778
  195. package/dist/cjs/SchemaValidationService-CKNW1bKg.js.map +0 -1
  196. package/dist/cjs/backfillMetadataPropertyIds-CP3y1skt.js +0 -89
  197. package/dist/cjs/backfillMetadataPropertyIds-CP3y1skt.js.map +0 -1
  198. package/dist/cjs/eas-CAnUC4wd.js +0 -138
  199. package/dist/cjs/eas-CAnUC4wd.js.map +0 -1
  200. package/dist/cjs/getItem-CWgb3e9G.js +0 -58
  201. package/dist/cjs/getItem-CWgb3e9G.js.map +0 -1
  202. package/dist/cjs/getPublishPayload-BsZWiJ03.js +0 -1603
  203. package/dist/cjs/getPublishPayload-BsZWiJ03.js.map +0 -1
  204. package/dist/cjs/getPublishUploads-BQr8aMVP.js +0 -495
  205. package/dist/cjs/getPublishUploads-BQr8aMVP.js.map +0 -1
  206. package/dist/cjs/index-nA5PxBSs.js +0 -70
  207. package/dist/cjs/index-nA5PxBSs.js.map +0 -1
  208. package/dist/cjs/index-usKaANAq.js +0 -22171
  209. package/dist/cjs/index-usKaANAq.js.map +0 -1
  210. package/dist/cjs/index.browser-BSf8HpnU.js +0 -22
  211. package/dist/cjs/index.browser-BSf8HpnU.js.map +0 -1
  212. package/dist/cjs/property-xMwPhQ5N.js +0 -327
  213. package/dist/cjs/property-xMwPhQ5N.js.map +0 -1
  214. package/dist/cjs/publishDraftGraph-BRWlR1KO.js +0 -403
  215. package/dist/cjs/publishDraftGraph-BRWlR1KO.js.map +0 -1
  216. package/dist/cjs/resetAmbiguousLegacyItemData-8nfkj9rz.js +0 -116
  217. package/dist/cjs/resetAmbiguousLegacyItemData-8nfkj9rz.js.map +0 -1
  218. package/dist/cjs/resolveModelForSyncedSeed-kYRuDWYw.js +0 -49
  219. package/dist/cjs/resolveModelForSyncedSeed-kYRuDWYw.js.map +0 -1
  220. package/dist/cjs/syncDbWithEas-DqRhV8o2.js +0 -886
  221. package/dist/cjs/syncDbWithEas-DqRhV8o2.js.map +0 -1
  222. package/dist/db-CQt-gcAl.js.map +0 -1
  223. package/dist/eas-CpOD74Ke.js.map +0 -1
  224. package/dist/getPublishPayload-Xl3sRkR-.js.map +0 -1
  225. package/dist/getPublishUploads-DHP01KgU.js +0 -484
  226. package/dist/getPublishUploads-DHP01KgU.js.map +0 -1
  227. package/dist/index-BGt4_oPO.js +0 -31
  228. package/dist/index-BGt4_oPO.js.map +0 -1
  229. package/dist/index-CSOQFAOn.js.map +0 -1
  230. package/dist/index-Xl1_4Lmb.js.map +0 -1
  231. package/dist/index.browser-CjTe0vt1.js.map +0 -1
  232. package/dist/index.node-Cfa7npS6.js.map +0 -1
  233. package/dist/main.cjs +0 -84
  234. package/dist/main.cjs.map +0 -1
  235. package/dist/property-BrXkY1Rg.js.map +0 -1
  236. package/dist/tabEvents-BZJMY5uK.js +0 -1344
  237. package/dist/tabEvents-BZJMY5uK.js.map +0 -1
  238. package/dist/tolerantWebAccess-BZ2RA_mb.js +0 -160
  239. package/dist/tolerantWebAccess-BZ2RA_mb.js.map +0 -1
@@ -1,1977 +0,0 @@
1
- 'use strict';
2
-
3
- var xstate = require('xstate');
4
- require('reflect-metadata');
5
- var main_cjs = require('./index-usKaANAq.js');
6
- require('pluralize');
7
- var drizzleOrm = require('drizzle-orm');
8
- var debug = require('debug');
9
- var Schema = require('./Schema-STP972Xw.js');
10
- require('immer');
11
- require('drizzle-orm/sqlite-core');
12
- require('@seedprotocol/arweave');
13
- require('@seedprotocol/eas');
14
- require('@sinclair/typebox');
15
- require('drizzle-orm/casing');
16
- require('lodash-es');
17
- require('nanoid');
18
- require('nanoid-dictionary');
19
- require('@seedprotocol/query');
20
- require('rxjs');
21
- require('@noble/hashes/sha3');
22
- require('@noble/hashes/utils');
23
- require('fs/promises');
24
- require('fs');
25
- require('path');
26
- require('url');
27
- require('node:url');
28
- require('@libsql/client');
29
- require('drizzle-orm/libsql');
30
- require('drizzle-orm/libsql/migrator');
31
- require('js-yaml');
32
- require('child_process');
33
- require('@seedprotocol/arweave/node');
34
- require('@seedprotocol/eas/node');
35
- require('./SchemaValidationService-CKNW1bKg.js');
36
- require('@sinclair/typebox/value');
37
-
38
- const logger$3 = debug('seedSdk:modelProperty:actors:saveToSchema');
39
- /**
40
- * Get schema name from model
41
- * This function finds which schema contains the given model
42
- * Exported so it can be reused by ModelProperty for setting schema name
43
- */
44
- async function getSchemaNameFromModel(modelName) {
45
- // Get the latest schema files and find which one contains this model
46
- const schemaHelpersMod = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.schema$1; });
47
- const { listLatestSchemaFiles } = schemaHelpersMod;
48
- const latestSchemas = await listLatestSchemaFiles();
49
- for (const schema of latestSchemas) {
50
- try {
51
- const fileManagerMod = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.BaseFileManager$1; });
52
- const { BaseFileManager } = fileManagerMod;
53
- const content = await BaseFileManager.readFileAsString(schema.filePath);
54
- const schemaFile = JSON.parse(content);
55
- if (schemaFile.models && schemaFile.models[modelName]) {
56
- return schema.name;
57
- }
58
- }
59
- catch (error) {
60
- logger$3('Error reading schema file:', error);
61
- continue;
62
- }
63
- }
64
- return undefined;
65
- }
66
- const saveToSchema = xstate.fromCallback(({ sendBack, input: { context }, self }) => {
67
- const _saveToSchema = async () => {
68
- // Use dynamic import to break circular dependency
69
- const validationServiceMod = await Promise.resolve().then(function () { return require('./SchemaValidationService-CKNW1bKg.js'); });
70
- const { SchemaValidationService } = validationServiceMod;
71
- const validationService = new SchemaValidationService();
72
- // Validate property structure before saving
73
- const validationResult = validationService.validatePropertyStructure(context);
74
- if (!validationResult.isValid) {
75
- throw new Error(`Property validation failed: ${validationResult.errors.map(e => e.message).join(', ')}`);
76
- }
77
- // Validate required fields
78
- if (!context.name) {
79
- throw new Error('Property name is required');
80
- }
81
- if (!context.dataType) {
82
- throw new Error('Data type is required');
83
- }
84
- if (!context.modelName) {
85
- throw new Error(`Model not found for modelId: ${context.modelId}`);
86
- }
87
- // Persist to database only - schema file is updated only when user calls Schema.saveNewVersion()
88
- const dbMod = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.db; });
89
- const { savePropertyToDb } = dbMod;
90
- await savePropertyToDb(context);
91
- // Mark schema as draft so user knows to save
92
- const schemaName = context._schemaName ?? (await getSchemaNameFromModel(context.modelName));
93
- if (schemaName) {
94
- const schemaMod = await Promise.resolve().then(function () { return require('./Schema-STP972Xw.js'); });
95
- const { Schema } = schemaMod;
96
- // Stopped meanwhile (unloaded, or evicted with its schema): don't load the schema again
97
- if (main_cjs.isActorStopped(self))
98
- return;
99
- const schema = Schema.create(schemaName, { waitForReady: false });
100
- schema.getService().send({
101
- type: 'markAsDraft',
102
- propertyKey: `${context.modelName}:${context.name}`,
103
- });
104
- }
105
- logger$3(`Successfully saved property ${context.modelName}:${context.name} to database`);
106
- };
107
- _saveToSchema().then(() => {
108
- sendBack({ type: 'saveToSchemaSuccess' });
109
- }).catch((error) => {
110
- logger$3('Error saving to schema:', error);
111
- sendBack({ type: 'saveToSchemaError', error });
112
- });
113
- return () => {
114
- // Cleanup function (optional)
115
- };
116
- });
117
-
118
- const logger$2 = debug('seedSdk:modelProperty:actors:compareAndMarkDraft');
119
- const compareAndMarkDraft = xstate.fromCallback(({ sendBack, input: { context }, self }) => {
120
- const _compareAndMarkDraft = async () => {
121
- // Fill modelName/dataType from _originalValues when missing, then from DB by schemaFileId
122
- let fullContext = {
123
- ...context,
124
- modelName: context.modelName ?? context._originalValues?.modelName,
125
- dataType: context.dataType ?? context._originalValues?.dataType,
126
- };
127
- const schemaFileIdForResolve = fullContext._propertyFileId || (typeof fullContext.id === 'string' ? fullContext.id : undefined);
128
- if (schemaFileIdForResolve && (fullContext.modelName === undefined || fullContext.dataType === undefined)) {
129
- try {
130
- const dbMod = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.db; });
131
- const { getPropertyModelNameAndDataType, getModelNameByModelId } = dbMod;
132
- let fromDb;
133
- for (let attempt = 0; attempt < 6; attempt++) {
134
- fromDb = await getPropertyModelNameAndDataType(schemaFileIdForResolve);
135
- if (fromDb)
136
- break;
137
- if (attempt < 5)
138
- await new Promise((r) => setTimeout(r, 40));
139
- }
140
- if (fromDb) {
141
- fullContext = {
142
- ...fullContext,
143
- modelName: fullContext.modelName ?? fromDb.modelName,
144
- dataType: fullContext.dataType ?? fromDb.dataType,
145
- };
146
- }
147
- if (fullContext.modelName === undefined && schemaFileIdForResolve) {
148
- const mod = await Promise.resolve().then(function () { return ModelProperty$1; });
149
- const ModelProperty = mod?.ModelProperty ?? mod?.default;
150
- const pendingModelId = ModelProperty?.getPendingModelId?.(schemaFileIdForResolve);
151
- if (pendingModelId != null) {
152
- const modelName = await getModelNameByModelId(pendingModelId);
153
- if (modelName) {
154
- fullContext = { ...fullContext, modelName };
155
- }
156
- }
157
- }
158
- }
159
- catch {
160
- // ignore
161
- }
162
- }
163
- const schemaFileId = fullContext._propertyFileId || (typeof fullContext.id === 'string' ? fullContext.id : undefined);
164
- // If _originalValues is not set, we still need to save to database if the property exists
165
- // This handles the case where the name is changed before _originalValues is initialized
166
- if (!fullContext._originalValues) {
167
- logger$2('No original values to compare against');
168
- logger$2(`[compareAndMarkDraft] Context: modelName=${fullContext.modelName}, name=${fullContext.name}, id=${fullContext.id}, _propertyFileId=${fullContext._propertyFileId}`);
169
- // If we have a name and modelName, try to save to database anyway
170
- // This ensures name changes are persisted even if _originalValues isn't initialized yet
171
- // We need either schemaFileId (id or _propertyFileId) to find the property in the database
172
- if (fullContext.modelName && fullContext.name && schemaFileId) {
173
- logger$2(`[compareAndMarkDraft] _originalValues not set, but saving to database anyway for property ${context.modelName}:${context.name} (schemaFileId: ${schemaFileId})`);
174
- try {
175
- const dbMod = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.db; });
176
- const { savePropertyToDb } = dbMod;
177
- // Ensure _propertyFileId is set for savePropertyToDb to find the property
178
- const contextWithFileId = {
179
- ...fullContext,
180
- _propertyFileId: schemaFileId,
181
- };
182
- await savePropertyToDb(contextWithFileId);
183
- logger$2(`[compareAndMarkDraft] Successfully saved property ${fullContext.modelName}:${fullContext.name} to database (no _originalValues)`);
184
- }
185
- catch (error) {
186
- logger$2(`[compareAndMarkDraft] Error saving property to database (no _originalValues): ${error}`);
187
- // Don't throw - this is a best-effort save, but log the error for debugging
188
- console.error(`[compareAndMarkDraft] Failed to save property ${fullContext.modelName}:${fullContext.name}:`, error);
189
- }
190
- }
191
- else {
192
- logger$2(`[compareAndMarkDraft] Cannot save property ${fullContext.modelName}:${fullContext.name} - missing required fields (schemaFileId: ${schemaFileId})`);
193
- }
194
- return;
195
- }
196
- logger$2(`[compareAndMarkDraft] Comparing: context.name=${fullContext.name}, _originalValues.name=${fullContext._originalValues?.name}`);
197
- // Compare current values with original
198
- // Only compare property fields, not internal fields
199
- const propertyFields = ['name', 'dataType', 'ref', 'refModelName', 'refModelId', 'refValueType', 'storageType', 'localStorageDir', 'filenameSuffix', 'modelName', 'modelId', 'required'];
200
- const hasChanges = propertyFields.some(key => {
201
- const currentValue = fullContext[key];
202
- const originalValue = fullContext._originalValues?.[key];
203
- // Handle name changes specifically
204
- if (key === 'name') {
205
- const nameChanged = currentValue !== originalValue;
206
- if (nameChanged) {
207
- logger$2(`[compareAndMarkDraft] Name change detected: "${originalValue}" -> "${currentValue}"`);
208
- }
209
- return nameChanged;
210
- }
211
- // Handle ref fields - compare by name
212
- if (key === 'ref' || key === 'refModelName') {
213
- const currentRef = fullContext.refModelName || fullContext.ref;
214
- const originalRef = fullContext._originalValues?.refModelName || fullContext._originalValues?.ref;
215
- // Both undefined/null means no ref, so they're the same
216
- if (!currentRef && !originalRef)
217
- return false;
218
- return currentRef !== originalRef;
219
- }
220
- // For other fields, compare values (handling undefined/null)
221
- if (currentValue === undefined && originalValue === undefined)
222
- return false;
223
- if (currentValue === null && originalValue === null)
224
- return false;
225
- if (currentValue === undefined && originalValue === null)
226
- return false;
227
- if (currentValue === null && originalValue === undefined)
228
- return false;
229
- return currentValue !== originalValue;
230
- });
231
- if (hasChanges) {
232
- logger$2(`Property ${fullContext.modelName}:${fullContext.name} has changes, marking as edited`);
233
- logger$2(`[compareAndMarkDraft] Context when saving: id=${fullContext.id}, _propertyFileId=${fullContext._propertyFileId}, name=${fullContext.name}, _originalValues.name=${fullContext._originalValues?.name}`);
234
- // Use dynamic import to break circular dependency
235
- const dbMod = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.db; });
236
- const { savePropertyToDb } = dbMod;
237
- // Save to database (but not JSON file) - always save to DB when there are changes
238
- try {
239
- await savePropertyToDb(fullContext);
240
- logger$2(`[compareAndMarkDraft] Successfully saved property ${fullContext.modelName}:${fullContext.name} to database`);
241
- }
242
- catch (error) {
243
- logger$2(`[compareAndMarkDraft] Error saving property to database: ${error}`);
244
- throw error;
245
- }
246
- // Mark schema as draft if schema name is available
247
- if (fullContext._schemaName) {
248
- // Get the Schema instance and mark it as draft
249
- const schemaMod = await Promise.resolve().then(function () { return require('./Schema-STP972Xw.js'); });
250
- const { Schema } = schemaMod;
251
- // Stopped meanwhile (unloaded, or evicted with its schema): don't load the schema again
252
- if (main_cjs.isActorStopped(self))
253
- return;
254
- const schema = Schema.create(fullContext._schemaName, {
255
- waitForReady: false,
256
- });
257
- // Send event to Schema machine to mark as draft
258
- schema.getService().send({
259
- type: 'markAsDraft',
260
- propertyKey: `${fullContext.modelName}:${fullContext.name}`,
261
- });
262
- }
263
- }
264
- else {
265
- // No changes - clear edited flag in database and context
266
- logger$2(`Property ${fullContext.modelName}:${fullContext.name} has no changes`);
267
- // Clear isEdited flag in database
268
- try {
269
- const seedSchemaMod = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.schema; });
270
- const { properties: propertiesTable, models: modelsTable } = seedSchemaMod;
271
- const drizzleMod = await import('drizzle-orm');
272
- const { eq, and } = drizzleMod;
273
- const db = main_cjs.BaseDb.getAppDb();
274
- if (db && fullContext.modelName && fullContext.name) {
275
- // Find this property's model (model names are only unique per schema)
276
- const { resolveModelRecord } = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.resolveModelRecord$1; });
277
- const modelRecord = await resolveModelRecord(fullContext.modelName, {
278
- modelId: typeof fullContext.modelId === 'number' ? fullContext.modelId : undefined,
279
- schemaName: fullContext._schemaName,
280
- }, db);
281
- const modelRecords = modelRecord ? [modelRecord] : [];
282
- if (modelRecords.length > 0) {
283
- // Find property by name and modelId
284
- const propertyRecords = await db
285
- .select({ id: propertiesTable.id })
286
- .from(propertiesTable)
287
- .where(and(eq(propertiesTable.name, fullContext.name), eq(propertiesTable.modelId, modelRecords[0].id)))
288
- .limit(1);
289
- if (propertyRecords.length > 0) {
290
- // Clear isEdited flag in database
291
- await db
292
- .update(propertiesTable)
293
- .set({ isEdited: false })
294
- .where(eq(propertiesTable.id, propertyRecords[0].id));
295
- logger$2(`Cleared isEdited flag in database for property ${fullContext.modelName}:${fullContext.name}`);
296
- }
297
- }
298
- }
299
- }
300
- catch (error) {
301
- logger$2(`Error clearing isEdited flag in database: ${error}`);
302
- }
303
- sendBack({
304
- type: 'clearEdited',
305
- });
306
- }
307
- };
308
- _compareAndMarkDraft().then(() => {
309
- sendBack({ type: 'compareAndMarkDraftSuccess' });
310
- }).catch((error) => {
311
- logger$2('Error comparing and marking draft:', error);
312
- sendBack({ type: 'compareAndMarkDraftError', error });
313
- });
314
- return () => {
315
- // Cleanup function (optional)
316
- };
317
- });
318
-
319
- const logger$1 = debug('seedSdk:modelProperty:actors:validateProperty');
320
- const validateProperty = xstate.fromCallback(({ sendBack, input: { context }, self }) => {
321
- const _validateProperty = async () => {
322
- // Use full context for validation: fill modelName/dataType from _originalValues when missing, then
323
- // from DB by schemaFileId (context.id) so just-created renames don't fail structure validation.
324
- let fullContext = {
325
- ...context,
326
- modelName: context.modelName ?? context._originalValues?.modelName,
327
- dataType: context.dataType ?? context._originalValues?.dataType,
328
- };
329
- const schemaFileId = typeof context.id === 'string' ? context.id : undefined;
330
- if (schemaFileId && (fullContext.modelName === undefined || fullContext.dataType === undefined)) {
331
- // Brief wait so trackPendingWrite from ModelProperty.create() has time to run (it's in setTimeout(0))
332
- await new Promise((r) => setTimeout(r, 60));
333
- try {
334
- const dbMod = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.db; });
335
- const { getPropertyModelNameAndDataType, getModelNameByModelId } = dbMod;
336
- // Try pending write first (property row may not exist yet)
337
- if (fullContext.modelName === undefined && schemaFileId) {
338
- const mod = await Promise.resolve().then(function () { return ModelProperty$1; });
339
- const ModelProperty = mod?.ModelProperty ?? mod?.default;
340
- const pendingModelId = ModelProperty?.getPendingModelId?.(schemaFileId);
341
- if (pendingModelId != null) {
342
- const modelName = await getModelNameByModelId(pendingModelId);
343
- if (modelName) {
344
- fullContext = { ...fullContext, modelName };
345
- }
346
- }
347
- }
348
- // Then DB property lookup with retry (catches row after initial write)
349
- let fromDb;
350
- for (let attempt = 0; attempt < 6; attempt++) {
351
- fromDb = await getPropertyModelNameAndDataType(schemaFileId);
352
- if (fromDb)
353
- break;
354
- if (attempt < 5)
355
- await new Promise((r) => setTimeout(r, 40));
356
- }
357
- if (fromDb) {
358
- fullContext = {
359
- ...fullContext,
360
- modelName: fullContext.modelName ?? fromDb.modelName,
361
- dataType: fullContext.dataType ?? fromDb.dataType,
362
- };
363
- }
364
- // If still no modelName, try pending write again (may have been set during retries)
365
- if (fullContext.modelName === undefined && schemaFileId) {
366
- const mod = await Promise.resolve().then(function () { return ModelProperty$1; });
367
- const ModelProperty = mod?.ModelProperty ?? mod?.default;
368
- const pendingModelId = ModelProperty?.getPendingModelId?.(schemaFileId);
369
- if (pendingModelId != null) {
370
- const modelName = await getModelNameByModelId(pendingModelId);
371
- if (modelName) {
372
- fullContext = { ...fullContext, modelName };
373
- }
374
- }
375
- }
376
- }
377
- catch {
378
- // ignore
379
- }
380
- }
381
- // Last resort for structure validation: TProperty requires dataType; allow name+id to pass if we have schemaFileId
382
- if (fullContext.dataType === undefined && (fullContext.modelName !== undefined || schemaFileId)) {
383
- fullContext = { ...fullContext, dataType: (fullContext.dataType ?? context._originalValues?.dataType) || 'Text' };
384
- }
385
- // Use dynamic imports to break circular dependencies
386
- const validationServiceMod = await Promise.resolve().then(function () { return require('./SchemaValidationService-CKNW1bKg.js'); });
387
- const { SchemaValidationService } = validationServiceMod;
388
- const validationService = new SchemaValidationService();
389
- const schemaMod = await Promise.resolve().then(function () { return require('./Schema-STP972Xw.js'); });
390
- const { Schema } = schemaMod;
391
- // Validate property structure
392
- const structureResult = validationService.validatePropertyStructure(fullContext);
393
- if (!structureResult.isValid) {
394
- sendBack({ type: 'validationError', errors: structureResult.errors });
395
- return;
396
- }
397
- // If we have schema name and model name, validate against schema
398
- // Stopped meanwhile (unloaded, or evicted with its schema): don't load the schema again
399
- if (main_cjs.isActorStopped(self))
400
- return;
401
- if (fullContext._schemaName && fullContext.modelName) {
402
- try {
403
- const schema = Schema.create(fullContext._schemaName, {
404
- waitForReady: false,
405
- });
406
- const schemaSnapshot = schema.getService().getSnapshot();
407
- const schemaStatus = schemaSnapshot.value;
408
- // Only validate against schema if it's loaded (in idle state)
409
- // If still loading, skip schema validation and only do structure validation
410
- if (schemaStatus === 'idle') {
411
- const schemaContext = schemaSnapshot.context;
412
- // Check if models are actually loaded
413
- if (schemaContext.models && Object.keys(schemaContext.models).length > 0) {
414
- // If property name has changed, validate against the original name (from schema file)
415
- // This handles the case where a property is renamed - the schema file still has the old name
416
- const propertyNameToValidate = fullContext._originalValues?.name && fullContext._originalValues.name !== fullContext.name
417
- ? fullContext._originalValues.name
418
- : fullContext.name || '';
419
- const schemaResult = validationService.validateProperty(schemaContext, fullContext.modelName, propertyNameToValidate, fullContext);
420
- if (!schemaResult.isValid) {
421
- // If property was renamed, some validation errors are expected (like property_not_found, missing_type)
422
- // Only fail if it's a critical error that's not related to the rename
423
- const isRenamed = fullContext._originalValues?.name && fullContext._originalValues.name !== fullContext.name;
424
- const criticalErrors = schemaResult.errors.filter(err => {
425
- // Allow property_not_found and missing_type errors when property is renamed
426
- if (isRenamed && (err.code === 'property_not_found' || err.code === 'missing_type')) {
427
- return false; // Not critical
428
- }
429
- // For non-renamed properties, only allow property_not_found if we're validating with the same name
430
- if (err.code === 'property_not_found' && propertyNameToValidate === fullContext.name) {
431
- return false; // Not critical
432
- }
433
- return true; // Critical error
434
- });
435
- if (criticalErrors.length > 0) {
436
- sendBack({ type: 'validationError', errors: criticalErrors });
437
- return;
438
- }
439
- // Continue with validation - rename-related errors are acceptable
440
- }
441
- }
442
- else {
443
- logger$1('Schema models not loaded yet, skipping schema validation');
444
- // Continue with structure validation only
445
- }
446
- }
447
- else {
448
- logger$1(`Schema is in ${schemaStatus} state, skipping schema validation`);
449
- // Continue with structure validation only
450
- }
451
- }
452
- catch (error) {
453
- logger$1('Error validating property against schema:', error);
454
- // Continue with structure validation only
455
- }
456
- }
457
- // All validations passed
458
- sendBack({ type: 'validationSuccess', errors: [] });
459
- };
460
- _validateProperty().catch((error) => {
461
- logger$1('Error in validateProperty:', error);
462
- sendBack({
463
- type: 'validationError',
464
- errors: [{
465
- field: 'property',
466
- message: error instanceof Error ? error.message : 'Unknown validation error',
467
- code: 'validation_exception',
468
- severity: 'error',
469
- }],
470
- });
471
- });
472
- return () => {
473
- // Cleanup function (optional)
474
- };
475
- });
476
-
477
- const modelPropertyMachine = xstate.setup({
478
- types: {
479
- context: {},
480
- input: {},
481
- events: {},
482
- },
483
- actors: {
484
- saveToSchema,
485
- compareAndMarkDraft,
486
- validateProperty,
487
- writeProcessMachine: main_cjs.writeProcessMachine,
488
- },
489
- guards: {
490
- isPropertyValid: ({ context }) => {
491
- const valid = !context._validationErrors || context._validationErrors.length === 0;
492
- return valid;
493
- },
494
- },
495
- actions: {
496
- assignValidationErrors: xstate.assign(({ context, event }) => {
497
- if (event.type === 'validationError') {
498
- return {
499
- ...context,
500
- _validationErrors: event.errors,
501
- };
502
- }
503
- return context;
504
- }),
505
- },
506
- }).createMachine({
507
- id: 'modelProperty',
508
- initial: 'idle',
509
- context: ({ input }) => {
510
- const context = input;
511
- return context;
512
- },
513
- on: {
514
- updateContext: {
515
- actions: xstate.assign(({ context, event }) => {
516
- const newContext = Object.assign({}, context);
517
- // Check if this is only updating internal fields
518
- const onlyInternalFields = Object.keys(event).every((key) => {
519
- return key === 'type' || key.startsWith('_');
520
- });
521
- // Update the context with new values
522
- for (let i = 0; i < Object.keys(event).length; i++) {
523
- const key = Object.keys(event)[i];
524
- if (key === 'type') {
525
- continue;
526
- }
527
- let value = event[key];
528
- // Convert null to undefined for optional fields (TypeBox validation expects undefined, not null)
529
- // This is especially important for refValueType, refModelId, etc.
530
- if (value === null && (key === 'refValueType' || key === 'refModelId' || key === 'ref' || key === 'refModelName')) {
531
- value = undefined;
532
- }
533
- newContext[key] = value;
534
- }
535
- // Preserve modelName and dataType from context or _originalValues so validation and savePropertyToDb
536
- // never run with missing required/needed fields (e.g. when a just-created property is renamed before
537
- // the full context is available from the creator).
538
- if (newContext.modelName === undefined && context._originalValues?.modelName !== undefined) {
539
- newContext.modelName = context._originalValues.modelName;
540
- }
541
- if (newContext.dataType === undefined && context._originalValues?.dataType !== undefined) {
542
- newContext.dataType = context._originalValues.dataType;
543
- }
544
- // Compare with original values and set _isEdited flag (only for non-internal updates)
545
- if (!onlyInternalFields && context._originalValues) {
546
- const hasChanges = Object.keys(event).some((key) => {
547
- if (key === 'type' || key.startsWith('_'))
548
- return false;
549
- return newContext[key] !== context._originalValues?.[key];
550
- });
551
- newContext._isEdited = hasChanges;
552
- }
553
- // Clear validation errors on context update (will be re-validated if needed)
554
- newContext._validationErrors = undefined;
555
- // Convert null to undefined for optional fields (TypeBox validation expects undefined, not null)
556
- // This is especially important for refValueType, refModelId, etc.
557
- const optionalFields = ['refValueType', 'refModelId', 'ref', 'refModelName'];
558
- for (const field of optionalFields) {
559
- if (newContext[field] === null) {
560
- newContext[field] = undefined;
561
- }
562
- }
563
- return newContext;
564
- }),
565
- // Only trigger validation and compareAndMarkDraft if we're updating non-internal fields
566
- guard: ({ event }) => {
567
- const shouldTransition = !Object.keys(event).every((key) => {
568
- return key === 'type' || key.startsWith('_');
569
- });
570
- return shouldTransition;
571
- },
572
- target: '.validating',
573
- },
574
- validateProperty: {
575
- target: '.validating',
576
- },
577
- validationSuccess: {
578
- actions: xstate.assign(({ context, event }) => ({
579
- ...context,
580
- _validationErrors: [],
581
- })),
582
- },
583
- validationError: {
584
- actions: 'assignValidationErrors',
585
- // Note: State-specific transitions are handled in child states
586
- // This root handler ensures errors are always assigned to context
587
- },
588
- initializeOriginalValues: {
589
- actions: xstate.assign(({ context, event }) => ({
590
- ...context,
591
- _originalValues: event.originalValues,
592
- // Senders don't know the schema name; keep one setSchemaName already stored.
593
- _schemaName: event.schemaName ?? context._schemaName,
594
- _isEdited: event.isEdited ?? false,
595
- _validationErrors: undefined,
596
- })),
597
- },
598
- clearEdited: {
599
- actions: xstate.assign(({ context }) => ({
600
- ...context,
601
- _isEdited: false,
602
- _originalValues: { ...context },
603
- })),
604
- },
605
- setSchemaName: {
606
- actions: xstate.assign(({ context, event }) => ({
607
- ...context,
608
- _schemaName: event.schemaName,
609
- })),
610
- },
611
- destroyStarted: {
612
- actions: xstate.assign({ _destroyError: null }),
613
- },
614
- destroyError: {
615
- actions: xstate.assign(({ event }) => ({
616
- _destroyError: event.error instanceof Error
617
- ? { message: event.error.message, name: event.error.name }
618
- : { message: String(event.error) },
619
- })),
620
- },
621
- },
622
- states: {
623
- idle: {
624
- on: {
625
- saveToSchema: { target: 'saveToSchema' },
626
- validateProperty: {
627
- target: 'validating',
628
- },
629
- requestWrite: {
630
- actions: ({ context, event }) => {
631
- if (context.writeProcess) {
632
- context.writeProcess.send({
633
- type: 'startWrite',
634
- data: event.data,
635
- });
636
- }
637
- },
638
- },
639
- },
640
- entry: xstate.assign({
641
- writeProcess: ({ spawn, context }) => {
642
- // Spawn writeProcess if we have id (schemaFileId)
643
- // New properties will have id generated
644
- const entityId = context.id;
645
- if (!context.writeProcess && entityId) {
646
- return spawn(main_cjs.writeProcessMachine, {
647
- input: {
648
- entityType: 'modelProperty',
649
- entityId: String(entityId),
650
- entityData: context,
651
- },
652
- });
653
- }
654
- return context.writeProcess;
655
- },
656
- }),
657
- },
658
- validating: {
659
- on: {
660
- saveToSchema: { target: 'saveToSchema' },
661
- validationSuccess: {
662
- target: 'compareAndMarkDraft',
663
- actions: xstate.assign(({ context }) => ({
664
- ...context,
665
- _validationErrors: [],
666
- })),
667
- },
668
- // validationError: Uses root handler for assignment, but needs state-specific transition
669
- validationError: {
670
- target: 'idle',
671
- actions: 'assignValidationErrors',
672
- },
673
- },
674
- invoke: {
675
- src: 'validateProperty',
676
- input: ({ context }) => ({ context }),
677
- },
678
- },
679
- compareAndMarkDraft: {
680
- on: {
681
- saveToSchema: { target: 'saveToSchema' },
682
- compareAndMarkDraftSuccess: {
683
- target: 'idle',
684
- },
685
- compareAndMarkDraftError: {
686
- target: 'idle',
687
- },
688
- },
689
- invoke: {
690
- src: 'compareAndMarkDraft',
691
- input: ({ context }) => ({ context }),
692
- },
693
- },
694
- saveToSchema: {
695
- // Gate the save in a transient child. An `always` on this parent targeting `.saving` re-enters
696
- // the invoking child on every microstep and never settles (save() ran the process out of memory).
697
- initial: 'checking',
698
- on: {
699
- // Apply updateContext but do not re-invoke saveToSchema actor (avoids infinite loop).
700
- // Context is updated; in-flight save completes with original context.
701
- updateContext: [
702
- {
703
- guard: ({ context, event }) => {
704
- const onlyInternal = Object.keys(event).every((k) => k === 'type' || k.startsWith('_'));
705
- return !onlyInternal && event.name !== undefined && event.name !== context.name;
706
- },
707
- actions: xstate.assign(({ context, event }) => {
708
- const newContext = Object.assign({}, context);
709
- const onlyInternalFields = Object.keys(event).every((key) => key === 'type' || key.startsWith('_'));
710
- for (const key of Object.keys(event)) {
711
- if (key === 'type')
712
- continue;
713
- let value = event[key];
714
- if (value === null && ['refValueType', 'refModelId', 'ref', 'refModelName'].includes(key))
715
- value = undefined;
716
- newContext[key] = value;
717
- }
718
- if (newContext.modelName === undefined && context._originalValues?.modelName !== undefined) {
719
- newContext.modelName = context._originalValues.modelName;
720
- }
721
- if (newContext.dataType === undefined && context._originalValues?.dataType !== undefined) {
722
- newContext.dataType = context._originalValues.dataType;
723
- }
724
- if (!onlyInternalFields && context._originalValues) {
725
- const hasChanges = Object.keys(event).some((key) => {
726
- if (key === 'type' || key.startsWith('_'))
727
- return false;
728
- return newContext[key] !== context._originalValues?.[key];
729
- });
730
- newContext._isEdited = hasChanges;
731
- }
732
- newContext._validationErrors = undefined;
733
- return newContext;
734
- }),
735
- },
736
- {
737
- actions: xstate.assign(({ context, event }) => {
738
- const newContext = Object.assign({}, context);
739
- const onlyInternalFields = Object.keys(event).every((key) => key === 'type' || key.startsWith('_'));
740
- for (const key of Object.keys(event)) {
741
- if (key === 'type')
742
- continue;
743
- let value = event[key];
744
- if (value === null && ['refValueType', 'refModelId', 'ref', 'refModelName'].includes(key))
745
- value = undefined;
746
- newContext[key] = value;
747
- }
748
- if (newContext.modelName === undefined && context._originalValues?.modelName !== undefined) {
749
- newContext.modelName = context._originalValues.modelName;
750
- }
751
- if (newContext.dataType === undefined && context._originalValues?.dataType !== undefined) {
752
- newContext.dataType = context._originalValues.dataType;
753
- }
754
- if (!onlyInternalFields && context._originalValues) {
755
- const hasChanges = Object.keys(event).some((key) => {
756
- if (key === 'type' || key.startsWith('_'))
757
- return false;
758
- return newContext[key] !== context._originalValues?.[key];
759
- });
760
- newContext._isEdited = hasChanges;
761
- }
762
- newContext._validationErrors = undefined;
763
- return newContext;
764
- }),
765
- },
766
- ],
767
- // validationError: Uses root handler for assignment, but needs state-specific transition
768
- validationError: {
769
- target: 'idle',
770
- actions: 'assignValidationErrors',
771
- },
772
- },
773
- states: {
774
- checking: {
775
- always: [
776
- { guard: 'isPropertyValid', target: 'saving' },
777
- { target: '#modelProperty.idle' },
778
- ],
779
- },
780
- saving: {
781
- on: {
782
- saveToSchemaSuccess: {
783
- target: '#modelProperty.idle',
784
- },
785
- saveToSchemaError: {
786
- target: '#modelProperty.idle',
787
- },
788
- },
789
- invoke: {
790
- src: 'saveToSchema',
791
- input: ({ context }) => ({ context }),
792
- },
793
- },
794
- },
795
- },
796
- },
797
- });
798
-
799
- /**
800
- * Returns property rows for a model identified by modelFileId (model's schemaFileId).
801
- * Resolves modelFileId -> modelId via models table, then selects all properties where modelId = ?.
802
- * Each row includes schemaFileId for use with ModelProperty.createById(schemaFileId).
803
- */
804
- const getModelPropertiesData = async (modelFileId) => {
805
- const appDb = main_cjs.BaseDb.getAppDb();
806
- if (!appDb || !modelFileId) {
807
- return [];
808
- }
809
- const modelRows = await appDb
810
- .select({ id: main_cjs.models.id })
811
- .from(main_cjs.models)
812
- .where(drizzleOrm.eq(main_cjs.models.schemaFileId, modelFileId))
813
- .limit(1);
814
- if (modelRows.length === 0) {
815
- return [];
816
- }
817
- const modelId = modelRows[0].id;
818
- const rows = await appDb
819
- .select()
820
- .from(main_cjs.properties)
821
- .where(drizzleOrm.eq(main_cjs.properties.modelId, modelId));
822
- return rows;
823
- };
824
-
825
- const logger = debug('seedSdk:modelProperty:ModelProperty');
826
- // Define the property keys from TProperty
827
- const TPropertyKeys = [
828
- 'id',
829
- 'name',
830
- 'dataType',
831
- 'ref',
832
- 'modelId',
833
- 'refModelId',
834
- 'refValueType',
835
- 'storageType',
836
- 'localStorageDir',
837
- 'filenameSuffix',
838
- 'modelName',
839
- 'refModelName',
840
- ];
841
- class ModelProperty {
842
- constructor(property) {
843
- /** currentEvictionEpoch() at construction: lookups started for this instance don't outlive an eviction. */
844
- this._evictionEpoch = main_cjs.currentEvictionEpoch();
845
- // id is now the schemaFileId (string), _dbId is the database integer ID
846
- // Preserve _propertyFileId if it exists in the property object (from getPropertySchema)
847
- // Convert null to undefined for optional fields (TypeBox validation expects undefined, not null)
848
- const serviceInput = {
849
- ...property,
850
- _propertyFileId: property._propertyFileId || property.id,
851
- refValueType: property.refValueType ?? undefined,
852
- refModelId: property.refModelId ?? undefined,
853
- ref: property.ref ?? undefined,
854
- refModelName: property.refModelName ?? undefined,
855
- };
856
- this._service = xstate.createActor(modelPropertyMachine, {
857
- input: serviceInput,
858
- });
859
- this._service.start();
860
- // Initialize original values from the input property
861
- this._initializeOriginalValues(property);
862
- // Note: Property getters/setters are now handled by the Proxy in create()
863
- }
864
- /**
865
- * Send from fire-and-forget async work (DB lookups started in the constructor), which can finish
866
- * after the instance was unloaded or evicted with its schema.
867
- */
868
- _sendIfRunning(event) {
869
- if (main_cjs.isActorStopped(this._service))
870
- return;
871
- this._service.send(event);
872
- }
873
- /**
874
- * Initialize original values and schema name for tracking changes
875
- * This is called asynchronously after construction
876
- * If the property was loaded from the database and differs from the schema file,
877
- * it will be marked as edited.
878
- */
879
- _initializeOriginalValues(property) {
880
- // Resolve refModelId if ref/refModelName is provided but refModelId is missing
881
- const refModelName = property.refModelName || property.ref;
882
- if (refModelName && !property.refModelId) {
883
- // Resolve refModelId asynchronously and update context
884
- this._resolveRefModelId(refModelName, property).then((refModelId) => {
885
- if (refModelId) {
886
- // Update the context with the resolved refModelId
887
- this._sendIfRunning({
888
- type: 'updateContext',
889
- refModelId,
890
- });
891
- }
892
- }).catch(() => {
893
- // Ignore errors - model might not exist yet
894
- });
895
- }
896
- // Get schema file values to use as "original" values
897
- // This allows us to detect if the property was edited (DB value differs from schema file)
898
- this._getSchemaFileValues(property).then((schemaFileValues) => {
899
- // Use schema file values as original (not the current property values)
900
- // This way, if current values differ from schema file, _isEdited will be true
901
- const originalValues = schemaFileValues || {};
902
- // Compare current property values with schema file values to determine if edited
903
- // Only compare schema-relevant fields, not database-specific fields like id, modelId
904
- let isEdited = false;
905
- if (schemaFileValues) {
906
- // Fields that are schema-relevant and should be compared
907
- const schemaRelevantFields = [
908
- 'dataType',
909
- 'ref',
910
- 'refModelName',
911
- 'refValueType',
912
- 'storageType',
913
- 'localStorageDir',
914
- 'filenameSuffix',
915
- ];
916
- // Check if any schema-relevant field differs between current property and schema file
917
- isEdited = schemaRelevantFields.some((key) => {
918
- const currentValue = property[key];
919
- const originalValue = schemaFileValues[key];
920
- // Handle ref fields - compare by name
921
- if (key === 'ref' || key === 'refModelName') {
922
- const currentRef = property.refModelName || property.ref;
923
- const originalRef = schemaFileValues.refModelName || schemaFileValues.ref;
924
- // Both undefined/null means no ref, so they're the same
925
- if (!currentRef && !originalRef)
926
- return false;
927
- return currentRef !== originalRef;
928
- }
929
- // For other fields, compare values (handling undefined/null)
930
- if (currentValue === undefined && originalValue === undefined)
931
- return false;
932
- if (currentValue === null && originalValue === null)
933
- return false;
934
- if (currentValue === undefined && originalValue === null)
935
- return false;
936
- if (currentValue === null && originalValue === undefined)
937
- return false;
938
- return currentValue !== originalValue;
939
- });
940
- }
941
- else {
942
- // No schema file values found - this is a runtime-created property
943
- // Use current property as original, but mark as edited since it hasn't been exported to file
944
- TPropertyKeys.forEach((key) => {
945
- if (property[key] !== undefined) {
946
- originalValues[key] = property[key];
947
- }
948
- });
949
- // For runtime-created properties, set isEdited = true initially
950
- isEdited = true;
951
- }
952
- // Initialize with original values, including isEdited flag
953
- // Load isEdited from database if property exists in DB (async, fire-and-forget)
954
- this._loadIsEditedFromDb(property, isEdited).then((isEditedFromDb) => {
955
- this._sendIfRunning({
956
- type: 'initializeOriginalValues',
957
- originalValues,
958
- schemaName: undefined, // Will be set later if needed
959
- isEdited: isEditedFromDb,
960
- });
961
- }).catch(() => {
962
- // If we can't load from DB, use computed isEdited value
963
- this._sendIfRunning({
964
- type: 'initializeOriginalValues',
965
- originalValues,
966
- schemaName: undefined, // Will be set later if needed
967
- isEdited,
968
- });
969
- });
970
- }).catch(() => {
971
- // If we can't get schema file values, use current property as original
972
- const originalValues = {};
973
- TPropertyKeys.forEach((key) => {
974
- if (property[key] !== undefined) {
975
- originalValues[key] = property[key];
976
- }
977
- });
978
- this._sendIfRunning({
979
- type: 'initializeOriginalValues',
980
- originalValues,
981
- schemaName: undefined,
982
- });
983
- });
984
- // Get schema name from model asynchronously (fire-and-forget)
985
- if (property.modelName) {
986
- this._setSchemaName(property).catch(() => {
987
- // If we can't get schema name, that's okay - it will be set later if needed
988
- });
989
- }
990
- }
991
- /**
992
- * Load isEdited flag from database if property exists in DB
993
- * @param property - The property data
994
- * @param fallbackIsEdited - Fallback value if property doesn't exist in DB
995
- * @returns The isEdited flag from database or fallback value
996
- */
997
- async _loadIsEditedFromDb(property, fallbackIsEdited) {
998
- if (!property.modelName || !property.name) {
999
- return fallbackIsEdited;
1000
- }
1001
- try {
1002
- const db = main_cjs.BaseDb.getAppDb();
1003
- if (!db) {
1004
- return fallbackIsEdited;
1005
- }
1006
- // Find this property's model (by modelId when known; model names are only unique per schema)
1007
- const { resolveModelRecord } = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.resolveModelRecord$1; });
1008
- const modelRecord = await resolveModelRecord(property.modelName, this._ownerModelScope(property), db);
1009
- const modelRecords = modelRecord ? [modelRecord] : [];
1010
- if (modelRecords.length === 0) {
1011
- return fallbackIsEdited;
1012
- }
1013
- // Find property by name and modelId
1014
- const propertyRecords = await db
1015
- .select({ isEdited: main_cjs.properties.isEdited })
1016
- .from(main_cjs.properties)
1017
- .where(drizzleOrm.and(drizzleOrm.eq(main_cjs.properties.name, property.name), drizzleOrm.eq(main_cjs.properties.modelId, modelRecords[0].id)))
1018
- .limit(1);
1019
- if (propertyRecords.length > 0) {
1020
- return propertyRecords[0].isEdited ?? false;
1021
- }
1022
- }
1023
- catch (error) {
1024
- // Ignore errors - use fallback value
1025
- }
1026
- return fallbackIsEdited;
1027
- }
1028
- /**
1029
- * Resolve refModelId from refModelName by querying the database
1030
- * @param refModelName - The name of the referenced model
1031
- * @returns The database ID of the referenced model, or undefined if not found
1032
- */
1033
- async _resolveRefModelId(refModelName, property) {
1034
- if (!refModelName) {
1035
- return undefined;
1036
- }
1037
- try {
1038
- const db = main_cjs.BaseDb.getAppDb();
1039
- if (!db) {
1040
- return undefined;
1041
- }
1042
- // Resolve the ref within the owning model's schema
1043
- const { resolveRefModelRecord } = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.resolveModelRecord$1; });
1044
- const refModelRecord = await resolveRefModelRecord(refModelName, property ? this._ownerModelScope(property) : { schemaName: this._getSnapshotContext()._schemaName }, db);
1045
- if (refModelRecord?.id) {
1046
- return refModelRecord.id;
1047
- }
1048
- }
1049
- catch (error) {
1050
- // Ignore errors - model might not exist yet or database not available
1051
- logger(`Error resolving refModelId for model "${refModelName}":`, error);
1052
- }
1053
- return undefined;
1054
- }
1055
- /** Which model this property belongs to: its models row id and/or schema. */
1056
- _ownerModelScope(property) {
1057
- const context = this._getSnapshotContext();
1058
- const modelId = property.modelId ?? context.modelId;
1059
- return {
1060
- modelId: typeof modelId === 'number' ? modelId : undefined,
1061
- schemaName: context._schemaName,
1062
- };
1063
- }
1064
- /**
1065
- * Get schema file values for this property to use as "original" values
1066
- * This allows comparison with database values to detect edits
1067
- */
1068
- async _getSchemaFileValues(property) {
1069
- if (!property.modelName || !property.name) {
1070
- return undefined;
1071
- }
1072
- try {
1073
- const { modelId, schemaName } = this._ownerModelScope(property);
1074
- // Synchronous cache hits first: callers compare against these values right after construction,
1075
- // so an extra await here lets every property look "edited" (a draft save each). Only then the
1076
- // DB (by modelId), and never instantiate a Model while its own properties are initializing.
1077
- let model = schemaName ? main_cjs.Model.getByName(property.modelName, schemaName) : undefined;
1078
- if (!model) {
1079
- try {
1080
- model = main_cjs.Model.getByName(property.modelName);
1081
- }
1082
- catch {
1083
- model = undefined; // ambiguous name; resolve by modelId below
1084
- }
1085
- }
1086
- if (!model && modelId) {
1087
- const { resolveModelRecord } = await Promise.resolve().then(function () { return require('./index-usKaANAq.js'); }).then(function (n) { return n.resolveModelRecord$1; });
1088
- const modelFileId = (await resolveModelRecord(property.modelName, { modelId }))?.schemaFileId;
1089
- model = modelFileId ? main_cjs.Model.getById(modelFileId) : undefined;
1090
- }
1091
- // Evicted (with its schema) or unloaded while the lookups above ran: don't instantiate its model
1092
- // again from rows that are being deleted.
1093
- if (!model && main_cjs.isActorStopped(this._service))
1094
- return undefined;
1095
- if (!model) {
1096
- try {
1097
- // The epoch from when this instance was built: an eviction of the model's schema since
1098
- // then (this instance may already be stopped) must not re-create the model.
1099
- model = await main_cjs.Model.getByNameAsync(property.modelName, undefined, {
1100
- evictionEpoch: this._evictionEpoch,
1101
- });
1102
- }
1103
- catch {
1104
- model = undefined;
1105
- }
1106
- }
1107
- if (!model || !model.properties || model.properties.length === 0) {
1108
- return undefined;
1109
- }
1110
- const schema = main_cjs.modelPropertiesToObject(model.properties);
1111
- // Get the schema file value for this property
1112
- const schemaFileValue = schema[property.name];
1113
- if (!schemaFileValue) {
1114
- return undefined;
1115
- }
1116
- // Return schema file values as original values
1117
- const originalValues = {
1118
- name: property.name,
1119
- modelName: property.modelName,
1120
- dataType: schemaFileValue.dataType,
1121
- storageType: schemaFileValue.storageType,
1122
- localStorageDir: schemaFileValue.localStorageDir,
1123
- filenameSuffix: schemaFileValue.filenameSuffix,
1124
- };
1125
- // Handle ref fields
1126
- if (schemaFileValue.ref) {
1127
- originalValues.ref = schemaFileValue.ref;
1128
- originalValues.refModelName = schemaFileValue.ref;
1129
- // Try to get refModelId from database
1130
- const refModelId = await this._resolveRefModelId(schemaFileValue.ref, property);
1131
- if (refModelId) {
1132
- originalValues.refModelId = refModelId;
1133
- }
1134
- }
1135
- if (schemaFileValue.refValueType) {
1136
- originalValues.refValueType = schemaFileValue.refValueType;
1137
- }
1138
- return originalValues;
1139
- }
1140
- catch (error) {
1141
- return undefined;
1142
- }
1143
- }
1144
- /**
1145
- * Set the schema name for this property by looking it up from the model
1146
- * Tries database first (more reliable), then falls back to schema files
1147
- */
1148
- async _setSchemaName(property) {
1149
- try {
1150
- // create() sets the schema name right after construction when its caller knows it. Don't look it
1151
- // up then: the file fallback below reads and parses every schema file, per property.
1152
- await Promise.resolve();
1153
- if (this._getSnapshotContext()._schemaName)
1154
- return;
1155
- let schemaName;
1156
- // From the property data: the instance's own modelId field is never assigned (the reactive proxy
1157
- // serves values from the machine context), so reading this.modelId skipped the DB lookup.
1158
- const { modelId } = this._ownerModelScope(property);
1159
- // Try to get schema name from database first (more reliable)
1160
- if (modelId) {
1161
- try {
1162
- const db = main_cjs.BaseDb.getAppDb();
1163
- if (db) {
1164
- const modelSchemaRecords = await db
1165
- .select({
1166
- schemaName: main_cjs.schemas.name,
1167
- })
1168
- .from(main_cjs.modelSchemas)
1169
- .innerJoin(main_cjs.schemas, drizzleOrm.eq(main_cjs.modelSchemas.schemaId, main_cjs.schemas.id))
1170
- .innerJoin(main_cjs.models, drizzleOrm.eq(main_cjs.modelSchemas.modelId, main_cjs.models.id))
1171
- .where(drizzleOrm.eq(main_cjs.models.id, modelId))
1172
- .limit(1);
1173
- if (modelSchemaRecords.length > 0) {
1174
- schemaName = modelSchemaRecords[0].schemaName;
1175
- }
1176
- }
1177
- }
1178
- catch (error) {
1179
- // Database lookup failed, continue to file-based lookup
1180
- }
1181
- }
1182
- // Fall back to schema file lookup if database didn't work
1183
- if (!schemaName) {
1184
- schemaName = await getSchemaNameFromModel(property.modelName);
1185
- }
1186
- if (schemaName) {
1187
- // Update the context with the schema name using dedicated event
1188
- this._sendIfRunning({
1189
- type: 'setSchemaName',
1190
- schemaName,
1191
- });
1192
- }
1193
- }
1194
- catch (error) {
1195
- // If we can't get schema name, that's okay - it will be set later if needed
1196
- }
1197
- }
1198
- /**
1199
- * Resolve schema name from DB when _schemaName is missing (e.g. destroy ran before _setSchemaName finished).
1200
- * Uses context.id (schemaFileId) and context.modelId to query properties → model_schemas → schemas.
1201
- */
1202
- async _resolveSchemaNameForDestroy(db, context) {
1203
- try {
1204
- let modelId = context.modelId;
1205
- const schemaFileId = context.id ?? context._propertyFileId;
1206
- // If we don't have modelId, get it from the property row by schemaFileId
1207
- if (modelId == null && schemaFileId) {
1208
- const propRows = await db
1209
- .select({ modelId: main_cjs.properties.modelId })
1210
- .from(main_cjs.properties)
1211
- .where(drizzleOrm.eq(main_cjs.properties.schemaFileId, schemaFileId))
1212
- .limit(1);
1213
- if (propRows.length > 0 && propRows[0].modelId != null) {
1214
- modelId = propRows[0].modelId;
1215
- }
1216
- }
1217
- if (modelId == null)
1218
- return undefined;
1219
- // Resolve modelFileId (string) to database ID (number) if needed - modelSchemas.modelId expects number
1220
- const modelIdNum = typeof modelId === 'string' ? await main_cjs.getModelIdByFileId(modelId) : modelId;
1221
- const modelSchemaRecords = await db
1222
- .select({ schemaName: main_cjs.schemas.name })
1223
- .from(main_cjs.modelSchemas)
1224
- .innerJoin(main_cjs.schemas, drizzleOrm.eq(main_cjs.modelSchemas.schemaId, main_cjs.schemas.id))
1225
- .where(drizzleOrm.eq(main_cjs.modelSchemas.modelId, modelIdNum))
1226
- .limit(1);
1227
- return modelSchemaRecords.length > 0 ? modelSchemaRecords[0].schemaName ?? undefined : undefined;
1228
- }
1229
- catch {
1230
- return undefined;
1231
- }
1232
- }
1233
- /**
1234
- * Manually set the schema name for this property
1235
- * Useful when you know the schema name from context (e.g., when working with Schema instances)
1236
- */
1237
- setSchemaName(schemaName) {
1238
- this._service.send({
1239
- type: 'setSchemaName',
1240
- schemaName,
1241
- });
1242
- }
1243
- static create(property, options) {
1244
- if (!property) {
1245
- throw new Error('Property is required');
1246
- }
1247
- const waitForReady = options?.waitForReady !== false;
1248
- const readyTimeout = options?.readyTimeout ?? 5000;
1249
- const schemaName = options?.schemaName;
1250
- // Handle 'type' field from JSON schema format - convert to 'dataType'
1251
- // Normalize to PascalCase so lowercase values from JSON (e.g. 'text') work
1252
- const propertyWithId = { ...property };
1253
- const rawType = propertyWithId.dataType ?? propertyWithId.type;
1254
- if (rawType) {
1255
- propertyWithId.dataType = main_cjs.normalizeDataType(rawType);
1256
- }
1257
- if (typeof propertyWithId.refValueType === 'string') {
1258
- propertyWithId.refValueType = main_cjs.normalizeDataType(propertyWithId.refValueType);
1259
- }
1260
- // Ensure id (schemaFileId) is set correctly
1261
- // Priority: _propertyFileId > id (if string) > generate new
1262
- // If id is a number (old format), use _propertyFileId instead
1263
- if (typeof propertyWithId.id === 'number') {
1264
- // id is a database integer ID, not schemaFileId - use _propertyFileId if available
1265
- if (propertyWithId._propertyFileId) {
1266
- propertyWithId.id = propertyWithId._propertyFileId;
1267
- }
1268
- else {
1269
- // Fallback: generate new schemaFileId (shouldn't happen, but safety)
1270
- propertyWithId.id = main_cjs.generateId();
1271
- logger(`ModelProperty.create: id was a number, generated new schemaFileId "${propertyWithId.id}" for property "${property.name}"`);
1272
- }
1273
- }
1274
- else if (!propertyWithId.id && propertyWithId._propertyFileId) {
1275
- // id is not set but _propertyFileId is available - use it
1276
- propertyWithId.id = propertyWithId._propertyFileId;
1277
- }
1278
- else if (!propertyWithId.id) {
1279
- // Generate id (schemaFileId) if not provided (for new properties)
1280
- // This ensures new properties can trigger write process
1281
- propertyWithId.id = main_cjs.generateId();
1282
- logger(`ModelProperty.create: Generated new id (schemaFileId) "${propertyWithId.id}" for property "${property.name}"`);
1283
- }
1284
- // Create cache key from modelName and name, or use id
1285
- let cacheKey = propertyWithId.modelName && propertyWithId.name
1286
- ? `${propertyWithId.modelName}:${propertyWithId.name}`
1287
- : propertyWithId.id
1288
- ? `id:${propertyWithId.id}`
1289
- : propertyWithId.name || 'unnamed';
1290
- // Model names are only unique per schema: a cached "Post:title" may be another schema's
1291
- // property. If the ids say it's a different property, key this one by its own id instead.
1292
- // (Not by file id: the same property is often created first with a generated id, then its real one.)
1293
- const isOtherProperty = (cachedContext) => (typeof propertyWithId.modelId === 'number' &&
1294
- typeof cachedContext.modelId === 'number' &&
1295
- cachedContext.modelId !== propertyWithId.modelId) ||
1296
- (!!schemaName && !!cachedContext._schemaName && cachedContext._schemaName !== schemaName);
1297
- const cachedByName = this.instanceCache.get(cacheKey);
1298
- if (cachedByName && propertyWithId.id && isOtherProperty(cachedByName.instance._getSnapshotContext())) {
1299
- // Reuse this property's own id-keyed entry if it already has one.
1300
- cacheKey = `id:${propertyWithId.id}`;
1301
- for (const [key, { instance }] of this.instanceCache.entries()) {
1302
- if (!key.startsWith('id:'))
1303
- continue;
1304
- const ctx = instance._getSnapshotContext();
1305
- if (ctx.modelName === propertyWithId.modelName && ctx.name === propertyWithId.name && !isOtherProperty(ctx)) {
1306
- cacheKey = key;
1307
- break;
1308
- }
1309
- }
1310
- }
1311
- // Check if instance exists in cache
1312
- if (this.instanceCache.has(cacheKey)) {
1313
- const { instance, refCount } = this.instanceCache.get(cacheKey);
1314
- const cachedContext = instance._getSnapshotContext();
1315
- // Update cached instance if new property data has fields that the cached instance doesn't have
1316
- // This handles cases where the property was created without ref initially, but now we have ref from schema
1317
- const needsUpdate = {};
1318
- if (propertyWithId.ref && !cachedContext.ref) {
1319
- needsUpdate.ref = propertyWithId.ref;
1320
- }
1321
- if (propertyWithId.refModelName && !cachedContext.refModelName) {
1322
- needsUpdate.refModelName = propertyWithId.refModelName;
1323
- }
1324
- if (propertyWithId.refModelId && !cachedContext.refModelId) {
1325
- needsUpdate.refModelId = propertyWithId.refModelId;
1326
- }
1327
- if (Object.keys(needsUpdate).length > 0) {
1328
- instance._service.send({
1329
- type: 'updateContext',
1330
- ...needsUpdate,
1331
- });
1332
- }
1333
- this.instanceCache.set(cacheKey, {
1334
- instance,
1335
- refCount: refCount + 1,
1336
- });
1337
- if (!waitForReady)
1338
- return instance;
1339
- return main_cjs.waitForEntityIdle(instance, { timeout: readyTimeout }).then(() => instance);
1340
- }
1341
- const newInstance = new this(propertyWithId);
1342
- // Set schema name before async operations so getModelId can resolve the correct model
1343
- if (schemaName) {
1344
- newInstance._service.send({
1345
- type: 'setSchemaName',
1346
- schemaName,
1347
- });
1348
- }
1349
- // Wrap instance in Proxy for reactive property access
1350
- const proxiedInstance = main_cjs.createReactiveProxy({
1351
- instance: newInstance,
1352
- service: newInstance._service,
1353
- trackedProperties: TPropertyKeys,
1354
- getContext: (instance) => {
1355
- const context = instance._getSnapshotContext();
1356
- return context;
1357
- },
1358
- sendUpdate: (instance, prop, value) => {
1359
- const currentContext = instance._getSnapshotContext();
1360
- if (currentContext[prop] === value)
1361
- return; // No-op, avoid triggering machine
1362
- instance._service.send({
1363
- type: 'updateContext',
1364
- [prop]: value,
1365
- });
1366
- },
1367
- });
1368
- this.instanceCache.set(cacheKey, {
1369
- instance: proxiedInstance,
1370
- refCount: 1,
1371
- });
1372
- this.indexCachedInstance(cacheKey, proxiedInstance);
1373
- // Trigger write process if property has modelId (or modelName) and id (schemaFileId)
1374
- // Wait for service to be ready (idle state) and have writeProcess spawned
1375
- const propertyFileId = propertyWithId.id; // id is now the schemaFileId (string)
1376
- const hasModelId = propertyWithId.modelId || propertyWithId.modelName;
1377
- // A _dbId means the data was loaded from an existing row (createById, getPropertySchema), so
1378
- // there's nothing to write. Writing it back anyway raced deletes of that row's model (FK error).
1379
- const isPersisted = typeof propertyWithId._dbId === 'number';
1380
- if (hasModelId && propertyFileId && !isPersisted) {
1381
- // Wait for writeProcess to be spawned (it's spawned in idle state entry action)
1382
- // Retry a few times if writeProcess isn't available yet
1383
- let retries = 0;
1384
- const maxRetries = 10;
1385
- const checkAndSend = async () => {
1386
- const service = proxiedInstance.getService();
1387
- const snapshot = service.getSnapshot();
1388
- // Unloaded or evicted (e.g. Schema.destroy) before the write started: nothing to write to
1389
- if (main_cjs.isActorStopped(service))
1390
- return;
1391
- if (snapshot.value === 'idle' && snapshot.context.writeProcess) {
1392
- const writeProcess = snapshot.context.writeProcess;
1393
- // Resolve dbModelId - convert from string (modelFileId) to number (database ID) if needed
1394
- let resolvedModelId = undefined;
1395
- if (propertyWithId.modelId) {
1396
- if (typeof propertyWithId.modelId === 'number') {
1397
- resolvedModelId = propertyWithId.modelId;
1398
- }
1399
- else if (typeof propertyWithId.modelId === 'string') {
1400
- // modelId is a string (modelFileId), need to convert to database ID
1401
- try {
1402
- resolvedModelId = await main_cjs.getModelIdByFileId(propertyWithId.modelId);
1403
- logger(`Converted modelFileId "${propertyWithId.modelId}" to database modelId: ${resolvedModelId}`);
1404
- }
1405
- catch (error) {
1406
- logger(`Failed to convert modelFileId "${propertyWithId.modelId}" to database ID: ${error}`);
1407
- console.error(`[ModelProperty.create] Failed to convert modelFileId: ${error}`);
1408
- }
1409
- }
1410
- }
1411
- // If we still don't have a modelId, try to resolve it from modelName
1412
- if (!resolvedModelId && propertyWithId.modelName) {
1413
- try {
1414
- // Get schemaName from context if available
1415
- const schemaName = snapshot.context._schemaName;
1416
- resolvedModelId = await main_cjs.getModelId(propertyWithId.modelName, schemaName);
1417
- logger(`Resolved modelId for model "${propertyWithId.modelName}": ${resolvedModelId}`);
1418
- }
1419
- catch (error) {
1420
- logger(`Failed to resolve modelId for model "${propertyWithId.modelName}": ${error}`);
1421
- console.error(`[ModelProperty.create] Failed to resolve modelId: ${error}`);
1422
- }
1423
- }
1424
- if (!resolvedModelId) {
1425
- logger(`ERROR: Cannot write property "${property.name}" - no modelId available`);
1426
- console.error(`[ModelProperty.create] ERROR: Cannot write property "${property.name}" - no modelId available. modelId: ${propertyWithId.modelId}, modelName: ${propertyWithId.modelName}`);
1427
- // Don't clear pending write here - it might resolve later
1428
- return;
1429
- }
1430
- // Resolving the modelId awaited the DB; the instance may have been stopped meanwhile
1431
- if (main_cjs.isActorStopped(service))
1432
- return;
1433
- // Track pending write now that we have the resolved modelId
1434
- this.trackPendingWrite(propertyFileId, resolvedModelId);
1435
- logger(`Triggering write process for property "${property.name}" (modelId: ${resolvedModelId}, propertyFileId: ${propertyFileId})`);
1436
- // Check current write state
1437
- const currentWriteState = writeProcess.getSnapshot();
1438
- if (currentWriteState.value === 'success') {
1439
- // Write already succeeded
1440
- void this.completePendingWrite(propertyFileId, resolvedModelId);
1441
- }
1442
- else {
1443
- // Set up subscription to catch future state changes
1444
- const writeSubscription = writeProcess.subscribe((writeSnapshot) => {
1445
- if (writeSnapshot.value === 'success') {
1446
- writeSubscription.unsubscribe();
1447
- logger(`[writeProcess subscription] Write succeeded for property "${property.name}" (propertyFileId: ${propertyFileId})`);
1448
- void this.completePendingWrite(propertyFileId, resolvedModelId);
1449
- }
1450
- else if (writeSnapshot.value === 'error') {
1451
- writeSubscription.unsubscribe();
1452
- const errorContext = writeSnapshot.context;
1453
- logger(`Write process failed for property "${property.name}" (propertyFileId: ${propertyFileId}): ${errorContext.error?.message || 'Unknown error'}`);
1454
- logger(`Write process error details:`, errorContext.error);
1455
- // Mark pending write as error
1456
- this.clearPendingWrite(propertyFileId, 'error');
1457
- }
1458
- });
1459
- }
1460
- const propertyData = {
1461
- modelId: resolvedModelId,
1462
- name: property.name,
1463
- dataType: property.dataType,
1464
- refModelId: property.refModelId,
1465
- refValueType: property.refValueType,
1466
- required: property.required,
1467
- storageType: property.storageType,
1468
- localStorageDir: property.localStorageDir,
1469
- filenameSuffix: property.filenameSuffix,
1470
- };
1471
- service.send({
1472
- type: 'requestWrite',
1473
- data: propertyData,
1474
- });
1475
- }
1476
- else if (retries < maxRetries) {
1477
- retries++;
1478
- setTimeout(checkAndSend, 50); // Retry after 50ms
1479
- }
1480
- else {
1481
- logger(`ERROR: writeProcess not available after ${maxRetries} retries for property "${property.name}" (propertyFileId: ${propertyFileId})`);
1482
- console.error(`[ModelProperty.create] ERROR: writeProcess not available after ${maxRetries} retries for property "${property.name}" (propertyFileId: ${propertyFileId})`);
1483
- // Mark as error if we couldn't even start the write process
1484
- this.clearPendingWrite(propertyFileId, 'error');
1485
- }
1486
- };
1487
- // Start checking after a short delay to allow state machine to initialize
1488
- setTimeout(checkAndSend, 0);
1489
- }
1490
- if (!waitForReady)
1491
- return proxiedInstance;
1492
- return main_cjs.waitForEntityIdle(proxiedInstance, { timeout: readyTimeout }).then(() => proxiedInstance);
1493
- }
1494
- /** Whether an idIndex entry still points at a cached instance with that id. */
1495
- static isIndexEntryCurrent(propertyFileId, entry) {
1496
- if (this.instanceCache.get(entry.cacheKey)?.instance !== entry.instance)
1497
- return false;
1498
- try {
1499
- return entry.instance._getSnapshotContext().id === propertyFileId;
1500
- }
1501
- catch {
1502
- return false;
1503
- }
1504
- }
1505
- /** Add a newly cached instance to idIndex, and keep its entry current when its id changes. */
1506
- static indexCachedInstance(cacheKey, instance) {
1507
- const index = (id) => {
1508
- if (!id)
1509
- return;
1510
- const existing = this.idIndex.get(id);
1511
- // Keep the instance cached first, as the old scan in insertion order returned
1512
- if (existing && existing.instance !== instance && this.isIndexEntryCurrent(id, existing))
1513
- return;
1514
- this.idIndex.set(id, { cacheKey, instance });
1515
- };
1516
- let indexedId = instance._getSnapshotContext().id;
1517
- index(indexedId);
1518
- instance._service.subscribe((snapshot) => {
1519
- const id = snapshot.context.id;
1520
- if (id === indexedId)
1521
- return;
1522
- indexedId = id;
1523
- index(id);
1524
- });
1525
- }
1526
- /**
1527
- * Get ModelProperty instance by propertyFileId from static cache
1528
- */
1529
- static getById(propertyFileId) {
1530
- if (!propertyFileId)
1531
- return undefined;
1532
- const indexed = this.idIndex.get(propertyFileId);
1533
- if (!indexed)
1534
- return undefined;
1535
- if (this.isIndexEntryCurrent(propertyFileId, indexed))
1536
- return indexed.instance;
1537
- // The indexed instance left the cache or changed id; another cached instance may still have it.
1538
- // Cache key might be "modelName:propertyName" or "id:propertyId"
1539
- this.idIndex.delete(propertyFileId);
1540
- for (const [cacheKey, { instance }] of this.instanceCache.entries()) {
1541
- // id is now the schemaFileId (string)
1542
- if (instance._getSnapshotContext().id === propertyFileId) {
1543
- this.idIndex.set(propertyFileId, { cacheKey, instance });
1544
- return instance;
1545
- }
1546
- }
1547
- return undefined;
1548
- }
1549
- /**
1550
- * Create or get ModelProperty instance by propertyFileId
1551
- * Queries the database to find the property if not cached
1552
- */
1553
- static async createById(propertyFileId, options) {
1554
- if (!propertyFileId) {
1555
- return undefined;
1556
- }
1557
- // First, check if we have an instance cached
1558
- const cachedInstance = this.getById(propertyFileId);
1559
- if (cachedInstance) {
1560
- return cachedInstance;
1561
- }
1562
- // Query database to get property data from ID
1563
- const db = main_cjs.BaseDb.getAppDb();
1564
- if (!db) {
1565
- return undefined;
1566
- }
1567
- const evictionEpoch = options?.evictionEpoch ?? main_cjs.currentEvictionEpoch();
1568
- const propertyRecords = await db
1569
- .select()
1570
- .from(main_cjs.properties)
1571
- .where(drizzleOrm.eq(main_cjs.properties.schemaFileId, propertyFileId))
1572
- .limit(1);
1573
- if (propertyRecords.length === 0) {
1574
- return undefined;
1575
- }
1576
- const propertyRecord = propertyRecords[0];
1577
- // Get model name
1578
- const modelRecords = await db
1579
- .select({ name: main_cjs.models.name })
1580
- .from(main_cjs.models)
1581
- .where(drizzleOrm.eq(main_cjs.models.id, propertyRecord.modelId))
1582
- .limit(1);
1583
- if (modelRecords.length === 0) {
1584
- return undefined;
1585
- }
1586
- const modelName = modelRecords[0].name;
1587
- // Build property data
1588
- // id is now the schemaFileId (string), _dbId is the database integer ID
1589
- const propertyData = {
1590
- id: propertyFileId, // schemaFileId (string) - public ID
1591
- _dbId: propertyRecord.id ?? undefined, // Database integer ID - internal only
1592
- name: propertyRecord.name,
1593
- dataType: propertyRecord.dataType,
1594
- modelId: propertyRecord.modelId,
1595
- modelName,
1596
- refModelId: propertyRecord.refModelId || undefined,
1597
- refValueType: propertyRecord.refValueType ? propertyRecord.refValueType : undefined,
1598
- required: propertyRecord.required ?? false,
1599
- storageType: propertyRecord.storageType ?? undefined,
1600
- localStorageDir: propertyRecord.localStorageDir ?? undefined,
1601
- filenameSuffix: propertyRecord.filenameSuffix ?? undefined,
1602
- };
1603
- // Load isEdited from database
1604
- const isEditedFromDb = propertyRecord.isEdited ?? false;
1605
- // Get ref model name if applicable
1606
- if (propertyRecord.refModelId) {
1607
- const refModelRecords = await db
1608
- .select({ name: main_cjs.models.name })
1609
- .from(main_cjs.models)
1610
- .where(drizzleOrm.eq(main_cjs.models.id, propertyRecord.refModelId))
1611
- .limit(1);
1612
- if (refModelRecords.length > 0) {
1613
- propertyData.refModelName = refModelRecords[0].name;
1614
- propertyData.ref = refModelRecords[0].name;
1615
- }
1616
- }
1617
- // Its model's schema was evicted while we read the rows (e.g. a model's property lookup in flight
1618
- // during Schema.destroy or test cleanup): don't bring the property back.
1619
- if (main_cjs.anyEvictionSince(evictionEpoch)) {
1620
- const schemaRows = await db
1621
- .select({ name: main_cjs.schemas.name })
1622
- .from(main_cjs.modelSchemas)
1623
- .innerJoin(main_cjs.schemas, drizzleOrm.eq(main_cjs.schemas.id, main_cjs.modelSchemas.schemaId))
1624
- .where(drizzleOrm.eq(main_cjs.modelSchemas.modelId, propertyRecord.modelId));
1625
- if (schemaRows.length === 0 ||
1626
- schemaRows.some((row) => main_cjs.schemaEvictedSince(row.name, evictionEpoch))) {
1627
- return undefined;
1628
- }
1629
- }
1630
- // Create ModelProperty instance (sync for createById so we can send updateContext)
1631
- const instance = this.create(propertyData, { waitForReady: false });
1632
- // Set isEdited from database after creation
1633
- if (isEditedFromDb) {
1634
- instance._service.send({
1635
- type: 'updateContext',
1636
- _isEdited: true,
1637
- });
1638
- }
1639
- return instance;
1640
- }
1641
- /**
1642
- * Find ModelProperty instance by propertyFileId
1643
- * Waits for the property to be fully loaded (idle state) by default
1644
- * @param options - Find options including propertyFileId and wait configuration
1645
- * @returns ModelProperty instance if found, undefined otherwise
1646
- */
1647
- static async find({ propertyFileId, waitForReady = true, readyTimeout = 5000, }) {
1648
- if (!propertyFileId) {
1649
- return undefined;
1650
- }
1651
- return await main_cjs.findEntity({
1652
- getById: (id) => ModelProperty.getById(id),
1653
- createById: (id) => ModelProperty.createById(id),
1654
- }, { id: propertyFileId }, {
1655
- waitForReady,
1656
- readyTimeout,
1657
- });
1658
- }
1659
- /**
1660
- * Get all ModelProperty instances for a model.
1661
- * Loads property rows from DB for the given modelFileId, creates instances via createById, optionally waits for idle.
1662
- */
1663
- static async all(modelFileId, options) {
1664
- const { waitForReady = false, readyTimeout = 5000 } = options ?? {};
1665
- if (!modelFileId) {
1666
- return [];
1667
- }
1668
- const rows = await getModelPropertiesData(modelFileId);
1669
- const instances = [];
1670
- for (const row of rows) {
1671
- if (row.schemaFileId) {
1672
- const instance = await this.createById(row.schemaFileId);
1673
- if (instance) {
1674
- instances.push(instance);
1675
- }
1676
- }
1677
- }
1678
- if (waitForReady && instances.length > 0) {
1679
- await Promise.all(instances.map((p) => main_cjs.waitForEntityIdle(p, {
1680
- timeout: readyTimeout,
1681
- })));
1682
- }
1683
- return instances;
1684
- }
1685
- /**
1686
- * Track a pending write for a property
1687
- */
1688
- static trackPendingWrite(propertyFileId, modelId) {
1689
- this.pendingWrites.set(propertyFileId, {
1690
- propertyFileId,
1691
- modelId,
1692
- status: 'pending',
1693
- timestamp: Date.now(),
1694
- });
1695
- }
1696
- /**
1697
- * A property write succeeded: add the property to its model's property ids, then drop the pending
1698
- * write. In that order, `Model.properties` (property ids + pending writes) never misses it; in Node,
1699
- * where the Model has no reactive liveQuery, nothing else would add it.
1700
- */
1701
- static async completePendingWrite(propertyFileId, modelId) {
1702
- try {
1703
- await main_cjs.Model.refreshPropertiesForDbId(modelId);
1704
- }
1705
- catch (error) {
1706
- logger(`Refreshing properties of model ${modelId} after writing "${propertyFileId}" failed: ${error}`);
1707
- }
1708
- this.clearPendingWrite(propertyFileId, 'success');
1709
- }
1710
- /**
1711
- * Clear or update pending write status
1712
- */
1713
- static clearPendingWrite(propertyFileId, status = 'success') {
1714
- const write = this.pendingWrites.get(propertyFileId);
1715
- if (write) {
1716
- if (status === 'success') {
1717
- // Remove successful writes from pendingWrites
1718
- this.pendingWrites.delete(propertyFileId);
1719
- logger(`Cleared pending write for property "${propertyFileId}" (status: success)`);
1720
- }
1721
- else {
1722
- // Update status to error but keep in map (for debugging/retry purposes)
1723
- write.status = 'error';
1724
- this.pendingWrites.set(propertyFileId, write);
1725
- logger(`Marked pending write as error for property "${propertyFileId}"`);
1726
- }
1727
- }
1728
- }
1729
- /**
1730
- * Get all pending property IDs for a model
1731
- */
1732
- static getPendingPropertyIds(modelId) {
1733
- return Array.from(this.pendingWrites.entries())
1734
- .filter(([_, write]) => write.modelId === modelId && write.status !== 'error')
1735
- .map(([propertyFileId]) => propertyFileId);
1736
- }
1737
- /**
1738
- * Get modelId for a property that has a pending write (row may not be in DB yet).
1739
- * Used to resolve modelName when validating a just-created property rename.
1740
- */
1741
- static getPendingModelId(propertyFileId) {
1742
- return this.pendingWrites.get(propertyFileId)?.modelId;
1743
- }
1744
- getService() {
1745
- return this._service;
1746
- }
1747
- _getSnapshot() {
1748
- return this._service.getSnapshot();
1749
- }
1750
- _getSnapshotContext() {
1751
- return this._getSnapshot().context;
1752
- }
1753
- get path() {
1754
- const workingDir = main_cjs.BaseFileManager.getWorkingDir();
1755
- if (!this.localStorageDir || !this.name || !this.filenameSuffix) {
1756
- return undefined;
1757
- }
1758
- return `${workingDir}/${this.localStorageDir}/${this.name}${this.filenameSuffix || ''}`;
1759
- }
1760
- get status() {
1761
- return this._getSnapshot().value;
1762
- }
1763
- get isEdited() {
1764
- const context = this._getSnapshotContext();
1765
- // First check in-memory state
1766
- if (context._isEdited !== undefined) {
1767
- return context._isEdited;
1768
- }
1769
- // Fall back to reading from database if we have _dbId
1770
- if (context._dbId) {
1771
- // Load from database asynchronously (fire-and-forget for now)
1772
- // For synchronous getter, we'll need to cache it or make it async
1773
- // For now, return false if not in context
1774
- return false;
1775
- }
1776
- return false;
1777
- }
1778
- get validationErrors() {
1779
- return this._getSnapshotContext()._validationErrors || [];
1780
- }
1781
- get isValid() {
1782
- const errors = this.validationErrors;
1783
- return errors.length === 0;
1784
- }
1785
- /**
1786
- * Validate the property
1787
- * @returns Validation result
1788
- */
1789
- async validate() {
1790
- return new Promise((resolve) => {
1791
- let resolved = false;
1792
- const subscription = this._service.subscribe((snapshot) => {
1793
- const state = snapshot.value;
1794
- // Wait for validation to complete (idle state after validating)
1795
- if (state === 'idle' && !resolved) {
1796
- resolved = true;
1797
- subscription.unsubscribe();
1798
- const errors = snapshot.context._validationErrors || [];
1799
- resolve({
1800
- isValid: errors.length === 0,
1801
- errors,
1802
- });
1803
- }
1804
- });
1805
- this._service.send({ type: 'validateProperty' });
1806
- // Timeout fallback
1807
- setTimeout(() => {
1808
- if (!resolved) {
1809
- resolved = true;
1810
- subscription.unsubscribe();
1811
- const errors = this._getSnapshotContext()._validationErrors || [];
1812
- resolve({
1813
- isValid: errors.length === 0,
1814
- errors,
1815
- });
1816
- }
1817
- }, 5000);
1818
- });
1819
- }
1820
- save() {
1821
- const snapshot = this._service.getSnapshot();
1822
- const isAlreadySaving = typeof snapshot.value === 'object' && snapshot.value !== null && 'saveToSchema' in snapshot.value;
1823
- if (isAlreadySaving) {
1824
- return;
1825
- }
1826
- const context = snapshot.context;
1827
- if (!context._isEdited)
1828
- return; // No changes to persist
1829
- // Validation will happen automatically in the machine before saving
1830
- this._service.send({
1831
- type: 'saveToSchema',
1832
- });
1833
- }
1834
- /**
1835
- * Reload property from database
1836
- * This refreshes the actor context with the latest data from the database
1837
- * Note: ModelProperty doesn't have a dedicated load actor, so this will
1838
- * re-initialize from the current property data
1839
- */
1840
- async reload() {
1841
- // ModelProperty doesn't have a separate load mechanism
1842
- // It's loaded as part of the Model/Schema
1843
- // This method is provided for API consistency
1844
- // To actually reload, you'd need to reload the parent Model or Schema
1845
- logger('ModelProperty.reload() called - ModelProperty is loaded as part of Model/Schema. Reload the parent Model or Schema instead.');
1846
- // No-op for now, but could be enhanced to reload from DB if needed
1847
- }
1848
- /**
1849
- * Force-evict cached ModelProperty instances for the given models, ignoring refCounts, and stop
1850
- * their services. Used by Schema.destroy() so a re-imported schema gets fresh instances instead of
1851
- * ones holding a deleted row's _dbId.
1852
- */
1853
- static evictForModels(modelNames, schemaName) {
1854
- if (modelNames.length === 0)
1855
- return;
1856
- const names = new Set(modelNames);
1857
- for (const [cacheKey, { instance }] of this.instanceCache.entries()) {
1858
- let context;
1859
- try {
1860
- context = instance._getSnapshotContext();
1861
- }
1862
- catch {
1863
- continue;
1864
- }
1865
- if (!context.modelName || !names.has(context.modelName))
1866
- continue;
1867
- if (schemaName && context._schemaName && context._schemaName !== schemaName)
1868
- continue;
1869
- this.instanceCache.delete(cacheKey);
1870
- try {
1871
- instance._service.stop();
1872
- }
1873
- catch {
1874
- // Service might already be stopped
1875
- }
1876
- }
1877
- }
1878
- unload() {
1879
- // ModelProperty doesn't have liveQuery subscriptions or complex cache management
1880
- // Just stop the service
1881
- try {
1882
- this._service.stop();
1883
- }
1884
- catch (error) {
1885
- // Service might already be stopped
1886
- }
1887
- }
1888
- /**
1889
- * Destroy the model property: remove from caches, delete from database, update Schema context, stop service.
1890
- */
1891
- async destroy() {
1892
- const context = this._getSnapshotContext();
1893
- // Stored under "modelName:name", or "id:<id>" when another schema's same-name property holds that key
1894
- const cacheKeys = [...ModelProperty.instanceCache.entries()]
1895
- .filter(([, entry]) => {
1896
- try {
1897
- return entry.instance._getSnapshotContext().id === context.id;
1898
- }
1899
- catch {
1900
- return false;
1901
- }
1902
- })
1903
- .map(([key]) => key);
1904
- if (cacheKeys.length === 0 && context.modelName && context.name) {
1905
- cacheKeys.push(`${context.modelName}:${context.name}`);
1906
- }
1907
- if (cacheKeys.length === 0)
1908
- return;
1909
- main_cjs.forceRemoveFromCaches(this, {
1910
- getCacheKeys: () => cacheKeys,
1911
- caches: [ModelProperty.instanceCache],
1912
- });
1913
- await main_cjs.runDestroyLifecycle(this, {
1914
- getService: (instance) => instance._service,
1915
- doDestroy: async () => {
1916
- const db = main_cjs.BaseDb.getAppDb();
1917
- let schemaName = context._schemaName;
1918
- const modelName = context.modelName;
1919
- const propertyName = context.name;
1920
- if (!modelName || !propertyName)
1921
- return;
1922
- // Resolve schema name from DB when _schemaName is missing (e.g. destroy ran before _setSchemaName finished)
1923
- if (!schemaName && db) {
1924
- schemaName = await this._resolveSchemaNameForDestroy(db, context);
1925
- }
1926
- if (db && schemaName) {
1927
- const propertyRecords = await db
1928
- .select({ propertyId: main_cjs.properties.id })
1929
- .from(main_cjs.properties)
1930
- .innerJoin(main_cjs.models, drizzleOrm.eq(main_cjs.properties.modelId, main_cjs.models.id))
1931
- .innerJoin(main_cjs.modelSchemas, drizzleOrm.eq(main_cjs.models.id, main_cjs.modelSchemas.modelId))
1932
- .innerJoin(main_cjs.schemas, drizzleOrm.eq(main_cjs.modelSchemas.schemaId, main_cjs.schemas.id))
1933
- .where(drizzleOrm.and(drizzleOrm.eq(main_cjs.schemas.name, schemaName), drizzleOrm.eq(main_cjs.models.name, modelName), drizzleOrm.eq(main_cjs.properties.name, propertyName)))
1934
- .limit(1);
1935
- if (propertyRecords.length > 0 && propertyRecords[0].propertyId != null) {
1936
- await db
1937
- .delete(main_cjs.properties)
1938
- .where(drizzleOrm.eq(main_cjs.properties.id, propertyRecords[0].propertyId));
1939
- }
1940
- }
1941
- if (schemaName) {
1942
- const schema = Schema.Schema.create(schemaName, { waitForReady: false });
1943
- const snapshot = schema.getService().getSnapshot();
1944
- const schemaContext = snapshot.context;
1945
- if (schemaContext.models?.[modelName]?.properties?.[propertyName]) {
1946
- const updatedModels = { ...schemaContext.models };
1947
- const updatedProperties = { ...updatedModels[modelName].properties };
1948
- delete updatedProperties[propertyName];
1949
- updatedModels[modelName] = { ...updatedModels[modelName], properties: updatedProperties };
1950
- schema.getService().send({ type: 'updateContext', models: updatedModels });
1951
- schema.getService().send({ type: 'markAsDraft', propertyKey: `property:${modelName}:${propertyName}` });
1952
- }
1953
- }
1954
- },
1955
- });
1956
- }
1957
- }
1958
- ModelProperty.instanceCache = new Map();
1959
- /**
1960
- * propertyFileId -> where the instance with that id is cached, so getById doesn't scan the cache
1961
- * (a model's properties are looked up one by one, which made loading one model O(n²)).
1962
- * Every cached instance's current id has an entry: it's added when the instance is cached and
1963
- * again whenever its context id changes (a property is often created with a generated id and
1964
- * then gets its real one). Entries aren't removed when an instance leaves the cache or changes
1965
- * id; getById checks an entry before using it.
1966
- */
1967
- ModelProperty.idIndex = new Map();
1968
- // Pending writes tracking
1969
- ModelProperty.pendingWrites = new Map();
1970
-
1971
- var ModelProperty$1 = /*#__PURE__*/Object.freeze({
1972
- __proto__: null,
1973
- ModelProperty: ModelProperty
1974
- });
1975
-
1976
- exports.ModelProperty = ModelProperty;
1977
- //# sourceMappingURL=ModelProperty-B-cZm1yU.js.map