@seedprotocol/sdk 0.7.9-next.1189.g5e63267 → 0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (581) hide show
  1. package/dist/BaseFileManager-BAlySv8w.js +650 -0
  2. package/dist/BaseFileManager-BAlySv8w.js.map +1 -0
  3. package/dist/{Item-CG5IhPtL.js → Item-Bf4kWKpH.js} +5576 -6649
  4. package/dist/Item-Bf4kWKpH.js.map +1 -0
  5. package/dist/{ModelProperty-DDD47KVm.js → ModelProperty-BlaEgb_U.js} +345 -709
  6. package/dist/ModelProperty-BlaEgb_U.js.map +1 -0
  7. package/dist/Schema-J_1f6p0c.js +1456 -0
  8. package/dist/Schema-J_1f6p0c.js.map +1 -0
  9. package/dist/{SchemaValidationService-CZzKNL04.js → SchemaValidationService-CnmKyzR-.js} +6 -9
  10. package/dist/{SchemaValidationService-CZzKNL04.js.map → SchemaValidationService-CnmKyzR-.js.map} +1 -1
  11. package/dist/{configurePlatform-Dukm2QwO.js → configurePlatform-CiOJ1R3q.js} +3 -12
  12. package/dist/configurePlatform-CiOJ1R3q.js.map +1 -0
  13. package/dist/db/drizzle/0026_add_content_hash_to_schemas.sql +1 -0
  14. package/dist/db/drizzle/0027_link_metadata_property_ids.sql +74 -0
  15. package/dist/db/drizzle/0028_add_definition_to_properties.sql +17 -0
  16. package/dist/db/drizzle/0029_dedupe_chain_uids_add_unique_indexes.sql +120 -0
  17. package/dist/db/drizzle/meta/0026_snapshot.json +1659 -0
  18. package/dist/db/drizzle/meta/0027_snapshot.json +1659 -0
  19. package/dist/db/drizzle/meta/0028_snapshot.json +1666 -0
  20. package/dist/db/drizzle/meta/0029_snapshot.json +1691 -0
  21. package/dist/db/drizzle/meta/_journal.json +29 -1
  22. package/dist/{detachRowsBeforeDelete-Di1enVUF.js → detachRowsBeforeDelete-sEaUwlOU.js} +2 -13
  23. package/dist/{detachRowsBeforeDelete-Di1enVUF.js.map → detachRowsBeforeDelete-sEaUwlOU.js.map} +1 -1
  24. package/dist/dist/{index-rOTW-2QZ.d.ts → BaseFileManager-BAlySv8w.d.ts} +1407 -2700
  25. package/dist/dist/BaseFileManager-BAlySv8w.d.ts.map +1 -0
  26. package/dist/dist/{configurePlatform-Dukm2QwO.d.ts → configurePlatform-CiOJ1R3q.d.ts} +1 -1
  27. package/dist/dist/configurePlatform-CiOJ1R3q.d.ts.map +1 -0
  28. package/dist/dist/{index-DxHpPNfn.d.ts → index-D0V-mjCV.d.ts} +39 -1
  29. package/dist/dist/{index-DxHpPNfn.d.ts.map → index-D0V-mjCV.d.ts.map} +1 -1
  30. package/dist/dist/{index.node-WsEJCOwC.d.ts → index.node-CYPemH-t.d.ts} +157 -5
  31. package/dist/dist/{index.node-WsEJCOwC.d.ts.map → index.node-CYPemH-t.d.ts.map} +1 -1
  32. package/dist/dist/platform.node.d.ts +2 -2
  33. package/dist/eas-wB_fQ8Tc.js +32 -0
  34. package/dist/eas-wB_fQ8Tc.js.map +1 -0
  35. package/dist/errors-DLSoqSHG.js +6550 -0
  36. package/dist/errors-DLSoqSHG.js.map +1 -0
  37. package/dist/getPublishPayload-BnIwpwz1.js +760 -0
  38. package/dist/getPublishPayload-BnIwpwz1.js.map +1 -0
  39. package/dist/getPublishPendingDiff-D6_-A5ns.js +125 -0
  40. package/dist/getPublishPendingDiff-D6_-A5ns.js.map +1 -0
  41. package/dist/getPublishUploads-sS6JfPgz.js +783 -0
  42. package/dist/getPublishUploads-sS6JfPgz.js.map +1 -0
  43. package/dist/{index-DQoyhp2c.js → index-BVBmz_cc.js} +11 -14
  44. package/dist/index-BVBmz_cc.js.map +1 -0
  45. package/dist/index-CYGWPoOx.js +27 -0
  46. package/dist/index-CYGWPoOx.js.map +1 -0
  47. package/dist/{index-DxHpPNfn.js → index-D0V-mjCV.js} +2 -2
  48. package/dist/index-D0V-mjCV.js.map +1 -0
  49. package/dist/index-DmTtj1nk.js +817 -0
  50. package/dist/index-DmTtj1nk.js.map +1 -0
  51. package/dist/{index.browser-QPWBkLJA.js → index.browser-CUmBVYen.js} +397 -356
  52. package/dist/index.browser-CUmBVYen.js.map +1 -0
  53. package/dist/{index.node-WsEJCOwC.js → index.node-CYPemH-t.js} +10 -10
  54. package/dist/index.node-CYPemH-t.js.map +1 -0
  55. package/dist/main.js +23 -1075
  56. package/dist/main.js.map +1 -1
  57. package/dist/modelPropertyRecordSchemas-C7UgA5TH.js +48 -0
  58. package/dist/modelPropertyRecordSchemas-C7UgA5TH.js.map +1 -0
  59. package/dist/node.js +14 -18
  60. package/dist/node.js.map +1 -1
  61. package/dist/platform.browser.js +8 -13
  62. package/dist/platform.browser.js.map +1 -1
  63. package/dist/platform.node.js +6 -16
  64. package/dist/platform.node.js.map +1 -1
  65. package/dist/property-BxUYznH0.js +87 -0
  66. package/dist/property-BxUYznH0.js.map +1 -0
  67. package/dist/publishGraph-EcB40Uii.js +311 -0
  68. package/dist/publishGraph-EcB40Uii.js.map +1 -0
  69. package/dist/readHtmlBodyForStorageSeed-D6KPtWoR.js +110 -0
  70. package/dist/readHtmlBodyForStorageSeed-D6KPtWoR.js.map +1 -0
  71. package/dist/seedSchema/ModelSchema.ts +4 -0
  72. package/dist/seedSchema/ModelSchemaSchema.ts +5 -2
  73. package/dist/seedSchema/PendingWriteSchema.ts +4 -1
  74. package/dist/seedSchema/SchemaSchema.ts +3 -0
  75. package/dist/seedSchema/SeedSchema.ts +8 -2
  76. package/dist/seedSchema/VersionSchema.ts +7 -2
  77. package/dist/seedSchema/attestedUid.ts +10 -0
  78. package/dist/src/Item/Item.d.ts +32 -30
  79. package/dist/src/Item/Item.d.ts.map +1 -1
  80. package/dist/src/Item/loadItem.d.ts +12 -0
  81. package/dist/src/Item/loadItem.d.ts.map +1 -0
  82. package/dist/src/Item/service/itemMachineSingle.d.ts +2 -13
  83. package/dist/src/Item/service/itemMachineSingle.d.ts.map +1 -1
  84. package/dist/src/ItemProperty/ItemProperty.d.ts +77 -46
  85. package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
  86. package/dist/src/ItemProperty/loadProperty.d.ts +11 -0
  87. package/dist/src/ItemProperty/loadProperty.d.ts.map +1 -0
  88. package/dist/src/ItemProperty/propertyWrite.d.ts +54 -0
  89. package/dist/src/ItemProperty/propertyWrite.d.ts.map +1 -0
  90. package/dist/src/ItemProperty/{service/actors/saveValueToDb/rowContext.d.ts → rowContext.d.ts} +1 -1
  91. package/dist/src/ItemProperty/rowContext.d.ts.map +1 -0
  92. package/dist/src/ItemProperty/service/actors/hydrateFromDb.d.ts +6 -0
  93. package/dist/src/ItemProperty/service/actors/hydrateFromDb.d.ts.map +1 -1
  94. package/dist/src/ItemProperty/service/actors/modelPropertyRecordSchemas.d.ts +5 -15
  95. package/dist/src/ItemProperty/service/actors/modelPropertyRecordSchemas.d.ts.map +1 -1
  96. package/dist/src/ItemProperty/service/propertyMachine.d.ts +4 -50
  97. package/dist/src/ItemProperty/service/propertyMachine.d.ts.map +1 -1
  98. package/dist/src/Model/Model.d.ts +26 -56
  99. package/dist/src/Model/Model.d.ts.map +1 -1
  100. package/dist/src/Model/createModelProperties.d.ts +10 -0
  101. package/dist/src/Model/createModelProperties.d.ts.map +1 -0
  102. package/dist/src/Model/loadModel.d.ts +13 -0
  103. package/dist/src/Model/loadModel.d.ts.map +1 -0
  104. package/dist/src/Model/service/actors/validateModel.d.ts.map +1 -1
  105. package/dist/src/Model/service/modelMachine.d.ts +5 -114
  106. package/dist/src/Model/service/modelMachine.d.ts.map +1 -1
  107. package/dist/src/ModelProperty/ModelProperty.d.ts +44 -37
  108. package/dist/src/ModelProperty/ModelProperty.d.ts.map +1 -1
  109. package/dist/src/ModelProperty/service/actors/compareAndMarkDraft.d.ts.map +1 -1
  110. package/dist/src/ModelProperty/service/actors/validateProperty.d.ts.map +1 -1
  111. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +6 -77
  112. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts.map +1 -1
  113. package/dist/src/Schema/Schema.d.ts +36 -37
  114. package/dist/src/Schema/Schema.d.ts.map +1 -1
  115. package/dist/src/Schema/loadOrCreateSchema.d.ts +21 -0
  116. package/dist/src/Schema/loadOrCreateSchema.d.ts.map +1 -0
  117. package/dist/src/Schema/service/schemaMachine.d.ts +7 -125
  118. package/dist/src/Schema/service/schemaMachine.d.ts.map +1 -1
  119. package/dist/src/browser/db/Db.d.ts.map +1 -1
  120. package/dist/src/browser/db/changeFeed.d.ts +2 -18
  121. package/dist/src/browser/db/changeFeed.d.ts.map +1 -1
  122. package/dist/src/browser/db/drizzleFiles.d.ts +6 -2
  123. package/dist/src/browser/db/drizzleFiles.d.ts.map +1 -1
  124. package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
  125. package/dist/src/browser/workers/imageResize.d.ts.map +1 -1
  126. package/dist/src/client/ClientManager.d.ts +90 -57
  127. package/dist/src/client/ClientManager.d.ts.map +1 -1
  128. package/dist/src/client/actors/addModelsToDb.d.ts +9 -7
  129. package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
  130. package/dist/src/client/actors/dbInit.d.ts +3 -3
  131. package/dist/src/client/actors/dbInit.d.ts.map +1 -1
  132. package/dist/src/client/actors/fileSystemInit.d.ts +3 -3
  133. package/dist/src/client/actors/fileSystemInit.d.ts.map +1 -1
  134. package/dist/src/client/actors/platformClassesInit.d.ts +10 -5
  135. package/dist/src/client/actors/platformClassesInit.d.ts.map +1 -1
  136. package/dist/src/client/actors/processSchemaFiles.d.ts +18 -3
  137. package/dist/src/client/actors/processSchemaFiles.d.ts.map +1 -1
  138. package/dist/src/client/actors/saveConfig.d.ts +6 -3
  139. package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
  140. package/dist/src/client/clientManagerMachine.d.ts +22 -20
  141. package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
  142. package/dist/src/client/constants.d.ts +1 -9
  143. package/dist/src/client/constants.d.ts.map +1 -1
  144. package/dist/src/client/readiness.d.ts +26 -0
  145. package/dist/src/client/readiness.d.ts.map +1 -0
  146. package/dist/src/db/read/currentPropertyRow.d.ts +61 -0
  147. package/dist/src/db/read/currentPropertyRow.d.ts.map +1 -0
  148. package/dist/src/db/read/findSeedRow.d.ts +20 -0
  149. package/dist/src/db/read/findSeedRow.d.ts.map +1 -0
  150. package/dist/src/db/read/findSeedRowsByUid.d.ts +4 -1
  151. package/dist/src/db/read/findSeedRowsByUid.d.ts.map +1 -1
  152. package/dist/src/db/read/getAttesterForSeed.d.ts +1 -1
  153. package/dist/src/db/read/getAttesterForSeed.d.ts.map +1 -1
  154. package/dist/src/db/read/getItemData.d.ts.map +1 -1
  155. package/dist/src/db/read/getItemProperties.d.ts +5 -0
  156. package/dist/src/db/read/getItemProperties.d.ts.map +1 -1
  157. package/dist/src/db/read/getMetadataAttestationUidsForSeedUid.d.ts.map +1 -1
  158. package/dist/src/db/read/getModelSchemas.d.ts +0 -7
  159. package/dist/src/db/read/getModelSchemas.d.ts.map +1 -1
  160. package/dist/src/db/read/getModelsData.d.ts.map +1 -1
  161. package/dist/src/db/read/getPropertyClear.d.ts +5 -1
  162. package/dist/src/db/read/getPropertyClear.d.ts.map +1 -1
  163. package/dist/src/db/read/getPublishPayload.d.ts +35 -40
  164. package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
  165. package/dist/src/db/read/getPublishPendingDiff.d.ts +2 -2
  166. package/dist/src/db/read/getPublishPendingDiff.d.ts.map +1 -1
  167. package/dist/src/db/read/getPublishUploads.d.ts +59 -28
  168. package/dist/src/db/read/getPublishUploads.d.ts.map +1 -1
  169. package/dist/src/db/read/getSchemaUidForModel.d.ts.map +1 -1
  170. package/dist/src/db/read/getSeedPublishState.d.ts.map +1 -1
  171. package/dist/src/db/read/getSeedPublisher.d.ts.map +1 -1
  172. package/dist/src/db/read/getVersionsForSeedUid.d.ts.map +1 -1
  173. package/dist/src/db/read/isSeedRevoked.d.ts.map +1 -1
  174. package/dist/src/db/read/localModelSchemaLookups.d.ts +8 -0
  175. package/dist/src/db/read/localModelSchemaLookups.d.ts.map +1 -0
  176. package/dist/src/db/read/metadataProperty.d.ts +104 -0
  177. package/dist/src/db/read/metadataProperty.d.ts.map +1 -0
  178. package/dist/src/db/read/publishGraph.d.ts +148 -0
  179. package/dist/src/db/read/publishGraph.d.ts.map +1 -0
  180. package/dist/src/db/read/resolveModelRecord.d.ts +26 -2
  181. package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -1
  182. package/dist/src/db/read/resolveRelatedSeedRef.d.ts +1 -7
  183. package/dist/src/db/read/resolveRelatedSeedRef.d.ts.map +1 -1
  184. package/dist/src/db/read/subqueries/metadataLatest.d.ts +3 -1
  185. package/dist/src/db/read/subqueries/metadataLatest.d.ts.map +1 -1
  186. package/dist/src/db/read/summarizePublishWork.d.ts +13 -5
  187. package/dist/src/db/read/summarizePublishWork.d.ts.map +1 -1
  188. package/dist/src/db/write/applyPropertyAttestationUidsFromPublish.d.ts +20 -21
  189. package/dist/src/db/write/applyPropertyAttestationUidsFromPublish.d.ts.map +1 -1
  190. package/dist/src/db/write/createMetadata.d.ts.map +1 -1
  191. package/dist/src/db/write/createNewItem.d.ts.map +1 -1
  192. package/dist/src/db/write/createSeeds.d.ts +5 -1
  193. package/dist/src/db/write/createSeeds.d.ts.map +1 -1
  194. package/dist/src/db/write/definitions.d.ts +127 -0
  195. package/dist/src/db/write/definitions.d.ts.map +1 -0
  196. package/dist/src/db/write/mergeChainRows.d.ts +22 -0
  197. package/dist/src/db/write/mergeChainRows.d.ts.map +1 -0
  198. package/dist/src/db/write/saveDefinition.d.ts +29 -0
  199. package/dist/src/db/write/saveDefinition.d.ts.map +1 -0
  200. package/dist/src/db/write/updateSeedUid.d.ts +4 -2
  201. package/dist/src/db/write/updateSeedUid.d.ts.map +1 -1
  202. package/dist/src/db/write/updateVersionUid.d.ts +12 -4
  203. package/dist/src/db/write/updateVersionUid.d.ts.map +1 -1
  204. package/dist/src/db/write/writeModelUids.d.ts +10 -0
  205. package/dist/src/db/write/writeModelUids.d.ts.map +1 -0
  206. package/dist/src/eas.d.ts +8 -6
  207. package/dist/src/eas.d.ts.map +1 -1
  208. package/dist/src/events/files/download.d.ts.map +1 -1
  209. package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
  210. package/dist/src/helpers/arweaveContentTags.d.ts +1 -1
  211. package/dist/src/helpers/arweaveContentTags.d.ts.map +1 -1
  212. package/dist/src/helpers/compareMetadataRowsLatestFirst.d.ts +18 -3
  213. package/dist/src/helpers/compareMetadataRowsLatestFirst.d.ts.map +1 -1
  214. package/dist/src/helpers/constants.d.ts +5 -2
  215. package/dist/src/helpers/constants.d.ts.map +1 -1
  216. package/dist/src/helpers/dataUrl.d.ts +10 -0
  217. package/dist/src/helpers/dataUrl.d.ts.map +1 -0
  218. package/dist/src/helpers/db.d.ts +54 -105
  219. package/dist/src/helpers/db.d.ts.map +1 -1
  220. package/dist/src/helpers/ensureImageLocal.d.ts.map +1 -1
  221. package/dist/src/helpers/entity/contextUpdate.d.ts +20 -0
  222. package/dist/src/helpers/entity/contextUpdate.d.ts.map +1 -0
  223. package/dist/src/helpers/entity/draftSaveErrors.d.ts +5 -0
  224. package/dist/src/helpers/entity/draftSaveErrors.d.ts.map +1 -1
  225. package/dist/src/helpers/entity/entityCache.d.ts +65 -3
  226. package/dist/src/helpers/entity/entityCache.d.ts.map +1 -1
  227. package/dist/src/helpers/entity/entityCommon.d.ts +0 -22
  228. package/dist/src/helpers/entity/entityCommon.d.ts.map +1 -1
  229. package/dist/src/helpers/entity/entityLifecycle.d.ts +75 -0
  230. package/dist/src/helpers/entity/entityLifecycle.d.ts.map +1 -0
  231. package/dist/src/helpers/entity/entityLiveQuery.d.ts +1 -23
  232. package/dist/src/helpers/entity/entityLiveQuery.d.ts.map +1 -1
  233. package/dist/src/helpers/htmlEmbeddedDataUriPublish.d.ts +4 -9
  234. package/dist/src/helpers/htmlEmbeddedDataUriPublish.d.ts.map +1 -1
  235. package/dist/src/helpers/index.d.ts +2 -24
  236. package/dist/src/helpers/index.d.ts.map +1 -1
  237. package/dist/src/helpers/localImageBlobUrl.d.ts +7 -0
  238. package/dist/src/helpers/localImageBlobUrl.d.ts.map +1 -0
  239. package/dist/src/helpers/mediaRef.d.ts +3 -70
  240. package/dist/src/helpers/mediaRef.d.ts.map +1 -1
  241. package/dist/src/helpers/metadataPropertyNames.d.ts +34 -26
  242. package/dist/src/helpers/metadataPropertyNames.d.ts.map +1 -1
  243. package/dist/src/helpers/mimeTypes.d.ts +2 -1
  244. package/dist/src/helpers/mimeTypes.d.ts.map +1 -1
  245. package/dist/src/helpers/model.d.ts.map +1 -1
  246. package/dist/src/helpers/ownership.d.ts.map +1 -1
  247. package/dist/src/helpers/property.d.ts +21 -26
  248. package/dist/src/helpers/property.d.ts.map +1 -1
  249. package/dist/src/helpers/propertyDefinition.d.ts +59 -0
  250. package/dist/src/helpers/propertyDefinition.d.ts.map +1 -0
  251. package/dist/src/helpers/readHtmlBodyForStorageSeed.d.ts.map +1 -1
  252. package/dist/src/helpers/relationSeedRef.d.ts +0 -7
  253. package/dist/src/helpers/relationSeedRef.d.ts.map +1 -1
  254. package/dist/src/helpers/schema.d.ts +24 -38
  255. package/dist/src/helpers/schema.d.ts.map +1 -1
  256. package/dist/src/helpers/schemaApply.d.ts +135 -0
  257. package/dist/src/helpers/schemaApply.d.ts.map +1 -0
  258. package/dist/src/helpers/tabEvents.d.ts.map +1 -1
  259. package/dist/src/helpers/updateSchema.d.ts +2 -18
  260. package/dist/src/helpers/updateSchema.d.ts.map +1 -1
  261. package/dist/src/imports/index.d.ts +1 -1
  262. package/dist/src/imports/index.d.ts.map +1 -1
  263. package/dist/src/imports/json.d.ts +8 -78
  264. package/dist/src/imports/json.d.ts.map +1 -1
  265. package/dist/src/index.d.ts +13 -13
  266. package/dist/src/index.d.ts.map +1 -1
  267. package/dist/src/interfaces/IItem.d.ts +5 -0
  268. package/dist/src/interfaces/IItem.d.ts.map +1 -1
  269. package/dist/src/interfaces/IItemProperty.d.ts +9 -7
  270. package/dist/src/interfaces/IItemProperty.d.ts.map +1 -1
  271. package/dist/src/node/db/Db.d.ts.map +1 -1
  272. package/dist/src/node/index.d.ts +2 -1
  273. package/dist/src/node/index.d.ts.map +1 -1
  274. package/dist/src/outbox/conflicts.d.ts +2 -0
  275. package/dist/src/outbox/conflicts.d.ts.map +1 -1
  276. package/dist/src/outbox/itemAppliers.d.ts +63 -14
  277. package/dist/src/outbox/itemAppliers.d.ts.map +1 -1
  278. package/dist/src/outbox/outbox.d.ts +14 -3
  279. package/dist/src/outbox/outbox.d.ts.map +1 -1
  280. package/dist/src/outbox/pendingValues.d.ts +3 -1
  281. package/dist/src/outbox/pendingValues.d.ts.map +1 -1
  282. package/dist/src/outbox/storageValueRow.d.ts +7 -0
  283. package/dist/src/outbox/storageValueRow.d.ts.map +1 -0
  284. package/dist/src/platform/configurePlatform.d.ts.map +1 -1
  285. package/dist/src/platform/index.browser.d.ts.map +1 -1
  286. package/dist/src/platform/index.node.d.ts.map +1 -1
  287. package/dist/src/seedSchema/ModelSchema.d.ts +19 -0
  288. package/dist/src/seedSchema/ModelSchema.d.ts.map +1 -1
  289. package/dist/src/seedSchema/ModelSchemaSchema.d.ts.map +1 -1
  290. package/dist/src/seedSchema/PendingWriteSchema.d.ts.map +1 -1
  291. package/dist/src/seedSchema/SchemaSchema.d.ts +19 -0
  292. package/dist/src/seedSchema/SchemaSchema.d.ts.map +1 -1
  293. package/dist/src/seedSchema/SeedSchema.d.ts.map +1 -1
  294. package/dist/src/seedSchema/VersionSchema.d.ts.map +1 -1
  295. package/dist/src/seedSchema/attestedUid.d.ts +9 -0
  296. package/dist/src/seedSchema/attestedUid.d.ts.map +1 -0
  297. package/dist/src/storageValue/content.d.ts +45 -0
  298. package/dist/src/storageValue/content.d.ts.map +1 -0
  299. package/dist/src/storageValue/findFile.d.ts +7 -0
  300. package/dist/src/storageValue/findFile.d.ts.map +1 -0
  301. package/dist/src/storageValue/resolve.d.ts +71 -0
  302. package/dist/src/storageValue/resolve.d.ts.map +1 -0
  303. package/dist/src/storageValue/save.d.ts +24 -0
  304. package/dist/src/storageValue/save.d.ts.map +1 -0
  305. package/dist/src/storageValue/stage.d.ts +22 -0
  306. package/dist/src/storageValue/stage.d.ts.map +1 -0
  307. package/dist/src/types/db.d.ts +0 -14
  308. package/dist/src/types/db.d.ts.map +1 -1
  309. package/dist/src/types/index.d.ts +3 -3
  310. package/dist/src/types/index.d.ts.map +1 -1
  311. package/dist/src/types/machines.d.ts +1 -1
  312. package/dist/src/types/machines.d.ts.map +1 -1
  313. package/dist/src/types/property.d.ts +9 -24
  314. package/dist/src/types/property.d.ts.map +1 -1
  315. package/dist/src/types/publish.d.ts +1 -1
  316. package/dist/src/types/publish.d.ts.map +1 -1
  317. package/dist/{syncDbWithEas-BMHoou6X.js → syncDbWithEas-CA7PvBgu.js} +112 -162
  318. package/dist/syncDbWithEas-CA7PvBgu.js.map +1 -0
  319. package/dist/tabEvents-CaVmngqr.js +3804 -0
  320. package/dist/tabEvents-CaVmngqr.js.map +1 -0
  321. package/dist/{tolerantWebAccess-BHndShch.js → tolerantWebAccess-BJqViq08.js} +10 -15
  322. package/dist/{tolerantWebAccess-BHndShch.js.map → tolerantWebAccess-BJqViq08.js.map} +1 -1
  323. package/dist/{resolveModelForSyncedSeed-fIURvYRV.js → updateSeedRevokedAt-BL9Bg-G7.js} +30 -4
  324. package/dist/updateSeedRevokedAt-BL9Bg-G7.js.map +1 -0
  325. package/package.json +6 -6
  326. package/dist/ClientManager-DqL0q_eu.js +0 -5292
  327. package/dist/ClientManager-DqL0q_eu.js.map +0 -1
  328. package/dist/Item-CG5IhPtL.js.map +0 -1
  329. package/dist/ModelProperty-DDD47KVm.js.map +0 -1
  330. package/dist/Schema-BS8oR7QV.js +0 -4370
  331. package/dist/Schema-BS8oR7QV.js.map +0 -1
  332. package/dist/backfillMetadataPropertyIds-C7P7FL6F.js +0 -72
  333. package/dist/backfillMetadataPropertyIds-C7P7FL6F.js.map +0 -1
  334. package/dist/changeFeed-M8sGbOrW.js +0 -726
  335. package/dist/changeFeed-M8sGbOrW.js.map +0 -1
  336. package/dist/configurePlatform-Dukm2QwO.js.map +0 -1
  337. package/dist/dist/ClientManager-DqL0q_eu.d.ts +0 -952
  338. package/dist/dist/ClientManager-DqL0q_eu.d.ts.map +0 -1
  339. package/dist/dist/Item-CG5IhPtL.d.ts +0 -1115
  340. package/dist/dist/Item-CG5IhPtL.d.ts.map +0 -1
  341. package/dist/dist/ModelProperty-DDD47KVm.d.ts +0 -315
  342. package/dist/dist/ModelProperty-DDD47KVm.d.ts.map +0 -1
  343. package/dist/dist/Schema-BS8oR7QV.d.ts +0 -432
  344. package/dist/dist/Schema-BS8oR7QV.d.ts.map +0 -1
  345. package/dist/dist/SchemaValidationService-CZzKNL04.d.ts +0 -118
  346. package/dist/dist/SchemaValidationService-CZzKNL04.d.ts.map +0 -1
  347. package/dist/dist/backfillMetadataPropertyIds-C7P7FL6F.d.ts +0 -7
  348. package/dist/dist/backfillMetadataPropertyIds-C7P7FL6F.d.ts.map +0 -1
  349. package/dist/dist/changeFeed-M8sGbOrW.d.ts +0 -160
  350. package/dist/dist/changeFeed-M8sGbOrW.d.ts.map +0 -1
  351. package/dist/dist/configurePlatform-Dukm2QwO.d.ts.map +0 -1
  352. package/dist/dist/detachRowsBeforeDelete-Di1enVUF.d.ts +0 -13
  353. package/dist/dist/detachRowsBeforeDelete-Di1enVUF.d.ts.map +0 -1
  354. package/dist/dist/eas-B5VJolSj.d.ts +0 -12
  355. package/dist/dist/eas-B5VJolSj.d.ts.map +0 -1
  356. package/dist/dist/errors-C1qUEZ7A.d.ts +0 -25
  357. package/dist/dist/errors-C1qUEZ7A.d.ts.map +0 -1
  358. package/dist/dist/errors-CS3nDpUU.d.ts +0 -515
  359. package/dist/dist/errors-CS3nDpUU.d.ts.map +0 -1
  360. package/dist/dist/eventBus-7vxTCc3a.d.ts +0 -18
  361. package/dist/dist/eventBus-7vxTCc3a.d.ts.map +0 -1
  362. package/dist/dist/findSeedRowsByUid-CuQmK7An.d.ts +0 -13
  363. package/dist/dist/findSeedRowsByUid-CuQmK7An.d.ts.map +0 -1
  364. package/dist/dist/getItem-Uha3_H0t.d.ts +0 -6
  365. package/dist/dist/getItem-Uha3_H0t.d.ts.map +0 -1
  366. package/dist/dist/getPublishPayload-pbin4lk8.d.ts +0 -65
  367. package/dist/dist/getPublishPayload-pbin4lk8.d.ts.map +0 -1
  368. package/dist/dist/getPublishPendingDiff-ByIaPvCn.d.ts +0 -18
  369. package/dist/dist/getPublishPendingDiff-ByIaPvCn.d.ts.map +0 -1
  370. package/dist/dist/getPublishUploads-BBqiowVc.d.ts +0 -89
  371. package/dist/dist/getPublishUploads-BBqiowVc.d.ts.map +0 -1
  372. package/dist/dist/index-DQoyhp2c.d.ts +0 -6
  373. package/dist/dist/index-DQoyhp2c.d.ts.map +0 -1
  374. package/dist/dist/index-njjjo74v.d.ts +0 -14
  375. package/dist/dist/index-njjjo74v.d.ts.map +0 -1
  376. package/dist/dist/index-rOTW-2QZ.d.ts.map +0 -1
  377. package/dist/dist/index.browser-QPWBkLJA.d.ts +0 -9959
  378. package/dist/dist/index.browser-QPWBkLJA.d.ts.map +0 -1
  379. package/dist/dist/mimeTypes-C9r76DCH.d.ts +0 -30
  380. package/dist/dist/mimeTypes-C9r76DCH.d.ts.map +0 -1
  381. package/dist/dist/model-oHA3YhOd.d.ts +0 -6
  382. package/dist/dist/model-oHA3YhOd.d.ts.map +0 -1
  383. package/dist/dist/modelPropertyRecordSchemas-onFOG2x6.d.ts +0 -7
  384. package/dist/dist/modelPropertyRecordSchemas-onFOG2x6.d.ts.map +0 -1
  385. package/dist/dist/platform.browser.d.ts +0 -3
  386. package/dist/dist/platform.browser.d.ts.map +0 -1
  387. package/dist/dist/property-C85DCD7K.d.ts +0 -37
  388. package/dist/dist/property-C85DCD7K.d.ts.map +0 -1
  389. package/dist/dist/propertyValidationRules-CjUPOFeu.d.ts +0 -13
  390. package/dist/dist/propertyValidationRules-CjUPOFeu.d.ts.map +0 -1
  391. package/dist/dist/publishDraftGraph-DBWAN72w.d.ts +0 -93
  392. package/dist/dist/publishDraftGraph-DBWAN72w.d.ts.map +0 -1
  393. package/dist/dist/resolveModelForSyncedSeed-fIURvYRV.d.ts +0 -21
  394. package/dist/dist/resolveModelForSyncedSeed-fIURvYRV.d.ts.map +0 -1
  395. package/dist/dist/syncDbWithEas-BMHoou6X.d.ts +0 -13
  396. package/dist/dist/syncDbWithEas-BMHoou6X.d.ts.map +0 -1
  397. package/dist/dist/tolerantWebAccess-BHndShch.d.ts +0 -113
  398. package/dist/dist/tolerantWebAccess-BHndShch.d.ts.map +0 -1
  399. package/dist/dist/updateSeedRevokedAt-DpfG6ztE.d.ts +0 -11
  400. package/dist/dist/updateSeedRevokedAt-DpfG6ztE.d.ts.map +0 -1
  401. package/dist/eas-B5VJolSj.js +0 -67
  402. package/dist/eas-B5VJolSj.js.map +0 -1
  403. package/dist/errors-CS3nDpUU.js +0 -4006
  404. package/dist/errors-CS3nDpUU.js.map +0 -1
  405. package/dist/eventBus-7vxTCc3a.js +0 -64
  406. package/dist/eventBus-7vxTCc3a.js.map +0 -1
  407. package/dist/findSeedRowsByUid-CuQmK7An.js +0 -47
  408. package/dist/findSeedRowsByUid-CuQmK7An.js.map +0 -1
  409. package/dist/getItem-Uha3_H0t.js +0 -45
  410. package/dist/getItem-Uha3_H0t.js.map +0 -1
  411. package/dist/getPublishPayload-pbin4lk8.js +0 -1736
  412. package/dist/getPublishPayload-pbin4lk8.js.map +0 -1
  413. package/dist/getPublishPendingDiff-ByIaPvCn.js +0 -118
  414. package/dist/getPublishPendingDiff-ByIaPvCn.js.map +0 -1
  415. package/dist/getPublishUploads-BBqiowVc.js +0 -1080
  416. package/dist/getPublishUploads-BBqiowVc.js.map +0 -1
  417. package/dist/index-DQoyhp2c.js.map +0 -1
  418. package/dist/index-DxHpPNfn.js.map +0 -1
  419. package/dist/index-njjjo74v.js +0 -29
  420. package/dist/index-njjjo74v.js.map +0 -1
  421. package/dist/index-rOTW-2QZ.js +0 -4760
  422. package/dist/index-rOTW-2QZ.js.map +0 -1
  423. package/dist/index.browser-QPWBkLJA.js.map +0 -1
  424. package/dist/index.node-WsEJCOwC.js.map +0 -1
  425. package/dist/mimeTypes-C9r76DCH.js +0 -209
  426. package/dist/mimeTypes-C9r76DCH.js.map +0 -1
  427. package/dist/model-oHA3YhOd.js +0 -29
  428. package/dist/model-oHA3YhOd.js.map +0 -1
  429. package/dist/modelPropertyRecordSchemas-onFOG2x6.js +0 -94
  430. package/dist/modelPropertyRecordSchemas-onFOG2x6.js.map +0 -1
  431. package/dist/property-C85DCD7K.js +0 -289
  432. package/dist/property-C85DCD7K.js.map +0 -1
  433. package/dist/propertyValidationRules-CjUPOFeu.js +0 -73
  434. package/dist/propertyValidationRules-CjUPOFeu.js.map +0 -1
  435. package/dist/publishDraftGraph-DBWAN72w.js +0 -505
  436. package/dist/publishDraftGraph-DBWAN72w.js.map +0 -1
  437. package/dist/resolveModelForSyncedSeed-fIURvYRV.js.map +0 -1
  438. package/dist/src/Item/service/actors/loadOrCreateItem.d.ts +0 -5
  439. package/dist/src/Item/service/actors/loadOrCreateItem.d.ts.map +0 -1
  440. package/dist/src/Item/service/actors/waitForDb.d.ts +0 -4
  441. package/dist/src/Item/service/actors/waitForDb.d.ts.map +0 -1
  442. package/dist/src/ItemProperty/service/actors/loadOrCreateProperty.d.ts +0 -5
  443. package/dist/src/ItemProperty/service/actors/loadOrCreateProperty.d.ts.map +0 -1
  444. package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts +0 -5
  445. package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts.map +0 -1
  446. package/dist/src/ItemProperty/service/actors/saveValueToDb/index.d.ts +0 -6
  447. package/dist/src/ItemProperty/service/actors/saveValueToDb/index.d.ts.map +0 -1
  448. package/dist/src/ItemProperty/service/actors/saveValueToDb/rowContext.d.ts.map +0 -1
  449. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveFile.d.ts +0 -24
  450. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveFile.d.ts.map +0 -1
  451. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveHtml.d.ts +0 -5
  452. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveHtml.d.ts.map +0 -1
  453. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveImage.d.ts +0 -5
  454. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveImage.d.ts.map +0 -1
  455. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts +0 -10
  456. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts.map +0 -1
  457. package/dist/src/ItemProperty/service/actors/saveValueToDb/storageValue.d.ts +0 -22
  458. package/dist/src/ItemProperty/service/actors/saveValueToDb/storageValue.d.ts.map +0 -1
  459. package/dist/src/ItemProperty/service/actors/waitForDb.d.ts +0 -4
  460. package/dist/src/ItemProperty/service/actors/waitForDb.d.ts.map +0 -1
  461. package/dist/src/Model/service/actors/createModelProperties.d.ts +0 -9
  462. package/dist/src/Model/service/actors/createModelProperties.d.ts.map +0 -1
  463. package/dist/src/Model/service/actors/loadOrCreateModel.d.ts +0 -5
  464. package/dist/src/Model/service/actors/loadOrCreateModel.d.ts.map +0 -1
  465. package/dist/src/Schema/service/actors/checkExistingSchema.d.ts +0 -5
  466. package/dist/src/Schema/service/actors/checkExistingSchema.d.ts.map +0 -1
  467. package/dist/src/Schema/service/actors/createModelInstances.d.ts +0 -11
  468. package/dist/src/Schema/service/actors/createModelInstances.d.ts.map +0 -1
  469. package/dist/src/Schema/service/actors/createPropertyInstances.d.ts +0 -12
  470. package/dist/src/Schema/service/actors/createPropertyInstances.d.ts.map +0 -1
  471. package/dist/src/Schema/service/actors/validateSchema.d.ts +0 -5
  472. package/dist/src/Schema/service/actors/validateSchema.d.ts.map +0 -1
  473. package/dist/src/Schema/service/actors/verifyModelInstancesInCache.d.ts +0 -6
  474. package/dist/src/Schema/service/actors/verifyModelInstancesInCache.d.ts.map +0 -1
  475. package/dist/src/Schema/service/actors/verifyModelsInDb.d.ts +0 -7
  476. package/dist/src/Schema/service/actors/verifyModelsInDb.d.ts.map +0 -1
  477. package/dist/src/Schema/service/actors/verifyPropertiesInDb.d.ts +0 -8
  478. package/dist/src/Schema/service/actors/verifyPropertiesInDb.d.ts.map +0 -1
  479. package/dist/src/Schema/service/actors/verifyPropertyInstancesInCache.d.ts +0 -6
  480. package/dist/src/Schema/service/actors/verifyPropertyInstancesInCache.d.ts.map +0 -1
  481. package/dist/src/Schema/service/actors/verifySchemaInDb.d.ts +0 -7
  482. package/dist/src/Schema/service/actors/verifySchemaInDb.d.ts.map +0 -1
  483. package/dist/src/Schema/service/actors/writeModelsToDb.d.ts +0 -9
  484. package/dist/src/Schema/service/actors/writeModelsToDb.d.ts.map +0 -1
  485. package/dist/src/Schema/service/actors/writePropertiesToDb.d.ts +0 -10
  486. package/dist/src/Schema/service/actors/writePropertiesToDb.d.ts.map +0 -1
  487. package/dist/src/Schema/service/actors/writeSchemaToDb.d.ts +0 -11
  488. package/dist/src/Schema/service/actors/writeSchemaToDb.d.ts.map +0 -1
  489. package/dist/src/browser/helpers/ArweaveClient.d.ts +0 -14
  490. package/dist/src/browser/helpers/ArweaveClient.d.ts.map +0 -1
  491. package/dist/src/browser/helpers/EasClient.d.ts +0 -10
  492. package/dist/src/browser/helpers/EasClient.d.ts.map +0 -1
  493. package/dist/src/browser/helpers/QueryClient.d.ts +0 -9
  494. package/dist/src/browser/helpers/QueryClient.d.ts.map +0 -1
  495. package/dist/src/client/actors/addModelsToStore.d.ts +0 -4
  496. package/dist/src/client/actors/addModelsToStore.d.ts.map +0 -1
  497. package/dist/src/db/backfillMetadataPropertyIds.d.ts +0 -7
  498. package/dist/src/db/backfillMetadataPropertyIds.d.ts.map +0 -1
  499. package/dist/src/db/read/getItem.d.ts +0 -3
  500. package/dist/src/db/read/getItem.d.ts.map +0 -1
  501. package/dist/src/db/read/getPropertyData.d.ts +0 -4
  502. package/dist/src/db/read/getPropertyData.d.ts.map +0 -1
  503. package/dist/src/db/read/getRelatedItemsForPublish.d.ts +0 -10
  504. package/dist/src/db/read/getRelatedItemsForPublish.d.ts.map +0 -1
  505. package/dist/src/db/read/getSeedData.d.ts +0 -12
  506. package/dist/src/db/read/getSeedData.d.ts.map +0 -1
  507. package/dist/src/db/read/itemLoads.d.ts +0 -20
  508. package/dist/src/db/read/itemLoads.d.ts.map +0 -1
  509. package/dist/src/db/read/publishDraftGraph.d.ts +0 -64
  510. package/dist/src/db/read/publishDraftGraph.d.ts.map +0 -1
  511. package/dist/src/db/write/linkModelToSchema.d.ts +0 -12
  512. package/dist/src/db/write/linkModelToSchema.d.ts.map +0 -1
  513. package/dist/src/helpers/ArweaveClient/BaseArweaveClient.d.ts +0 -2
  514. package/dist/src/helpers/ArweaveClient/BaseArweaveClient.d.ts.map +0 -1
  515. package/dist/src/helpers/ArweaveClient/arweaveL1UploadApi.d.ts +0 -2
  516. package/dist/src/helpers/ArweaveClient/arweaveL1UploadApi.d.ts.map +0 -1
  517. package/dist/src/helpers/ArweaveClient/selectReadGateway.d.ts +0 -2
  518. package/dist/src/helpers/ArweaveClient/selectReadGateway.d.ts.map +0 -1
  519. package/dist/src/helpers/ArweaveClient/uploadApiVerification.d.ts +0 -2
  520. package/dist/src/helpers/ArweaveClient/uploadApiVerification.d.ts.map +0 -1
  521. package/dist/src/helpers/EasClient/BaseEasClient.d.ts +0 -2
  522. package/dist/src/helpers/EasClient/BaseEasClient.d.ts.map +0 -1
  523. package/dist/src/helpers/QueryClient/BaseQueryClient.d.ts +0 -2
  524. package/dist/src/helpers/QueryClient/BaseQueryClient.d.ts.map +0 -1
  525. package/dist/src/helpers/easUid.d.ts +0 -2
  526. package/dist/src/helpers/easUid.d.ts.map +0 -1
  527. package/dist/src/helpers/entity/entityDestroy.d.ts +0 -49
  528. package/dist/src/helpers/entity/entityDestroy.d.ts.map +0 -1
  529. package/dist/src/helpers/entity/entityFind.d.ts +0 -52
  530. package/dist/src/helpers/entity/entityFind.d.ts.map +0 -1
  531. package/dist/src/helpers/entity/entityState.d.ts +0 -28
  532. package/dist/src/helpers/entity/entityState.d.ts.map +0 -1
  533. package/dist/src/helpers/entity/entityUnload.d.ts +0 -56
  534. package/dist/src/helpers/entity/entityUnload.d.ts.map +0 -1
  535. package/dist/src/helpers/entity/evictionEpoch.d.ts +0 -43
  536. package/dist/src/helpers/entity/evictionEpoch.d.ts.map +0 -1
  537. package/dist/src/helpers/entity/index.d.ts +0 -12
  538. package/dist/src/helpers/entity/index.d.ts.map +0 -1
  539. package/dist/src/helpers/gateway/gatewayState.d.ts +0 -2
  540. package/dist/src/helpers/gateway/gatewayState.d.ts.map +0 -1
  541. package/dist/src/helpers/gateway/resolveSeedGatewayEndpoints.d.ts +0 -2
  542. package/dist/src/helpers/gateway/resolveSeedGatewayEndpoints.d.ts.map +0 -1
  543. package/dist/src/helpers/gateway/seedGatewayConfig.d.ts +0 -2
  544. package/dist/src/helpers/gateway/seedGatewayConfig.d.ts.map +0 -1
  545. package/dist/src/helpers/getSchemaForItemProperty.d.ts +0 -15
  546. package/dist/src/helpers/getSchemaForItemProperty.d.ts.map +0 -1
  547. package/dist/src/helpers/getSegmentedItemProperties.d.ts +0 -10
  548. package/dist/src/helpers/getSegmentedItemProperties.d.ts.map +0 -1
  549. package/dist/src/helpers/isSqliteUniqueConstraintError.d.ts +0 -7
  550. package/dist/src/helpers/isSqliteUniqueConstraintError.d.ts.map +0 -1
  551. package/dist/src/helpers/propertyValidationRules.d.ts +0 -20
  552. package/dist/src/helpers/propertyValidationRules.d.ts.map +0 -1
  553. package/dist/src/helpers/propertyWriteTickets.d.ts +0 -11
  554. package/dist/src/helpers/propertyWriteTickets.d.ts.map +0 -1
  555. package/dist/src/helpers/reconcilePropertyDraftRows.d.ts +0 -10
  556. package/dist/src/helpers/reconcilePropertyDraftRows.d.ts.map +0 -1
  557. package/dist/src/helpers/waitForEntityIdle.d.ts +0 -21
  558. package/dist/src/helpers/waitForEntityIdle.d.ts.map +0 -1
  559. package/dist/src/interfaces/IQueryClient.d.ts +0 -15
  560. package/dist/src/interfaces/IQueryClient.d.ts.map +0 -1
  561. package/dist/src/node/helpers/ArweaveClient.d.ts +0 -5
  562. package/dist/src/node/helpers/ArweaveClient.d.ts.map +0 -1
  563. package/dist/src/node/helpers/EasClient.d.ts +0 -5
  564. package/dist/src/node/helpers/EasClient.d.ts.map +0 -1
  565. package/dist/src/node/helpers/QueryClient.d.ts +0 -5
  566. package/dist/src/node/helpers/QueryClient.d.ts.map +0 -1
  567. package/dist/src/services/write/actors/validateEntity.d.ts +0 -6
  568. package/dist/src/services/write/actors/validateEntity.d.ts.map +0 -1
  569. package/dist/src/services/write/actors/writeToDatabase.d.ts +0 -14
  570. package/dist/src/services/write/actors/writeToDatabase.d.ts.map +0 -1
  571. package/dist/src/services/write/writeProcessMachine.d.ts +0 -56
  572. package/dist/src/services/write/writeProcessMachine.d.ts.map +0 -1
  573. package/dist/src/stores/eas.d.ts +0 -2
  574. package/dist/src/stores/eas.d.ts.map +0 -1
  575. package/dist/src/types/arweave.d.ts +0 -57
  576. package/dist/src/types/arweave.d.ts.map +0 -1
  577. package/dist/src/types/gateway.d.ts +0 -2
  578. package/dist/src/types/gateway.d.ts.map +0 -1
  579. package/dist/syncDbWithEas-BMHoou6X.js.map +0 -1
  580. package/dist/updateSeedRevokedAt-DpfG6ztE.js +0 -31
  581. package/dist/updateSeedRevokedAt-DpfG6ztE.js.map +0 -1
@@ -1,4006 +0,0 @@
1
- import { fromCallback, fromPromise, setup, assign, createActor } from 'xstate';
2
- import { L as BaseDb, p as models, E as properties, a5 as generateId, aG as isInternalSchema, aH as writeModelToDb, aI as writePropertyToDb, z as modelSchemas, J as schemas, A as AmbiguousModelError, aJ as renameModelInDb, aK as currentNameMatchLevels, a as TProperty } from './index-rOTW-2QZ.js';
3
- import { eq, and, isNull, or } from 'drizzle-orm';
4
- import debug from 'debug';
5
- import { P as isActorStopped, ak as ActorKeyedState, F as waitForEntityIdle, Q as DEFAULT_READY_TIMEOUT_MS, R as createReactiveProxy, T as findEntity, X as ClientManagerState, I as Item, al as clearDestroySubscriptions, U as forceRemoveFromCaches, am as unloadEntity, V as runDestroyLifecycle } from './Item-CG5IhPtL.js';
6
- import { v as getClient } from './ClientManager-DqL0q_eu.js';
7
- import { modelPropertiesToObject } from './model-oHA3YhOd.js';
8
- import { localNamesInBestMatchTier } from '@seedprotocol/eas';
9
- import { Type } from '@sinclair/typebox';
10
-
11
- const logger$7 = debug('seedSdk:model:actors:loadOrCreateModel');
12
- /**
13
- * Create ModelProperty instances for all property IDs to ensure they're cached
14
- * This ensures that ModelProperty.getById() in Model.properties getter will find the instances
15
- * @param propertyFileIds - Array of property file IDs to create instances for
16
- */
17
- const createPropertyInstances = async (propertyFileIds) => {
18
- if (propertyFileIds.length === 0) {
19
- return;
20
- }
21
- try {
22
- const mod = await import('./ModelProperty-DDD47KVm.js');
23
- const ModelProperty = mod?.ModelProperty ?? mod?.default;
24
- if (!ModelProperty) {
25
- logger$7('createPropertyInstances: ModelProperty not available from dynamic import');
26
- return;
27
- }
28
- // Create instances for all property IDs in parallel
29
- // ModelProperty.createById() will check cache first, then query DB and create if needed
30
- const createPromises = propertyFileIds.map(async (propertyFileId) => {
31
- try {
32
- const property = await ModelProperty.createById(propertyFileId);
33
- if (property) {
34
- logger$7(`Created/cached ModelProperty instance for propertyFileId "${propertyFileId}"`);
35
- }
36
- else {
37
- logger$7(`ModelProperty.createById returned undefined for propertyFileId "${propertyFileId}" (may not exist in DB yet)`);
38
- }
39
- }
40
- catch (error) {
41
- logger$7(`Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
42
- // Don't throw - continue with other properties
43
- }
44
- });
45
- await Promise.all(createPromises);
46
- logger$7(`Finished creating/caching ${propertyFileIds.length} ModelProperty instances`);
47
- }
48
- catch (error) {
49
- logger$7(`Error in createPropertyInstances: ${error}`);
50
- // Don't throw - this is best-effort to pre-populate cache
51
- }
52
- };
53
- const loadOrCreateModel = fromCallback(({ sendBack, input: { context }, self }) => {
54
- const _loadOrCreateModel = async () => {
55
- const { modelName, schemaName, id, _idFromSchema } = context; // id is now the schemaFileId (string)
56
- if (!modelName || !schemaName) {
57
- throw new Error('Model name and schema name are required');
58
- }
59
- // CRITICAL: Model instances load their data from database, not from Schema context.
60
- // Schema is read-only with respect to Model instances.
61
- const db = BaseDb.getAppDb();
62
- let schemaFileId = id; // id is now the schemaFileId (string)
63
- let modelRecord = null;
64
- let foundBySchemaFileId = false; // Track if model was found by schemaFileId
65
- let existingNumbersFromSchemaContext = null; // When we fall through (same name, different id), schema model occupies base name
66
- // Step 1: Load from database FIRST (primary source of truth)
67
- if (db) {
68
- try {
69
- // Try to find model by schemaFileId if provided
70
- if (schemaFileId) {
71
- const dbModels = await db
72
- .select()
73
- .from(models)
74
- .where(eq(models.schemaFileId, schemaFileId))
75
- .limit(1);
76
- if (dbModels.length > 0) {
77
- modelRecord = dbModels[0];
78
- foundBySchemaFileId = true;
79
- logger$7(`Found model "${modelName}" in database by schemaFileId "${schemaFileId}"`);
80
- }
81
- }
82
- // If not found by ID, try by name
83
- // But if we have a schemaFileId and the model found by name has a different schemaFileId,
84
- // don't use it - we're creating a new model from a schema file with a specific ID
85
- if (!modelRecord) {
86
- // Model names are only unique per schema: prefer this schema's row, and otherwise only
87
- // consider same-name rows that no schema claims yet (never another schema's model).
88
- const _mod_0 = await import('./index-rOTW-2QZ.js');
89
- const _ns_0 = _mod_0.bd;
90
- const { modelSchemas } = _ns_0;
91
- const _mod_1 = await import('./index-rOTW-2QZ.js');
92
- const _ns_1 = _mod_1.bc;
93
- const { schemas: schemasTable } = _ns_1;
94
- let dbModels = await db
95
- .select({
96
- id: models.id,
97
- name: models.name,
98
- schemaFileId: models.schemaFileId,
99
- isEdited: models.isEdited,
100
- })
101
- .from(models)
102
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
103
- .innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
104
- .where(and(eq(models.name, modelName), eq(schemasTable.name, schemaName)))
105
- .limit(1);
106
- if (dbModels.length === 0) {
107
- dbModels = await db
108
- .select({
109
- id: models.id,
110
- name: models.name,
111
- schemaFileId: models.schemaFileId,
112
- isEdited: models.isEdited,
113
- })
114
- .from(models)
115
- .leftJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
116
- // Stubs only: an unlinked row with a schemaFileId is a deleted schema's leftover, not ours
117
- .where(and(eq(models.name, modelName), isNull(modelSchemas.id), isNull(models.schemaFileId)))
118
- .limit(1);
119
- }
120
- if (dbModels.length > 0) {
121
- const foundModel = dbModels[0];
122
- const dbSchemaFileId = foundModel.schemaFileId;
123
- // CRITICAL: If we found a model in the database by name, check if there's already a cached instance
124
- // with that schemaFileId. If so, we should use that instance's schemaFileId for the current instance.
125
- // This handles the case where Model.create was called with a generated ID, but the model
126
- // already exists in the database with a different ID. By updating the current instance's
127
- // schemaFileId to match the database, both will point to the same cached instance.
128
- // Skip for schema models (_idFromSchema): their id is authoritative, and a same-name row
129
- // with a different id belongs to another schema (e.g. two schemas that each define "Post").
130
- if (dbSchemaFileId && !(_idFromSchema && schemaFileId && schemaFileId !== dbSchemaFileId)) {
131
- try {
132
- const modelMod = await Promise.resolve().then(function () { return Model$1; });
133
- const { Model } = modelMod;
134
- // Access instanceCacheById via type assertion since it's protected
135
- const cacheById = Model.instanceCacheById;
136
- if (cacheById.has(dbSchemaFileId)) {
137
- logger$7(`Model "${modelName}" found in database by name with schemaFileId "${dbSchemaFileId}", and a cached instance already exists. Updating current instance to use the same schemaFileId.`);
138
- // Update the current instance's schemaFileId to match the database
139
- // This ensures both instances point to the same cached instance
140
- modelRecord = foundModel;
141
- schemaFileId = dbSchemaFileId;
142
- logger$7(`Using existing cached instance with schemaFileId "${schemaFileId}" for model "${modelName}"`);
143
- }
144
- }
145
- catch (error) {
146
- logger$7(`Error checking Model cache for schemaFileId "${dbSchemaFileId}": ${error}`);
147
- // Fall through to normal processing
148
- }
149
- }
150
- // If we have a schemaFileId and it doesn't match the found model's schemaFileId,
151
- // this is a new model - don't use the existing model (will be renamed if duplicate)
152
- // UNLESS we found a cached instance above (in which case modelRecord is already set)
153
- if (!modelRecord && schemaFileId && dbSchemaFileId && schemaFileId !== dbSchemaFileId) {
154
- logger$7(`Model "${modelName}" found by name but has different schemaFileId (provided: "${schemaFileId}", db: "${dbSchemaFileId}"). Creating new model (will check for duplicates).`);
155
- // Don't set modelRecord - we'll create a new model with potentially renamed name
156
- }
157
- else if (!modelRecord && schemaFileId && !dbSchemaFileId) {
158
- // We have a schemaFileId but the found model doesn't have one - this is a new model
159
- // Don't reuse the existing model, create a new one (will be renamed if duplicate)
160
- logger$7(`Model "${modelName}" found by name but found model has no schemaFileId. Creating new model with schemaFileId "${schemaFileId}" (will check for duplicates).`);
161
- // Don't set modelRecord - we'll create a new model
162
- }
163
- else if (!modelRecord) {
164
- // Only reuse existing model if:
165
- // 1. No schemaFileId was provided (loading existing model), OR
166
- // 2. schemaFileId matches (same model)
167
- logger$7(`Model "${modelName}" found by name, reusing existing model (schemaFileId: provided="${schemaFileId}", db="${dbSchemaFileId}")`);
168
- modelRecord = foundModel;
169
- // Only use database schemaFileId if no id was explicitly provided
170
- // If an id was provided, we should use it (it might be creating a new model with a specific ID)
171
- if (!id && dbSchemaFileId) {
172
- schemaFileId = dbSchemaFileId;
173
- logger$7(`Using database schemaFileId "${schemaFileId}" for model "${modelName}" (no ID was provided)`);
174
- }
175
- else if (id) {
176
- logger$7(`Preserving provided id (schemaFileId) "${schemaFileId}" for model "${modelName}" (ignoring database schemaFileId "${dbSchemaFileId}")`);
177
- }
178
- logger$7(`Found model "${modelName}" in database by name, schemaFileId: "${schemaFileId}"`);
179
- }
180
- }
181
- }
182
- // If we found the model record, load its properties from database
183
- if (modelRecord && modelRecord.id) {
184
- const propertyRecords = await db
185
- .select()
186
- .from(properties)
187
- .where(eq(properties.modelId, modelRecord.id));
188
- // Properties are now loaded via liveQuery, not passed through context
189
- // The property records are used by liveQuery to populate ModelProperty instances
190
- // Pre-create ModelProperty instances for all property IDs to ensure they're cached
191
- // This ensures that ModelProperty.getById() in Model.properties getter will find the instances
192
- const propertyFileIds = propertyRecords
193
- .map((record) => record.schemaFileId)
194
- .filter((id) => id !== null && id !== undefined);
195
- if (propertyFileIds.length > 0) {
196
- // Not for a model stopped meanwhile (unloaded, or evicted with its schema)
197
- if (!isActorStopped(self))
198
- await createPropertyInstances(propertyFileIds);
199
- }
200
- // Generate schemaFileId if not set
201
- if (!schemaFileId) {
202
- schemaFileId = generateId();
203
- logger$7(`Generated id (schemaFileId) "${schemaFileId}" for model "${modelName}"`);
204
- }
205
- // Include _dbId from database record so properties can be created if needed
206
- // Even if we found an existing model, we may still need to create properties
207
- // if _pendingPropertyDefinitions are provided
208
- // Track conflict detection metadata
209
- const loadedAt = Date.now();
210
- // Note: models table doesn't have updatedAt or version, so we use loadedAt for _dbUpdatedAt
211
- // In the future, if models table gets these fields, we should use them
212
- const dbUpdatedAt = loadedAt;
213
- // CRITICAL: Include _liveQueryPropertyIds in loadOrCreateModelSuccess event
214
- // This ensures the properties getter works immediately, even before _setupLiveQuerySubscription runs
215
- // We already have the property IDs from the database query, so include them now
216
- sendBack({
217
- type: 'loadOrCreateModelSuccess',
218
- model: {
219
- id: schemaFileId, // schemaFileId (string) - public ID
220
- _dbId: modelRecord.id, // Database integer ID - internal only
221
- _liveQueryPropertyIds: propertyFileIds, // Property IDs from database query
222
- _propertiesUpdated: Date.now(), // Timestamp for tracking
223
- _isEdited: modelRecord.isEdited ?? false, // Load isEdited from database
224
- _loadedAt: loadedAt,
225
- _dbUpdatedAt: dbUpdatedAt,
226
- },
227
- });
228
- return;
229
- }
230
- }
231
- catch (error) {
232
- logger$7(`Error loading model from database: ${error}`);
233
- // Fall through to create new model
234
- }
235
- }
236
- // Step 2: Fallback to Schema context (only if database doesn't have the model)
237
- // This handles the case where model exists in schema file but not yet in database
238
- try {
239
- const _mod_2 = await import('./Schema-BS8oR7QV.js');
240
- const _ns_2 = _mod_2.a;
241
- const schemaMod = _ns_2;
242
- const { Schema } = schemaMod;
243
- // Stopped meanwhile (unloaded, or evicted with its schema): don't load the schema again
244
- if (isActorStopped(self))
245
- return;
246
- const schema = Schema.create(schemaName, { waitForReady: false, hold: false });
247
- const schemaSnapshot = schema.getService().getSnapshot();
248
- // Wait for schema to load if it's still loading
249
- // Check if state is a loading state object (XState v5 nested states)
250
- const isLoading = typeof schemaSnapshot.value === 'object' && 'loading' in schemaSnapshot.value;
251
- if (isLoading) {
252
- await new Promise((resolve, reject) => {
253
- const subscription = schema.getService().subscribe((snapshot) => {
254
- if (snapshot.value === 'idle' || snapshot.value === 'error') {
255
- subscription.unsubscribe();
256
- if (snapshot.value === 'error') {
257
- reject(new Error('Schema failed to load'));
258
- }
259
- else {
260
- resolve();
261
- }
262
- }
263
- });
264
- });
265
- }
266
- const schemaContext = schema.getService().getSnapshot().context;
267
- // Check by name first; also check by schemaFileId so we recognize the schema model even when context is keyed differently or not yet updated
268
- if (schemaContext.models) {
269
- const byName = schemaContext.models[modelName];
270
- const schemaModelId = byName?.id ?? null;
271
- if (schemaFileId === schemaModelId) {
272
- logger$7(`Found model "${modelName}" in Schema context (database fallback)`);
273
- if (!schemaFileId) {
274
- schemaFileId = generateId();
275
- logger$7(`Generated id (schemaFileId) "${schemaFileId}" for model "${modelName}"`);
276
- }
277
- sendBack({
278
- type: 'loadOrCreateModelSuccess',
279
- model: { id: schemaFileId },
280
- });
281
- return;
282
- }
283
- // Same name but different id: runtime create colliding with schema model name; fall through
284
- if (byName) {
285
- existingNumbersFromSchemaContext = new Set([0]);
286
- }
287
- else if (schemaFileId) {
288
- // Schema context may not have model by name yet (e.g. still loading). If our id appears in any schema model, we're the schema model.
289
- for (const def of Object.values(schemaContext.models)) {
290
- if (def?.id === schemaFileId) {
291
- logger$7(`Found model in Schema context by id "${schemaFileId}" (database fallback)`);
292
- sendBack({
293
- type: 'loadOrCreateModelSuccess',
294
- model: { id: schemaFileId },
295
- });
296
- return;
297
- }
298
- }
299
- }
300
- }
301
- }
302
- catch (error) {
303
- logger$7(`Error loading model from Schema context: ${error}`);
304
- // Fall through to create new model
305
- }
306
- // Step 3: Check for duplicate names in database before creating new model
307
- // Skip duplicate check only if we found a model record to use (modelRecord is set)
308
- // OR if the model was found by schemaFileId (preserving original name from schema file)
309
- // OR if this is an internal schema (Seed Protocol) - internal schemas should preserve original names
310
- // Otherwise, check for duplicates when creating a new model
311
- // NOTE: We should NOT skip duplicate check just because schemaFileId exists - that could be a newly generated ID
312
- // for a new model that needs to be renamed if it's a duplicate
313
- let finalModelName = modelName;
314
- if (db) {
315
- // Check if this is an internal schema (Seed Protocol)
316
- const isInternal = isInternalSchema(schemaName);
317
- // Only skip duplicate check if:
318
- // 1. We found a model record to use (modelRecord is set), OR
319
- // 2. The model was found by schemaFileId (preserving original name from schema file), OR
320
- // 3. This is an internal schema (internal schemas should preserve original names from schema files), OR
321
- // 4. This instance was created with modelFileId from schema (_idFromSchema) - schema models keep their name
322
- const shouldSkipDuplicateCheck = modelRecord !== null || foundBySchemaFileId || isInternal || !!_idFromSchema;
323
- logger$7(`Duplicate check: modelRecord=${modelRecord !== null}, foundBySchemaFileId=${foundBySchemaFileId}, isInternal=${isInternal}, _idFromSchema=${!!_idFromSchema}, schemaFileId=${schemaFileId}, shouldSkip=${shouldSkipDuplicateCheck}`);
324
- if (!shouldSkipDuplicateCheck) {
325
- try {
326
- const lowerModelName = modelName.toLowerCase();
327
- const existingNumbers = new Set(existingNumbersFromSchemaContext ?? []);
328
- // First, check Model cache for models (includes models from imported schemas that may not be in DB yet)
329
- try {
330
- const modelMod = await Promise.resolve().then(function () { return Model$1; });
331
- const { Model } = modelMod;
332
- // Check name-based cache for this schema
333
- // Skip the current model (context.id): Model.create() adds the new instance to the cache
334
- // before this callback runs, so we must not count it as a duplicate.
335
- logger$7(`Checking Model cache for duplicates in schema "${schemaName}"`);
336
- const currentModelFileId = schemaFileId ?? null;
337
- const cacheByName = Model.instanceCacheByName;
338
- for (const [nameKey, cachedModelFileId] of cacheByName.entries()) {
339
- if (currentModelFileId && cachedModelFileId === currentModelFileId)
340
- continue; // skip self
341
- const [cachedSchemaName, cachedModelName] = nameKey.split(':', 2);
342
- if (cachedSchemaName === schemaName && cachedModelName) {
343
- const lowerCachedName = cachedModelName.toLowerCase();
344
- if (lowerCachedName === lowerModelName) {
345
- existingNumbers.add(0); // Base name exists
346
- logger$7(`Found duplicate in Model cache: "${cachedModelName}" matches "${modelName}"`);
347
- }
348
- else if (lowerCachedName.startsWith(lowerModelName + ' ')) {
349
- // Check if it's the base name followed by a space and a number
350
- const suffix = lowerCachedName.slice(lowerModelName.length + 1);
351
- const number = parseInt(suffix, 10);
352
- if (!isNaN(number) && suffix === number.toString()) {
353
- existingNumbers.add(number);
354
- logger$7(`Found numbered variant in Model cache: "${cachedModelName}" (number: ${number})`);
355
- }
356
- }
357
- }
358
- }
359
- // Also check legacy cache (skip current model by id)
360
- const legacyCache = Model.instanceCache;
361
- for (const [nameKey, entry] of legacyCache?.entries() || []) {
362
- const legacyId = entry?.instance?._getSnapshotContext?.()?.id;
363
- if (currentModelFileId && legacyId === currentModelFileId)
364
- continue; // skip self
365
- const [cachedSchemaName, cachedModelName] = nameKey.split(':', 2);
366
- if (cachedSchemaName === schemaName && cachedModelName) {
367
- const lowerCachedName = cachedModelName.toLowerCase();
368
- if (lowerCachedName === lowerModelName) {
369
- existingNumbers.add(0); // Base name exists
370
- logger$7(`Found duplicate in Model legacy cache: "${cachedModelName}" matches "${modelName}"`);
371
- }
372
- else if (lowerCachedName.startsWith(lowerModelName + ' ')) {
373
- // Check if it's the base name followed by a space and a number
374
- const suffix = lowerCachedName.slice(lowerModelName.length + 1);
375
- const number = parseInt(suffix, 10);
376
- if (!isNaN(number) && suffix === number.toString()) {
377
- existingNumbers.add(number);
378
- logger$7(`Found numbered variant in Model legacy cache: "${cachedModelName}" (number: ${number})`);
379
- }
380
- }
381
- }
382
- }
383
- }
384
- catch (error) {
385
- logger$7(`Error checking Model cache for duplicates: ${error}`);
386
- // Continue with database check
387
- }
388
- // Also check database for models (in case they're persisted but not in Schema context)
389
- if (db) {
390
- const _mod_3 = await import('./index-rOTW-2QZ.js');
391
- const _ns_3 = _mod_3.bd;
392
- const modelSchemaSchemaMod = _ns_3;
393
- const { modelSchemas } = modelSchemaSchemaMod;
394
- const _mod_4 = await import('./index-rOTW-2QZ.js');
395
- const _ns_4 = _mod_4.bc;
396
- const schemaSchemaMod = _ns_4;
397
- const { schemas: schemasTable } = schemaSchemaMod;
398
- // Query all models for this schema to check for duplicates (case-insensitive)
399
- logger$7(`Checking database for duplicate model names in schema "${schemaName}"`);
400
- const allModelsForSchema = await db
401
- .select({
402
- name: models.name,
403
- })
404
- .from(models)
405
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
406
- .innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
407
- .where(eq(schemasTable.name, schemaName));
408
- logger$7(`Found ${allModelsForSchema.length} models in database for schema "${schemaName}": ${allModelsForSchema.map((m) => m.name).join(', ')}`);
409
- if (allModelsForSchema.length === 0 && modelName.includes('Shared')) {
410
- logger$7(`[DEBUG] No models found in database for schema "${schemaName}" when checking for duplicate "${modelName}"`);
411
- }
412
- // Check for exact match (case-insensitive) or matches with number suffix
413
- for (const dbModel of allModelsForSchema) {
414
- const lowerDbName = dbModel.name.toLowerCase();
415
- if (lowerDbName === lowerModelName) {
416
- existingNumbers.add(0); // Base name exists
417
- logger$7(`Found duplicate in database: "${dbModel.name}" matches "${modelName}"`);
418
- }
419
- else if (lowerDbName.startsWith(lowerModelName + ' ')) {
420
- // Check if it's the base name followed by a space and a number
421
- const suffix = lowerDbName.slice(lowerModelName.length + 1);
422
- const number = parseInt(suffix, 10);
423
- if (!isNaN(number) && suffix === number.toString()) {
424
- existingNumbers.add(number);
425
- logger$7(`Found numbered variant in database: "${dbModel.name}" (number: ${number})`);
426
- }
427
- }
428
- }
429
- }
430
- // If duplicates found, generate unique name
431
- logger$7(`Duplicate check results: existingNumbers=${Array.from(existingNumbers).join(', ')}, hasBaseName=${existingNumbers.has(0)}`);
432
- if (existingNumbers.has(0)) {
433
- let nextNumber = 1;
434
- while (existingNumbers.has(nextNumber)) {
435
- nextNumber++;
436
- }
437
- finalModelName = `${modelName} ${nextNumber}`;
438
- logger$7(`Found duplicate model name "${modelName}" in schema "${schemaName}", using unique name "${finalModelName}"`);
439
- }
440
- else {
441
- logger$7(`No duplicate found for model name "${modelName}" in schema "${schemaName}"`);
442
- }
443
- }
444
- catch (error) {
445
- logger$7(`Error checking for duplicate model names: ${error}`);
446
- // Continue with original name if check fails
447
- }
448
- }
449
- else {
450
- if (foundBySchemaFileId) {
451
- logger$7(`Preserving original model name "${modelName}" for model found by schemaFileId (schemaFileId: ${schemaFileId})`);
452
- }
453
- else if (modelRecord) {
454
- logger$7(`Using existing model name "${modelName}" from database (modelRecord found)`);
455
- }
456
- else if (isInternal) {
457
- logger$7(`Preserving original model name "${modelName}" for internal schema "${schemaName}" (skipping duplicate check)`);
458
- }
459
- }
460
- }
461
- // Step 4: Create new empty model (not found in database or Schema context)
462
- logger$7(`Creating new model "${finalModelName}" in schema "${schemaName}"`);
463
- // Generate schemaFileId for new model
464
- if (!schemaFileId) {
465
- schemaFileId = generateId();
466
- logger$7(`Generated id (schemaFileId) "${schemaFileId}" for new model "${finalModelName}"`);
467
- }
468
- // Runtime creates use a placeholder cache key in Model.create; move it to the final name. Skip when schema model (_idFromSchema).
469
- if (!_idFromSchema) {
470
- const modelMod = await Promise.resolve().then(function () { return Model$1; });
471
- const { Model } = modelMod;
472
- Model.updateNameIndex('__pending__' + schemaFileId, finalModelName, schemaName, schemaFileId);
473
- }
474
- // If model name was changed, store it in a temporary internal field so loadOrCreateModelSuccess can apply it
475
- // This avoids the issue where updateContext with modelName triggers validation and state transition
476
- // We use _pendingModelName (internal field) which won't trigger validation
477
- if (finalModelName !== modelName) {
478
- logger$7(`Model name changed from "${modelName}" to "${finalModelName}", storing in _pendingModelName for loadOrCreateModelSuccess`);
479
- // Store the final name in a temporary internal field that will be picked up by loadOrCreateModelSuccess
480
- sendBack({
481
- type: 'updateContext',
482
- _pendingModelName: finalModelName,
483
- });
484
- }
485
- else {
486
- logger$7(`Model name unchanged: "${modelName}"`);
487
- }
488
- // Mark schema as draft when a new model is created so saveNewVersion() can persist it
489
- try {
490
- const _mod_5 = await import('./Schema-BS8oR7QV.js');
491
- const _ns_5 = _mod_5.a;
492
- const schemaMod = _ns_5;
493
- const { Schema } = schemaMod;
494
- if (!isActorStopped(self)) {
495
- const schema = Schema.create(schemaName, { waitForReady: false, hold: false });
496
- schema.getService().send({ type: 'markAsDraft', propertyKey: 'schema:models' });
497
- }
498
- }
499
- catch (err) {
500
- logger$7(`Failed to mark schema as draft after creating model: ${err}`);
501
- }
502
- sendBack({
503
- type: 'loadOrCreateModelSuccess',
504
- model: {
505
- id: schemaFileId, // schemaFileId (string) - public ID
506
- },
507
- });
508
- };
509
- _loadOrCreateModel().catch((error) => {
510
- logger$7('Error loading or creating model:', error);
511
- sendBack({ type: 'loadOrCreateModelError', error });
512
- });
513
- });
514
-
515
- const logger$6 = debug('seedSdk:model:actors:validateModel');
516
- // Validation timeout in milliseconds (10 seconds)
517
- const VALIDATION_TIMEOUT_MS = 10000;
518
- const validateModel = fromCallback(({ sendBack, input: { context }, self }) => {
519
- let hasResponded = false;
520
- // Timeout handler to ensure we always respond
521
- const timeoutId = setTimeout(() => {
522
- if (!hasResponded) {
523
- hasResponded = true;
524
- sendBack({
525
- type: 'validationError',
526
- errors: [{
527
- field: 'model',
528
- message: `Model validation timed out after ${VALIDATION_TIMEOUT_MS}ms`,
529
- code: 'validation_timeout',
530
- severity: 'error',
531
- }],
532
- });
533
- }
534
- }, VALIDATION_TIMEOUT_MS);
535
- const _validateModel = async () => {
536
- try {
537
- // Use dynamic imports to break circular dependencies
538
- const validationServiceMod = await import('./SchemaValidationService-CZzKNL04.js');
539
- const { SchemaValidationService } = validationServiceMod;
540
- const validationService = new SchemaValidationService();
541
- const _mod_6 = await import('./Schema-BS8oR7QV.js');
542
- const _ns_6 = _mod_6.a;
543
- const schemaMod = _ns_6;
544
- const { Schema } = schemaMod;
545
- // Validate model structure
546
- const structureResult = validationService.validateModelStructure(context);
547
- if (!structureResult.isValid) {
548
- if (!hasResponded) {
549
- hasResponded = true;
550
- clearTimeout(timeoutId);
551
- sendBack({ type: 'validationError', errors: structureResult.errors });
552
- }
553
- return;
554
- }
555
- // If we have schema name, validate against schema
556
- // CRITICAL: Use validateModelAgainstSchema which doesn't require the model to be in schema context
557
- // This allows validation BEFORE registration, preventing update loops
558
- // Stopped meanwhile (unloaded, or evicted with its schema): don't load the schema again
559
- if (isActorStopped(self))
560
- return;
561
- if (context.schemaName) {
562
- try {
563
- const schema = Schema.create(context.schemaName, { waitForReady: false, hold: false });
564
- const schemaSnapshot = schema.getService().getSnapshot();
565
- const schemaStatus = schemaSnapshot.value;
566
- // Only validate against schema if it's loaded (in idle state)
567
- if (schemaStatus === 'idle') {
568
- const schemaContext = schemaSnapshot.context;
569
- // Use validateModelAgainstSchema which validates the model data directly
570
- // without requiring it to be in schema.models. This prevents loops because:
571
- // 1. Model validates against schema (model NOT in schema context yet)
572
- // 2. If valid, model gets registered with schema
573
- // 3. No need for manual updates because model is already correct (liveQuery handles updates)
574
- const schemaResult = validationService.validateModelAgainstSchema(schemaContext, context.modelName, context);
575
- if (!schemaResult.isValid) {
576
- if (!hasResponded) {
577
- hasResponded = true;
578
- clearTimeout(timeoutId);
579
- sendBack({ type: 'validationError', errors: schemaResult.errors });
580
- }
581
- return;
582
- }
583
- }
584
- else {
585
- logger$6(`Schema is in ${schemaStatus} state, skipping schema validation`);
586
- // Continue with structure validation only
587
- }
588
- }
589
- catch (error) {
590
- logger$6('Error validating model against schema:', error);
591
- // Continue with structure validation only
592
- }
593
- }
594
- // All validations passed
595
- if (!hasResponded) {
596
- hasResponded = true;
597
- clearTimeout(timeoutId);
598
- sendBack({ type: 'validationSuccess', errors: [] });
599
- }
600
- }
601
- catch (error) {
602
- if (!hasResponded) {
603
- hasResponded = true;
604
- clearTimeout(timeoutId);
605
- logger$6('Error in validateModel:', error);
606
- sendBack({
607
- type: 'validationError',
608
- errors: [{
609
- field: 'model',
610
- message: error instanceof Error ? error.message : 'Unknown validation error',
611
- code: 'validation_exception',
612
- severity: 'error',
613
- }],
614
- });
615
- }
616
- }
617
- };
618
- _validateModel().catch((error) => {
619
- if (!hasResponded) {
620
- hasResponded = true;
621
- clearTimeout(timeoutId);
622
- logger$6('Error in validateModel:', error);
623
- sendBack({
624
- type: 'validationError',
625
- errors: [{
626
- field: 'model',
627
- message: error instanceof Error ? error.message : 'Unknown validation error',
628
- code: 'validation_exception',
629
- severity: 'error',
630
- }],
631
- });
632
- }
633
- });
634
- return () => {
635
- // Cleanup function - clear timeout if actor is stopped
636
- clearTimeout(timeoutId);
637
- };
638
- });
639
-
640
- const logger$5 = debug('seedSdk:model:actors:createModelProperties');
641
- const createModelProperties = fromCallback(({ sendBack, input, self }) => {
642
- // The model was stopped (unloaded, or evicted with its schema by Schema.destroy / test cleanup) while
643
- // this ran: stopping it stops this actor too, but can't cancel the awaits below. A stopped model
644
- // creates no properties (they'd be cached for a model that's gone, and write rows under it).
645
- const stopped = () => isActorStopped(self);
646
- const _createProperties = async () => {
647
- const { context, propertyDefinitions } = input;
648
- const { id, _dbId, modelName, schemaName } = context;
649
- if (!id || !_dbId || !modelName) {
650
- throw new Error('Model ID, file ID, and name are required to create properties');
651
- }
652
- if (!propertyDefinitions || Object.keys(propertyDefinitions).length === 0) {
653
- logger$5('No property definitions provided, skipping property creation');
654
- sendBack({ type: 'createModelPropertiesSuccess' });
655
- return;
656
- }
657
- logger$5(`Creating ${Object.keys(propertyDefinitions).length} properties for model "${modelName}" (id: ${_dbId})`);
658
- const mod = await import('./ModelProperty-DDD47KVm.js');
659
- const ModelProperty = mod?.ModelProperty ?? mod?.default;
660
- if (!ModelProperty) {
661
- logger$5('ModelProperty not available from dynamic import');
662
- sendBack({ type: 'createModelPropertiesError', error: new Error('ModelProperty not available') });
663
- return;
664
- }
665
- const drizzleMod = await import('drizzle-orm');
666
- const { and, eq } = drizzleMod;
667
- const db = BaseDb.getAppDb();
668
- for (const [propName, propData] of Object.entries(propertyDefinitions)) {
669
- // The model's own write (writeModelToDb) normally stored this property already, under
670
- // propData.id or an id it generated. Reuse that row so the instance gets the same id
671
- // and doesn't write the property a second time.
672
- const propertyRecord = db
673
- ? (await db
674
- .select()
675
- .from(properties)
676
- .where(propData.id
677
- ? eq(properties.schemaFileId, propData.id)
678
- : and(eq(properties.modelId, _dbId), eq(properties.name, propName)))
679
- .limit(1))[0]
680
- : undefined;
681
- // Otherwise use provided ID or generate a random one
682
- // IDs should be generated in the import process before creating properties
683
- const _mod_7 = await import('./index-rOTW-2QZ.js');
684
- const _ns_7 = _mod_7.bh;
685
- const { generateId } = _ns_7;
686
- const propertyFileId = propData.id || propertyRecord?.schemaFileId || generateId();
687
- logger$5(`Creating property "${propName}" with fileId "${propertyFileId}"`);
688
- // Query database to get refModelId if the property has ref/refModelName
689
- // The property should already be in DB from addModelsToDb
690
- let refModelId = propData.refModelId;
691
- if (!refModelId && (propData.ref || propData.refModelName) && db) {
692
- try {
693
- const refModelName = propData.refModelName || propData.ref;
694
- // First try to get refModelId from the property record in the database
695
- if (propertyRecord?.refModelId) {
696
- refModelId = propertyRecord.refModelId;
697
- logger$5(`Found refModelId ${refModelId} from database for property "${propName}"`);
698
- }
699
- else if (refModelName) {
700
- // Fallback: resolve the ref by name within this model's schema
701
- const _mod_8 = await import('./index-rOTW-2QZ.js');
702
- const _ns_8 = _mod_8.be;
703
- const { resolveModelRecord } = _ns_8;
704
- const refModelRecord = await resolveModelRecord(refModelName, { schemaName }, db);
705
- if (refModelRecord?.id) {
706
- refModelId = refModelRecord.id;
707
- logger$5(`Resolved refModelId ${refModelId} from model name "${refModelName}" for property "${propName}"`);
708
- }
709
- }
710
- }
711
- catch (error) {
712
- logger$5(`Error fetching refModelId for property "${propName}":`, error);
713
- // Continue without refModelId - it will be resolved later in _initializeOriginalValues
714
- }
715
- }
716
- if (stopped()) {
717
- logger$5(`Model "${modelName}" was stopped while creating its properties; not creating the rest`);
718
- return;
719
- }
720
- // Create ModelProperty instance
721
- // This will load from DB if it exists, or create new instance
722
- // The property should already be in DB from writeModelToDb
723
- // Note: propertyFileId is the schemaFileId (string), not the database ID (number)
724
- // We pass it as _propertyFileId so getById() can find the instance
725
- const property = await ModelProperty.create({
726
- name: propName,
727
- modelName,
728
- modelId: _dbId,
729
- dataType: propData.dataType,
730
- ref: propData.ref,
731
- refModelName: propData.refModelName,
732
- refModelId,
733
- refValueType: propData.refValueType,
734
- // Definitions built from DB rows may lack storage settings; the row has them.
735
- storageType: propData.storageType ?? propertyRecord?.storageType ?? undefined,
736
- localStorageDir: propData.localStorageDir ?? propertyRecord?.localStorageDir ?? undefined,
737
- filenameSuffix: propData.filenameSuffix ?? propertyRecord?.filenameSuffix ?? undefined,
738
- required: propData.required,
739
- // Rules defined in code live only on the instance (the properties table has no column
740
- // for them); getPropertySchema reads them from here when no schema file has them.
741
- validation: propData.validation,
742
- htmlEmbeddedDataUriPolicy: propData.htmlEmbeddedDataUriPolicy,
743
- _propertyFileId: propertyFileId, // Store schemaFileId for getById() lookups
744
- _dbId: propertyRecord?.schemaFileId === propertyFileId ? propertyRecord.id : undefined, // Already persisted: skip the write
745
- }, { schemaName }); // Use 'as any' because _propertyFileId is not in TProperty type
746
- // The model keeps its properties; an app's unload of the same instance doesn't stop them.
747
- ModelProperty.adoptForModel(property);
748
- }
749
- logger$5(`Successfully created all properties for model "${modelName}"`);
750
- sendBack({ type: 'createModelPropertiesSuccess' });
751
- };
752
- _createProperties().catch((error) => {
753
- logger$5(`Error creating model properties: ${error}`);
754
- sendBack({
755
- type: 'createModelPropertiesError',
756
- error: error instanceof Error ? error : new Error(String(error))
757
- });
758
- });
759
- return () => {
760
- // Cleanup function (optional)
761
- };
762
- });
763
-
764
- /**
765
- * Eviction epochs: lets work that was started before a schema's Model/ModelProperty instances were
766
- * evicted (Schema.destroy, test cleanup) tell, when it finishes, that it must not put instances for
767
- * that schema back in the caches. Stopping the evicted actors can't cancel such work (a DB lookup
768
- * that ends in Model.create, a model's property creation), and finishing it after the eviction would
769
- * rebuild instances from rows that are about to be deleted, which then write themselves again.
770
- *
771
- * Capture `currentEvictionEpoch()` when the work starts; before creating or caching an instance, skip
772
- * it when `schemaEvictedSince(schemaName, epoch)`. Work started after an eviction is unaffected.
773
- */
774
- let epoch = 0;
775
- const lastEvictionBySchema = new Map();
776
- // Schemas whose destroy() is still running (by name)
777
- const destroyingSchemas = new Map();
778
- // Single models destroyed (Model.destroy), by modelFileId
779
- const lastDestroyByModelId = new Map();
780
- // Models whose destroy() is still deleting their rows
781
- const destroyingModelIds = new Map();
782
- function currentEvictionEpoch() {
783
- return epoch;
784
- }
785
- /** Called by Model.evictForSchema. */
786
- function recordSchemaEviction(schemaName) {
787
- epoch += 1;
788
- lastEvictionBySchema.set(schemaName, epoch);
789
- }
790
- /**
791
- * Called by Schema.destroy before it evicts the schema's models; `finishSchemaDestroy` once its rows
792
- * are gone. The schema's actor stays live until then, and work it starts meanwhile (its live query
793
- * reading a model row the drained write just landed) captures an epoch after the eviction, so the
794
- * epoch alone can't tell it to stay away.
795
- */
796
- function recordSchemaDestroy(schemaName) {
797
- recordSchemaEviction(schemaName);
798
- destroyingSchemas.set(schemaName, (destroyingSchemas.get(schemaName) ?? 0) + 1);
799
- }
800
- function finishSchemaDestroy(schemaName) {
801
- const remaining = (destroyingSchemas.get(schemaName) ?? 1) - 1;
802
- if (remaining > 0)
803
- destroyingSchemas.set(schemaName, remaining);
804
- else
805
- destroyingSchemas.delete(schemaName);
806
- // Work that read a row during the destroy and checks after it must still see the eviction
807
- recordSchemaEviction(schemaName);
808
- }
809
- /**
810
- * True when the schema is being destroyed, or its instances were evicted after `since` (an epoch
811
- * from currentEvictionEpoch).
812
- */
813
- function schemaEvictedSince(schemaName, since) {
814
- if (!schemaName)
815
- return false;
816
- if (destroyingSchemas.has(schemaName))
817
- return true;
818
- if (epoch === since)
819
- return false;
820
- return (lastEvictionBySchema.get(schemaName) ?? 0) > since;
821
- }
822
- /**
823
- * Called by Model.destroy before it clears the caches; `finishModelDestroy` once its rows are gone.
824
- * Until then a lookup by id still finds the row, so it must not re-cache the model either.
825
- */
826
- function recordModelDestroy(modelFileId) {
827
- epoch += 1;
828
- lastDestroyByModelId.set(modelFileId, epoch);
829
- destroyingModelIds.set(modelFileId, (destroyingModelIds.get(modelFileId) ?? 0) + 1);
830
- }
831
- function finishModelDestroy(modelFileId) {
832
- const remaining = (destroyingModelIds.get(modelFileId) ?? 1) - 1;
833
- if (remaining > 0)
834
- destroyingModelIds.set(modelFileId, remaining);
835
- else
836
- destroyingModelIds.delete(modelFileId);
837
- }
838
- /**
839
- * True when the model is being destroyed, or was destroyed after `since` (an epoch from
840
- * currentEvictionEpoch): a lookup that read its row must not create or cache an instance.
841
- */
842
- function modelDestroyedSince(modelFileId, since) {
843
- if (!modelFileId)
844
- return false;
845
- if (destroyingModelIds.has(modelFileId))
846
- return true;
847
- if (epoch === since)
848
- return false;
849
- return (lastDestroyByModelId.get(modelFileId) ?? 0) > since;
850
- }
851
- /**
852
- * True when any schema was evicted after `since` or is being destroyed (cheap check before looking up
853
- * which schemas apply).
854
- */
855
- function anyEvictionSince(since) {
856
- return epoch !== since || destroyingSchemas.size > 0;
857
- }
858
-
859
- const logger$4 = debug('seedSdk:write:validateEntity');
860
- // XState v5 type inference bug: fromPromise types are inverted
861
- // We use type assertion to work around this
862
- const validateEntity = fromPromise(async ({ input }) => {
863
- // Type assertion to fix XState v5 type inference bug
864
- // Convert through unknown to avoid type overlap error
865
- const entityInput = input;
866
- // Validation awaits imports before looking up the schema; the entity can be evicted with its
867
- // schema meanwhile (Schema.destroy, test cleanup). Schema.create would then load the evicted
868
- // schema again, and its load re-creates the schema's models from rows being deleted.
869
- const evictionEpoch = currentEvictionEpoch();
870
- /** The schema to validate against; throws (validation then skips the schema check) once it was evicted. */
871
- const schemaForValidation = async (schemaName) => {
872
- const _mod_9 = await import('./Schema-BS8oR7QV.js');
873
- const _ns_9 = _mod_9.a;
874
- const { Schema } = _ns_9;
875
- if (schemaEvictedSince(schemaName, evictionEpoch)) {
876
- throw new Error(`Schema "${schemaName}" was evicted during validation`);
877
- }
878
- return Schema.create(schemaName, { waitForReady: false, hold: false });
879
- };
880
- const _validate = async () => {
881
- try {
882
- const msg = `[validateEntity] Starting validation for ${entityInput.entityType}`;
883
- logger$4(msg);
884
- logger$4(`[validateEntity] Entity data:`, entityInput.entityData);
885
- let result = { isValid: true, errors: [] };
886
- if (entityInput.entityType === 'model') {
887
- const structureMsg = `[validateEntity] Validating model structure`;
888
- logger$4(structureMsg);
889
- // Use existing Model validation
890
- const validationServiceMod = await import('./SchemaValidationService-CZzKNL04.js');
891
- const { SchemaValidationService } = validationServiceMod;
892
- const validationService = new SchemaValidationService();
893
- // Validate model structure
894
- const structureResult = validationService.validateModelStructure(entityInput.entityData);
895
- const structureResultMsg = `[validateEntity] Structure validation result: isValid=${structureResult.isValid}, errors=${structureResult.errors?.length || 0}`;
896
- logger$4(structureResultMsg);
897
- if (!structureResult.isValid) {
898
- result = {
899
- isValid: false,
900
- errors: structureResult.errors,
901
- };
902
- }
903
- else {
904
- // If schema name provided, validate against schema
905
- if (entityInput.entityData.schemaName) {
906
- try {
907
- logger$4(`[validateEntity] Validating model against schema "${entityInput.entityData.schemaName}"`);
908
- const schema = await schemaForValidation(entityInput.entityData.schemaName);
909
- const schemaSnapshot = schema.getService().getSnapshot();
910
- const schemaStatus = schemaSnapshot.value;
911
- logger$4(`[validateEntity] Schema status: ${schemaStatus}`);
912
- if (schemaStatus === 'idle') {
913
- const schemaContext = schemaSnapshot.context;
914
- logger$4(`[validateEntity] Running validateModelAgainstSchema`);
915
- const schemaResult = validationService.validateModelAgainstSchema(schemaContext, entityInput.entityData.modelName, entityInput.entityData);
916
- logger$4(`[validateEntity] Schema validation result:`, schemaResult);
917
- if (!schemaResult.isValid) {
918
- result = {
919
- isValid: false,
920
- errors: schemaResult.errors,
921
- };
922
- }
923
- }
924
- else {
925
- logger$4(`[validateEntity] Schema not in idle state (${schemaStatus}), skipping schema validation`);
926
- }
927
- }
928
- catch (error) {
929
- logger$4('Error validating model against schema:', error);
930
- // Continue with structure validation only
931
- }
932
- }
933
- else {
934
- logger$4(`[validateEntity] No schemaName provided, skipping schema validation`);
935
- }
936
- }
937
- }
938
- else if (entityInput.entityType === 'modelProperty') {
939
- // Use existing ModelProperty validation
940
- const validationServiceMod = await import('./SchemaValidationService-CZzKNL04.js');
941
- const { SchemaValidationService } = validationServiceMod;
942
- const validationService = new SchemaValidationService();
943
- // Validate property structure
944
- const structureResult = validationService.validatePropertyStructure(entityInput.entityData);
945
- if (!structureResult.isValid) {
946
- result = {
947
- isValid: false,
948
- errors: structureResult.errors,
949
- };
950
- }
951
- else {
952
- // If schema name and model name provided, validate against schema
953
- if (entityInput.entityData._schemaName && entityInput.entityData.modelName) {
954
- try {
955
- const schema = await schemaForValidation(entityInput.entityData._schemaName);
956
- const schemaSnapshot = schema.getService().getSnapshot();
957
- const schemaStatus = schemaSnapshot.value;
958
- if (schemaStatus === 'idle') {
959
- const schemaContext = schemaSnapshot.context;
960
- if (schemaContext.models && Object.keys(schemaContext.models).length > 0) {
961
- const schemaResult = validationService.validateProperty(schemaContext, entityInput.entityData.modelName, entityInput.entityData.name || '', entityInput.entityData);
962
- if (!schemaResult.isValid) {
963
- result = {
964
- isValid: false,
965
- errors: schemaResult.errors,
966
- };
967
- }
968
- }
969
- }
970
- }
971
- catch (error) {
972
- logger$4('Error validating property against schema:', error);
973
- // Continue with structure validation only
974
- }
975
- }
976
- }
977
- }
978
- // Return the result - fromPromise will automatically put this in event.output
979
- const completeMsg = `[validateEntity] Validation complete: isValid=${result.isValid}, errors=${result.errors?.length || 0}`;
980
- logger$4(completeMsg);
981
- logger$4(`[validateEntity] Validation complete:`, result);
982
- return result;
983
- }
984
- catch (error) {
985
- const errorMsg = `[validateEntity] Error in validateEntity: ${error instanceof Error ? error.message : String(error)}`;
986
- logger$4(errorMsg);
987
- logger$4('Error in validateEntity:', error);
988
- // Throw error - fromPromise will automatically create error.platform event
989
- throw error;
990
- }
991
- };
992
- return await _validate();
993
- });
994
-
995
- const logger$3 = debug('seedSdk:write:writeToDatabase');
996
- // Writes that have started. Stopping a write process's actor can't cancel one, so code that deletes rows
997
- // under live instances (Schema.destroy, test cleanup) waits for these after evicting the instances.
998
- const inFlightWrites = new Set();
999
- /**
1000
- * Resolves once every database write started so far has finished (or after timeoutMs). Evict/stop the
1001
- * instances first so no new writes start while waiting.
1002
- */
1003
- const waitForInFlightWrites = async (timeoutMs = 5000) => {
1004
- const deadline = Date.now() + timeoutMs;
1005
- while (inFlightWrites.size > 0 && Date.now() < deadline) {
1006
- await Promise.race([
1007
- Promise.allSettled([...inFlightWrites]),
1008
- new Promise((resolve) => setTimeout(resolve, deadline - Date.now())),
1009
- ]);
1010
- }
1011
- };
1012
- const writeToDatabase = fromCallback(({ sendBack, input }) => {
1013
- const _write = async () => {
1014
- try {
1015
- const db = BaseDb.getAppDb();
1016
- if (!db) {
1017
- throw new Error('Database not available');
1018
- }
1019
- let output = input.entityData;
1020
- if (input.entityType === 'model') {
1021
- const writeMsg = `Writing model to database: ${input.entityData.modelName} (schemaId: ${input.entityData.schemaId})`;
1022
- logger$3(writeMsg);
1023
- logger$3(`Writing model to database:`, {
1024
- modelFileId: input.entityId,
1025
- modelName: input.entityData.modelName,
1026
- schemaId: input.entityData.schemaId,
1027
- });
1028
- await writeModelToDb(input.entityId, input.entityData);
1029
- // Get _dbId from database after write
1030
- const db = BaseDb.getAppDb();
1031
- if (db) {
1032
- const modelRecords = await db
1033
- .select({ id: models.id })
1034
- .from(models)
1035
- .where(eq(models.schemaFileId, input.entityId)) // entityId is now the schemaFileId (string)
1036
- .limit(1);
1037
- if (modelRecords.length > 0 && modelRecords[0].id) {
1038
- output = { ...input.entityData, _dbId: modelRecords[0].id }; // Store as _dbId (database integer ID)
1039
- }
1040
- else {
1041
- output = input.entityData;
1042
- }
1043
- }
1044
- else {
1045
- output = input.entityData;
1046
- }
1047
- const successMsg = `Successfully wrote model "${input.entityData.modelName}" to database`;
1048
- logger$3(successMsg);
1049
- }
1050
- else if (input.entityType === 'modelProperty') {
1051
- // Use current ModelProperty context when available so a user rename before the
1052
- // initial write completes is not overwritten by the stale requestWrite payload.
1053
- let dataToWrite = input.entityData;
1054
- try {
1055
- const mod = await import('./ModelProperty-DDD47KVm.js');
1056
- const ModelProperty = mod?.ModelProperty ?? mod?.default;
1057
- if (ModelProperty && typeof ModelProperty.getById === 'function') {
1058
- const instance = ModelProperty.getById(input.entityId);
1059
- if (instance && typeof instance._getSnapshotContext === 'function') {
1060
- const ctx = instance._getSnapshotContext();
1061
- const ctxName = ctx.name ?? input.entityData.name;
1062
- dataToWrite = {
1063
- ...input.entityData,
1064
- name: ctxName,
1065
- dataType: ctx.dataType ?? input.entityData.dataType,
1066
- refModelId: ctx.refModelId ?? input.entityData.refModelId,
1067
- refValueType: ctx.refValueType ?? input.entityData.refValueType,
1068
- refModelName: ctx.refModelName ?? input.entityData.refModelName,
1069
- storageType: ctx.storageType ?? input.entityData.storageType,
1070
- localStorageDir: ctx.localStorageDir ?? input.entityData.localStorageDir,
1071
- filenameSuffix: ctx.filenameSuffix ?? input.entityData.filenameSuffix,
1072
- required: ctx.required ?? input.entityData.required,
1073
- };
1074
- }
1075
- }
1076
- }
1077
- catch (_) {
1078
- // Fall back to input.entityData if instance not available
1079
- }
1080
- await writePropertyToDb(input.entityId, dataToWrite);
1081
- output = input.entityData;
1082
- }
1083
- else {
1084
- throw new Error(`Unknown entity type: ${input.entityType}`);
1085
- }
1086
- // Send success event explicitly - callback actors don't trigger onDone
1087
- sendBack({
1088
- type: 'writeSuccess',
1089
- output,
1090
- });
1091
- }
1092
- catch (error) {
1093
- logger$3('Error in writeToDatabase:', error);
1094
- sendBack({
1095
- type: 'writeError',
1096
- error: error instanceof Error ? error : new Error(String(error)),
1097
- });
1098
- }
1099
- };
1100
- const write = _write().catch((error) => {
1101
- logger$3('Error in writeToDatabase promise:', error);
1102
- sendBack({
1103
- type: 'writeError',
1104
- error: error instanceof Error ? error : new Error(String(error)),
1105
- });
1106
- });
1107
- inFlightWrites.add(write);
1108
- write.finally(() => inFlightWrites.delete(write));
1109
- return () => {
1110
- // Cleanup function (optional)
1111
- };
1112
- });
1113
-
1114
- var writeToDatabase$1 = /*#__PURE__*/Object.freeze({
1115
- __proto__: null,
1116
- waitForInFlightWrites: waitForInFlightWrites,
1117
- writeToDatabase: writeToDatabase
1118
- });
1119
-
1120
- const logger$2 = debug('seedSdk:write:writeProcessMachine');
1121
- const writeProcessMachine = setup({
1122
- types: {
1123
- context: {},
1124
- input: {},
1125
- events: {},
1126
- },
1127
- actors: {
1128
- validateEntity,
1129
- writeToDatabase,
1130
- },
1131
- }).createMachine({
1132
- id: 'writeProcess',
1133
- initial: 'idle',
1134
- context: ({ input }) => ({
1135
- entityType: input.entityType,
1136
- entityId: input.entityId,
1137
- entityData: input.entityData || {},
1138
- validationErrors: [],
1139
- error: null,
1140
- pendingWrite: null,
1141
- }),
1142
- states: {
1143
- idle: {
1144
- on: {
1145
- startWrite: {
1146
- target: 'validating',
1147
- actions: [
1148
- assign({
1149
- pendingWrite: ({ event }) => ({
1150
- data: event.data,
1151
- timestamp: Date.now(),
1152
- }),
1153
- }),
1154
- ({ event, context }) => {
1155
- logger$2(`[startWrite] Received startWrite for ${context.entityType} "${context.entityId}"`);
1156
- logger$2(`[startWrite] Data:`, event.data);
1157
- },
1158
- ],
1159
- },
1160
- },
1161
- },
1162
- validating: {
1163
- entry: ({ context }) => {
1164
- const msg = `[validating] Entering validating state for ${context.entityType} "${context.entityId}"`;
1165
- logger$2(msg);
1166
- logger$2(`[validating] Entity data:`, context.pendingWrite?.data || context.entityData);
1167
- },
1168
- invoke: {
1169
- src: 'validateEntity',
1170
- input: ({ context }) => {
1171
- const entityData = context.pendingWrite?.data || context.entityData;
1172
- logger$2(`[validating] Invoking validateEntity with:`, { entityType: context.entityType, entityData });
1173
- return {
1174
- entityType: context.entityType,
1175
- entityData,
1176
- };
1177
- },
1178
- onDone: [
1179
- {
1180
- target: 'writing',
1181
- guard: ({ event }) => {
1182
- // The output might be the result directly, or it might be wrapped in an event
1183
- let result;
1184
- if (event.output && typeof event.output === 'object' && 'isValid' in event.output) {
1185
- result = event.output;
1186
- }
1187
- else if (event.output && typeof event.output === 'object' && 'type' in event.output) {
1188
- // If it's wrapped in an event, extract the result
1189
- result = event.output;
1190
- }
1191
- else {
1192
- console.error(`[validating] Unexpected event.output format! Event:`, event);
1193
- return false;
1194
- }
1195
- if (!result || typeof result.isValid !== 'boolean') {
1196
- console.error(`[validating] Invalid result format! Result:`, result);
1197
- return false;
1198
- }
1199
- const resultMsg = `[validating] Validation result: isValid=${result.isValid}, errors=${result.errors?.length || 0}`;
1200
- logger$2(resultMsg);
1201
- logger$2(`[validating] Validation result:`, result);
1202
- return result.isValid;
1203
- },
1204
- actions: [
1205
- assign({
1206
- validationErrors: [],
1207
- }),
1208
- ({ context }) => {
1209
- const msg = `[validating] Validation passed, transitioning to writing for ${context.entityType} "${context.entityId}"`;
1210
- logger$2(msg);
1211
- },
1212
- ],
1213
- },
1214
- {
1215
- target: 'error',
1216
- actions: [
1217
- assign({
1218
- validationErrors: ({ event }) => {
1219
- const result = event.output;
1220
- return result.errors || [];
1221
- },
1222
- error: ({ event }) => {
1223
- const result = event.output;
1224
- return new Error(`Validation failed: ${result.errors.map(e => e.message).join(', ')}`);
1225
- },
1226
- }),
1227
- ({ context, event }) => {
1228
- const result = event.output;
1229
- logger$2(`[validating] Validation failed for ${context.entityType} "${context.entityId}":`, result.errors);
1230
- },
1231
- ],
1232
- },
1233
- ],
1234
- onError: {
1235
- target: 'error',
1236
- actions: [
1237
- assign({
1238
- error: ({ event }) => event.error instanceof Error ? event.error : new Error(String(event.error)),
1239
- }),
1240
- ({ context, event }) => {
1241
- const errorMsg = `[validating] Validation error for ${context.entityType} "${context.entityId}": ${event.error}`;
1242
- logger$2(errorMsg);
1243
- console.error(errorMsg, event.error); // Always log to console
1244
- },
1245
- ],
1246
- },
1247
- },
1248
- },
1249
- writing: {
1250
- invoke: {
1251
- src: 'writeToDatabase',
1252
- input: ({ context }) => {
1253
- const entityData = context.pendingWrite?.data || context.entityData;
1254
- logger$2(`[writing] Calling writeToDatabase for ${context.entityType} "${context.entityId}"`);
1255
- logger$2(`[writing] Entity data:`, entityData);
1256
- return {
1257
- entityType: context.entityType,
1258
- entityId: context.entityId,
1259
- entityData,
1260
- };
1261
- },
1262
- },
1263
- on: {
1264
- writeSuccess: {
1265
- target: 'success',
1266
- actions: [
1267
- assign({
1268
- pendingWrite: null,
1269
- entityData: ({ context, event }) => {
1270
- // Update entityData with any returned data from write
1271
- return event.output || context.entityData;
1272
- },
1273
- }),
1274
- ({ context }) => {
1275
- logger$2(`[writing] Write successful for ${context.entityType} "${context.entityId}"`);
1276
- },
1277
- ],
1278
- },
1279
- writeError: {
1280
- target: 'error',
1281
- actions: [
1282
- assign({
1283
- error: ({ event }) => event.error instanceof Error ? event.error : new Error(String(event.error)),
1284
- }),
1285
- ({ context, event }) => {
1286
- const errorMsg = `[writing] Write error for ${context.entityType} "${context.entityId}": ${event.error}`;
1287
- logger$2(errorMsg);
1288
- // Nothing else reports a failed persist to the caller, so always log to console
1289
- console.error(errorMsg, event.error);
1290
- },
1291
- ],
1292
- },
1293
- },
1294
- },
1295
- success: {
1296
- after: {
1297
- 2000: { target: 'idle' }, // Auto-reset after 2s
1298
- },
1299
- },
1300
- error: {},
1301
- },
1302
- });
1303
-
1304
- const logger$1 = debug('seedSdk:model:modelMachine');
1305
- const modelMachine = setup({
1306
- types: {
1307
- context: {},
1308
- input: {},
1309
- events: {},
1310
- },
1311
- actors: {
1312
- loadOrCreateModel,
1313
- validateModel,
1314
- createModelProperties,
1315
- writeProcessMachine,
1316
- },
1317
- guards: {
1318
- isModelValid: ({ context }) => {
1319
- return !context._validationErrors || context._validationErrors.length === 0;
1320
- },
1321
- },
1322
- actions: {
1323
- assignValidationErrors: assign(({ context, event }) => {
1324
- if (event.type === 'validationError') {
1325
- return {
1326
- ...context,
1327
- _validationErrors: event.errors,
1328
- };
1329
- }
1330
- return context;
1331
- }),
1332
- },
1333
- }).createMachine({
1334
- id: 'model',
1335
- initial: 'loading',
1336
- context: ({ input }) => ({
1337
- id: input.id,
1338
- _dbId: undefined,
1339
- modelName: input.modelName,
1340
- schemaName: input.schemaName,
1341
- _pendingPropertyDefinitions: input._pendingPropertyDefinitions,
1342
- _idFromSchema: input._idFromSchema,
1343
- _isDraft: false,
1344
- _editedProperties: new Set(),
1345
- _validationErrors: undefined,
1346
- writeProcess: undefined,
1347
- _liveQueryPropertyIds: [], // Initialize empty array
1348
- }),
1349
- on: {
1350
- updateContext: [
1351
- {
1352
- // Always update context first (no guard, always runs)
1353
- actions: [
1354
- assign(({ context, event }) => {
1355
- const newContext = { ...context };
1356
- // Check if this is only updating internal fields
1357
- const onlyInternalFields = Object.keys(event).every((key) => {
1358
- return key === 'type' || key.startsWith('_');
1359
- });
1360
- // Update the context with new values
1361
- for (let i = 0; i < Object.keys(event).length; i++) {
1362
- const key = Object.keys(event)[i];
1363
- if (key === 'type') {
1364
- continue;
1365
- }
1366
- const eventValue = event[key];
1367
- // Ensure arrays are properly copied (create new reference)
1368
- newContext[key] = Array.isArray(eventValue) ? [...eventValue] : eventValue;
1369
- }
1370
- // Compare with original values and set _isEdited flag (only for non-internal updates)
1371
- // Note: properties are not in context - they're computed from ModelProperty instances
1372
- // Property changes are tracked via ModelProperty._isEdited flags
1373
- if (!onlyInternalFields && context._originalValues) {
1374
- const hasChanges = Object.keys(event).some((key) => {
1375
- if (key === 'type' || key.startsWith('_') || key === 'properties')
1376
- return false;
1377
- const newValue = newContext[key];
1378
- const oldValue = context._originalValues?.[key];
1379
- // Deep comparison for objects/arrays
1380
- if (typeof newValue === 'object' && typeof oldValue === 'object') {
1381
- return JSON.stringify(newValue) !== JSON.stringify(oldValue);
1382
- }
1383
- return newValue !== oldValue;
1384
- });
1385
- newContext._isEdited = hasChanges;
1386
- }
1387
- // Clear validation errors on context update (will be re-validated if needed)
1388
- newContext._validationErrors = undefined;
1389
- return newContext;
1390
- }),
1391
- // Verify the context was updated and create property instances if needed
1392
- ({ event, self }) => {
1393
- if (event._liveQueryPropertyIds !== undefined) {
1394
- const newPropertyIds = event._liveQueryPropertyIds;
1395
- // The model can be evicted with its schema (Schema.destroy, test cleanup) before the
1396
- // timer and import below run: then create nothing, and drop lookups an eviction overtakes.
1397
- const evictionEpoch = currentEvictionEpoch();
1398
- // Use setTimeout to check after assign has been applied
1399
- setTimeout(() => {
1400
- const snapshot = self.getSnapshot();
1401
- if (snapshot.status === 'stopped')
1402
- return;
1403
- // Create ModelProperty instances for any new property IDs
1404
- if (Array.isArray(newPropertyIds) && newPropertyIds.length > 0) {
1405
- // Import and create instances asynchronously (fire-and-forget)
1406
- import('./ModelProperty-DDD47KVm.js').then(({ ModelProperty }) => {
1407
- if (self.getSnapshot().status === 'stopped')
1408
- return;
1409
- const createPromises = newPropertyIds.map(async (propertyFileId) => {
1410
- try {
1411
- const property = await ModelProperty.createById(propertyFileId, { evictionEpoch });
1412
- if (property) {
1413
- logger$1(`[modelMachine] Created/cached ModelProperty instance for propertyFileId "${propertyFileId}" after _liveQueryPropertyIds update`);
1414
- }
1415
- }
1416
- catch (error) {
1417
- logger$1(`[modelMachine] Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
1418
- }
1419
- });
1420
- Promise.all(createPromises).catch((error) => {
1421
- logger$1(`[modelMachine] Error creating property instances: ${error}`);
1422
- });
1423
- }).catch((error) => {
1424
- logger$1(`[modelMachine] Error importing ModelProperty: ${error}`);
1425
- });
1426
- }
1427
- }, 0);
1428
- }
1429
- },
1430
- ],
1431
- // Don't transition - stay in current state, context will be updated
1432
- },
1433
- {
1434
- // Conditionally trigger validation for non-internal updates
1435
- guard: ({ event }) => {
1436
- return !Object.keys(event).every((key) => {
1437
- return key === 'type' || key.startsWith('_');
1438
- });
1439
- },
1440
- target: '.validating',
1441
- },
1442
- ],
1443
- validateModel: {
1444
- target: '.validating',
1445
- },
1446
- validationSuccess: {
1447
- actions: assign(({ context }) => ({
1448
- ...context,
1449
- _validationErrors: [],
1450
- })),
1451
- },
1452
- validationError: {
1453
- actions: 'assignValidationErrors',
1454
- },
1455
- initializeOriginalValues: {
1456
- actions: assign(({ context, event }) => ({
1457
- ...context,
1458
- _originalValues: event.originalValues,
1459
- _isEdited: event.isEdited ?? false,
1460
- _validationErrors: undefined,
1461
- })),
1462
- },
1463
- markAsDraft: {
1464
- actions: assign(({ context, event }) => ({
1465
- ...context,
1466
- _isEdited: true,
1467
- _editedProperties: new Set([
1468
- ...(context._editedProperties || []),
1469
- event.propertyKey,
1470
- ]),
1471
- })),
1472
- },
1473
- clearDraft: {
1474
- actions: assign(({ context }) => ({
1475
- ...context,
1476
- _isEdited: false,
1477
- _editedProperties: new Set(),
1478
- _originalValues: {
1479
- // Properties are not stored in context - they're computed from ModelProperty instances
1480
- // Properties serialization happens separately when initializing original values
1481
- },
1482
- })),
1483
- },
1484
- destroyStarted: {
1485
- actions: assign({ _destroyError: null }),
1486
- },
1487
- destroyError: {
1488
- actions: assign(({ event }) => ({
1489
- _destroyError: event.error instanceof Error
1490
- ? { message: event.error.message, name: event.error.name }
1491
- : { message: String(event.error) },
1492
- })),
1493
- },
1494
- },
1495
- states: {
1496
- loading: {
1497
- on: {
1498
- loadOrCreateModelSuccess: {
1499
- target: 'idle',
1500
- actions: assign(({ context, event }) => {
1501
- const hasPendingProps = !!(context._pendingPropertyDefinitions && Object.keys(context._pendingPropertyDefinitions).length > 0);
1502
- logger$1(`[loadOrCreateModelSuccess] Preserving _pendingPropertyDefinitions: ${hasPendingProps} (${Object.keys(context._pendingPropertyDefinitions || {}).length} properties)`);
1503
- // Check if there's a pending model name update (set before this event to avoid validation trigger)
1504
- const finalModelName = context._pendingModelName || context.modelName;
1505
- if (context._pendingModelName) {
1506
- logger$1(`[loadOrCreateModelSuccess] Applying pending model name: "${context.modelName}" -> "${finalModelName}"`);
1507
- }
1508
- return {
1509
- ...context,
1510
- id: event.model.id, // schemaFileId (string)
1511
- _dbId: event.model._dbId, // Database integer ID
1512
- _isEdited: event.model._isEdited ?? false, // Load isEdited from database
1513
- _editedProperties: new Set(),
1514
- _validationErrors: undefined,
1515
- // Use final model name (may have been set via _pendingModelName to avoid validation trigger)
1516
- modelName: finalModelName,
1517
- // Clear the temporary field
1518
- _pendingModelName: undefined,
1519
- // Preserve _pendingPropertyDefinitions if it exists
1520
- _pendingPropertyDefinitions: context._pendingPropertyDefinitions,
1521
- // Preserve _liveQueryPropertyIds if provided (set by loadOrCreateModel when loading from DB)
1522
- _liveQueryPropertyIds: event.model._liveQueryPropertyIds ?? context._liveQueryPropertyIds ?? [],
1523
- // Preserve conflict detection metadata if provided
1524
- _loadedAt: event.model._loadedAt,
1525
- _dbVersion: event.model._dbVersion,
1526
- _dbUpdatedAt: event.model._dbUpdatedAt,
1527
- _originalValues: {
1528
- // Properties are not stored in context - they're computed from ModelProperty instances
1529
- // Properties will be loaded via liveQuery after model creation
1530
- },
1531
- };
1532
- }),
1533
- },
1534
- loadOrCreateModelError: {
1535
- target: 'error',
1536
- },
1537
- },
1538
- invoke: {
1539
- src: 'loadOrCreateModel',
1540
- input: ({ context }) => ({ context }),
1541
- },
1542
- },
1543
- idle: {
1544
- entry: assign({
1545
- writeProcess: ({ context, spawn }) => {
1546
- if (!context.writeProcess && context.id) {
1547
- logger$1(`[idle entry] Spawning writeProcess for model "${context.modelName}" (${context.id})`);
1548
- return spawn(writeProcessMachine, {
1549
- input: {
1550
- entityType: 'model',
1551
- entityId: context.id,
1552
- entityData: {
1553
- modelName: context.modelName,
1554
- schemaName: context.schemaName,
1555
- // Properties are not stored in context - they'll be provided via requestWrite event
1556
- },
1557
- },
1558
- });
1559
- }
1560
- return context.writeProcess;
1561
- },
1562
- }),
1563
- on: {
1564
- refreshProperties: {
1565
- actions: () => {
1566
- // This event will be handled by the Model instance's subscription
1567
- // The Model will call _refreshPropertiesFromDb when it receives this event
1568
- logger$1(`[idle] refreshProperties event received, will be handled by Model instance`);
1569
- },
1570
- },
1571
- validateModel: {
1572
- target: 'validating',
1573
- },
1574
- reloadFromDb: {
1575
- target: 'loading',
1576
- },
1577
- requestWrite: {
1578
- actions: ({ context, event }) => {
1579
- if (context.writeProcess) {
1580
- logger$1(`[requestWrite] Sending startWrite to writeProcess with data:`, event.data);
1581
- context.writeProcess.send({
1582
- type: 'startWrite',
1583
- data: event.data,
1584
- });
1585
- }
1586
- else {
1587
- logger$1(`[requestWrite] ERROR: writeProcess not available for model "${context.modelName}" (${context.id})`);
1588
- }
1589
- },
1590
- },
1591
- writeSuccess: [
1592
- {
1593
- guard: ({ context }) => {
1594
- // If we have pending property definitions, transition to creatingProperties
1595
- const hasPending = !!(context._pendingPropertyDefinitions && Object.keys(context._pendingPropertyDefinitions).length > 0);
1596
- logger$1(`[writeSuccess guard] hasPending: ${hasPending}, _pendingPropertyDefinitions keys: ${context._pendingPropertyDefinitions ? Object.keys(context._pendingPropertyDefinitions).length : 0}`);
1597
- return hasPending;
1598
- },
1599
- target: 'creatingProperties',
1600
- actions: assign(({ context, event }) => {
1601
- logger$1(`[writeSuccess] Transitioning to creatingProperties for model "${context.modelName}"`);
1602
- // Update _dbId from write output if available (writeToDatabase returns _dbId, not id)
1603
- const newContext = { ...context };
1604
- const dbId = event.output?._dbId ?? event.output?.id;
1605
- if (dbId != null) {
1606
- newContext._dbId = dbId;
1607
- logger$1(`[writeSuccess] Updated _dbId to ${dbId}`);
1608
- }
1609
- return newContext;
1610
- }),
1611
- },
1612
- {
1613
- target: 'idle',
1614
- actions: assign(({ context, event }) => {
1615
- // Update _dbId from write output if available (writeToDatabase returns _dbId, not id)
1616
- const newContext = { ...context };
1617
- const dbId = event.output?._dbId ?? event.output?.id;
1618
- if (dbId != null) {
1619
- newContext._dbId = dbId;
1620
- }
1621
- return newContext;
1622
- }),
1623
- },
1624
- ],
1625
- },
1626
- // No `always: hasValidationErrors -> validating` here: validation is re-run when the context changes
1627
- // (updateContext) or on request (validateModel). Re-entering while errors exist would loop forever on a
1628
- // model that is still invalid.
1629
- },
1630
- validating: {
1631
- on: {
1632
- validationSuccess: {
1633
- target: 'idle',
1634
- actions: assign(({ context }) => ({
1635
- ...context,
1636
- _validationErrors: [],
1637
- })),
1638
- },
1639
- validationError: {
1640
- target: 'idle',
1641
- actions: 'assignValidationErrors',
1642
- },
1643
- },
1644
- invoke: {
1645
- src: 'validateModel',
1646
- input: ({ context }) => ({ context }),
1647
- },
1648
- },
1649
- creatingProperties: {
1650
- entry: ({ context }) => {
1651
- logger$1(`[creatingProperties entry] Starting property creation for model "${context.modelName}" with ${Object.keys(context._pendingPropertyDefinitions || {}).length} properties`);
1652
- },
1653
- invoke: {
1654
- src: 'createModelProperties',
1655
- input: ({ context }) => {
1656
- logger$1(`[creatingProperties invoke] Invoking createModelProperties with ${Object.keys(context._pendingPropertyDefinitions || {}).length} property definitions`);
1657
- return {
1658
- context,
1659
- propertyDefinitions: context._pendingPropertyDefinitions || {},
1660
- };
1661
- },
1662
- },
1663
- on: {
1664
- createModelPropertiesSuccess: {
1665
- target: 'idle',
1666
- actions: [
1667
- assign(({ context }) => {
1668
- // Only clear _pendingPropertyDefinitions, preserve everything else including _liveQueryPropertyIds
1669
- return {
1670
- ...context,
1671
- _pendingPropertyDefinitions: undefined,
1672
- };
1673
- }),
1674
- ({ self }) => {
1675
- // Trigger property refresh after properties are created
1676
- // This ensures _liveQueryPropertyIds is updated in Node.js where liveQuery isn't available
1677
- setTimeout(() => {
1678
- if (self.getSnapshot().status === 'stopped')
1679
- return;
1680
- self.send({ type: 'refreshProperties' });
1681
- }, 100); // Small delay to ensure properties are written to DB
1682
- },
1683
- ],
1684
- },
1685
- createModelPropertiesError: {
1686
- target: 'error',
1687
- actions: ({ event }) => {
1688
- logger$1('Error creating model properties:', event.error);
1689
- },
1690
- },
1691
- },
1692
- },
1693
- error: {},
1694
- },
1695
- });
1696
-
1697
- /**
1698
- * Error thrown when a conflict is detected between actor context and database
1699
- */
1700
- class ConflictError extends Error {
1701
- constructor(message, conflict) {
1702
- super(message);
1703
- this.name = 'ConflictError';
1704
- this.conflict = conflict;
1705
- Object.setPrototypeOf(this, ConflictError.prototype);
1706
- }
1707
- }
1708
-
1709
- /**
1710
- * Set up liveQuery subscription for an entity
1711
- * This enables cross-instance synchronization (e.g., changes in other tabs/windows)
1712
- *
1713
- * @param instance - Entity instance
1714
- * @param config - LiveQuery configuration
1715
- */
1716
- function setupEntityLiveQuery(instance, config) {
1717
- const isBrowser = typeof window !== 'undefined';
1718
- const logger = debug(config.loggerName);
1719
- // Use a closure variable to track setup state per instance
1720
- const setupState = { subscriptionSetUp: false };
1721
- const setupLiveQuery = async (entityId) => {
1722
- if (setupState.subscriptionSetUp) {
1723
- return;
1724
- }
1725
- setupState.subscriptionSetUp = true;
1726
- logger(`Setting up liveQuery for entity ID: ${entityId}`);
1727
- try {
1728
- // Query initial data if provided (for Node.js compatibility)
1729
- if (config.queryInitialData) {
1730
- const initialRows = await config.queryInitialData(entityId);
1731
- const initialIds = config.extractEntityIds(initialRows);
1732
- if (initialIds.length > 0) {
1733
- logger(`Initial query returned ${initialIds.length} entities`);
1734
- // Create child instances if provided (not for a stopped entity: an unloaded or evicted
1735
- // parent must not re-cache its children)
1736
- if (isActorStopped(instance.getService()))
1737
- return;
1738
- if (config.createChildInstances) {
1739
- await config.createChildInstances(initialIds);
1740
- }
1741
- // Update context with initial IDs (the queries above can outlive the instance)
1742
- if (isActorStopped(instance.getService()))
1743
- return;
1744
- config.updateContext(instance, initialIds);
1745
- }
1746
- }
1747
- // Only set up liveQuery subscription in browser environment
1748
- if (isBrowser) {
1749
- // buildQuery can be async to allow dynamic imports
1750
- const queryResult = config.buildQuery(entityId);
1751
- const query$ = queryResult instanceof Promise ? await queryResult : queryResult;
1752
- const instanceState = config.instanceState.get(instance);
1753
- if (!instanceState) {
1754
- logger('Instance state not found');
1755
- return;
1756
- }
1757
- // Unloaded or destroyed while setup awaited: subscribing now would leak the query.
1758
- if (isActorStopped(instance.getService()))
1759
- return;
1760
- // Subscribe to liveQuery updates
1761
- const subscription = query$.subscribe({
1762
- next: async (rows) => {
1763
- // Check if instance state still exists (hasn't been cleaned up)
1764
- const currentInstanceState = config.instanceState.get(instance);
1765
- if (!currentInstanceState) {
1766
- logger('Instance state was cleaned up, skipping update');
1767
- return;
1768
- }
1769
- logger(`Query returned ${rows.length} rows`);
1770
- const ids = config.extractEntityIds(rows);
1771
- // Create child instances if provided (before updating context; not for a stopped entity)
1772
- if (isActorStopped(instance.getService()))
1773
- return;
1774
- if (config.createChildInstances && ids.length > 0) {
1775
- // Nothing awaits this handler, so a child that fails to load (its row deleted while it
1776
- // loads, say) would be an unhandled rejection. Skip this result; a later one tries again.
1777
- try {
1778
- await config.createChildInstances(ids);
1779
- }
1780
- catch (error) {
1781
- logger(`createChildInstances failed: ${error}`);
1782
- return;
1783
- }
1784
- }
1785
- // Update context with new IDs
1786
- if (isActorStopped(instance.getService()))
1787
- return;
1788
- config.updateContext(instance, ids);
1789
- },
1790
- error: (error) => {
1791
- logger(`LiveQuery error: ${error}`);
1792
- },
1793
- });
1794
- instanceState.liveQuerySubscription = subscription;
1795
- logger(`LiveQuery subscription set up for entity ID: ${entityId}`);
1796
- }
1797
- else {
1798
- logger('Skipping liveQuery subscription in Node.js environment');
1799
- }
1800
- }
1801
- catch (error) {
1802
- logger(`Error setting up subscription: ${error}`);
1803
- setupState.subscriptionSetUp = false; // Reset on error so we can retry
1804
- }
1805
- };
1806
- // The entity's DB row can be written after the entity goes idle (e.g. a schema import writes its
1807
- // models' rows after the Model instances loaded from the schema file). Nothing changes the
1808
- // entity's snapshot then, so an idle entity's id is also retried on a timer until found or the
1809
- // actor stops. Only while idle and not writing (canRetryEntityId): a busy entity's own snapshots
1810
- // drive the lookup, and finding its row mid-write handed a Model its _dbId before its write (and
1811
- // its properties' rows) had finished.
1812
- let retryIndex = 0;
1813
- let retryTimer;
1814
- const scheduleRetry = () => {
1815
- if (retryTimer !== undefined || retryIndex >= ENTITY_ID_RETRY_DELAYS_MS.length)
1816
- return;
1817
- retryTimer = setTimeout(() => {
1818
- retryTimer = undefined;
1819
- // Busy: its next snapshot retries
1820
- if (instance.getService().getSnapshot().value !== 'idle')
1821
- return;
1822
- if (config.canRetryEntityId && !config.canRetryEntityId(instance))
1823
- return;
1824
- retryIndex++;
1825
- void trySetup();
1826
- }, ENTITY_ID_RETRY_DELAYS_MS[retryIndex]);
1827
- };
1828
- const trySetup = async () => {
1829
- if (setupState.subscriptionSetUp || isActorStopped(instance.getService()))
1830
- return;
1831
- if (config.isReady && !config.isReady(instance)) {
1832
- if (config.waitForReady) {
1833
- await config.waitForReady(instance);
1834
- }
1835
- else {
1836
- return; // Not ready yet, will retry on next snapshot
1837
- }
1838
- }
1839
- let entityId;
1840
- try {
1841
- entityId = await config.getEntityId(instance);
1842
- }
1843
- catch (error) {
1844
- logger(`Error getting entity ID: ${error}`);
1845
- }
1846
- if (!entityId) {
1847
- scheduleRetry();
1848
- return;
1849
- }
1850
- // Once we have entity ID, set up the liveQuery subscription (only once)
1851
- if (!setupState.subscriptionSetUp) {
1852
- await setupLiveQuery(entityId);
1853
- }
1854
- if (setupState.subscriptionSetUp) {
1855
- setupSubscription.unsubscribe();
1856
- if (retryTimer !== undefined)
1857
- clearTimeout(retryTimer);
1858
- }
1859
- };
1860
- // Set up liveQuery subscription as soon as we have entity ID
1861
- const setupSubscription = instance.getService().subscribe({
1862
- next: () => {
1863
- void trySetup();
1864
- },
1865
- complete: () => {
1866
- if (retryTimer !== undefined)
1867
- clearTimeout(retryTimer);
1868
- },
1869
- });
1870
- // Also check current state immediately in case entity ID is already available
1871
- void trySetup();
1872
- }
1873
- /** Backoff for retrying an entity's id when its DB row isn't there yet (~60s in total). */
1874
- const ENTITY_ID_RETRY_DELAYS_MS = [50, 100, 200, 400, 800, 1600, ...Array(28).fill(2000)];
1875
-
1876
- /**
1877
- * Returns model rows (id, name, schemaFileId, isEdited) for building Model instances.
1878
- * When schemaIdentifier is provided, only returns models for that schema (join models -> model_schemas -> schemas).
1879
- * schemaIdentifier can be either schema name or schema file ID.
1880
- */
1881
- const getModelsData = async (schemaIdentifier) => {
1882
- const appDb = BaseDb.getAppDb();
1883
- if (!appDb) {
1884
- return [];
1885
- }
1886
- if (schemaIdentifier !== undefined && schemaIdentifier !== '') {
1887
- const runQuery = () => appDb
1888
- .select({
1889
- id: models.id,
1890
- name: models.name,
1891
- schemaFileId: models.schemaFileId,
1892
- isEdited: models.isEdited,
1893
- })
1894
- .from(models)
1895
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
1896
- .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
1897
- .where(or(eq(schemas.name, schemaIdentifier), eq(schemas.schemaFileId, schemaIdentifier)));
1898
- let rows = await runQuery();
1899
- // Retry when we get 0 rows (read-after-write consistency; SQLocal/async may not see the write immediately).
1900
- for (let i = 0; i < 4 && rows.length === 0; i++) {
1901
- await new Promise((r) => setTimeout(r, 150));
1902
- rows = await runQuery();
1903
- }
1904
- return rows;
1905
- }
1906
- const rows = await appDb
1907
- .select({
1908
- id: models.id,
1909
- name: models.name,
1910
- schemaFileId: models.schemaFileId,
1911
- isEdited: models.isEdited,
1912
- })
1913
- .from(models);
1914
- return rows;
1915
- };
1916
-
1917
- /**
1918
- * Errors from draft saves that assignment starts in the background (schema/model renames and
1919
- * tracked-property edits). Nothing awaits those saves, so the error is kept for `lastSaveError`
1920
- * instead of only being logged. Keyed by the instance's service, which the instance and its
1921
- * proxy share.
1922
- */
1923
- const draftSaveErrors = new WeakMap();
1924
- function getDraftSaveError(service) {
1925
- return draftSaveErrors.get(service);
1926
- }
1927
- /** Run a background draft save, recording its failure (or clearing an earlier one on success). */
1928
- function trackDraftSave(service, save, label) {
1929
- save.then(() => {
1930
- draftSaveErrors.delete(service);
1931
- }, (error) => {
1932
- const recorded = error instanceof Error ? error : new Error(String(error));
1933
- draftSaveErrors.set(service, recorded);
1934
- console.warn(`[seed] ${label}: draft save failed (see lastSaveError):`, recorded.message);
1935
- });
1936
- }
1937
-
1938
- const logger = debug('seedSdk:model:Model');
1939
- const saveDraftLogger = debug('seedSdk:model:saveDraftToDb');
1940
- // Lazy import cache for ModelProperty to avoid circular dependency
1941
- // Eagerly start loading to minimize delay on first access
1942
- let ModelPropertyClass = null;
1943
- const modelPropertyImportPromise = import('./ModelProperty-DDD47KVm.js')
1944
- .then(module => {
1945
- ModelPropertyClass = module.ModelProperty;
1946
- return ModelPropertyClass;
1947
- })
1948
- .catch(() => {
1949
- // If import fails, ModelPropertyClass remains null
1950
- return null;
1951
- });
1952
- function getModelProperty() {
1953
- // Return cached class if available (synchronous access)
1954
- // Note: On first access before import completes, this will return null
1955
- // and the getter will return empty array, which matches the original behavior
1956
- return ModelPropertyClass;
1957
- }
1958
- // Mutable state per Model instance
1959
- const modelInstanceState = new ActorKeyedState();
1960
- // Settles once the properties passed to Model.create exist, or rejects when they can't be created.
1961
- // The model reaches idle before its write, so idle alone doesn't mean they exist yet. Keyed by the
1962
- // actor because callers hold the proxy, not the instance.
1963
- const pendingPropertyCreation = new WeakMap();
1964
- // Define tracked properties for the Proxy
1965
- // These properties will be read from/written to the actor context
1966
- const TRACKED_PROPERTIES = [
1967
- 'id', // schemaFileId (string) - public ID
1968
- 'modelName',
1969
- 'schemaName',
1970
- ];
1971
- class Model {
1972
- static trackPendingWrite(modelFileId, schemaId) {
1973
- this.pendingWrites.set(modelFileId, {
1974
- modelFileId,
1975
- schemaId,
1976
- status: 'pending',
1977
- timestamp: Date.now(),
1978
- });
1979
- }
1980
- static getPendingModelIds(schemaId) {
1981
- return Array.from(this.pendingWrites.entries())
1982
- .filter(([_, write]) => write.schemaId === schemaId && write.status !== 'error')
1983
- .map(([modelFileId]) => modelFileId);
1984
- }
1985
- // 'name' is available as an alias for 'modelName' via the proxy
1986
- constructor(modelName, schemaName, id, // schemaFileId (string) - public ID
1987
- initialContext, idFromSchema // true when id was provided via options (schema model), not generated
1988
- ) {
1989
- const serviceInput = {
1990
- modelName,
1991
- schemaName,
1992
- ...(id ? { id } : {}),
1993
- ...(initialContext?._pendingPropertyDefinitions ? { _pendingPropertyDefinitions: initialContext._pendingPropertyDefinitions } : {}),
1994
- _idFromSchema: !!idFromSchema,
1995
- };
1996
- this._service = createActor(modelMachine, {
1997
- input: serviceInput,
1998
- });
1999
- this._service.start();
2000
- // Initialize instance state in WeakMap
2001
- modelInstanceState.set(this, {
2002
- liveQuerySubscription: null,
2003
- });
2004
- // Set up liveQuery subscription for cross-instance property updates
2005
- this._setupLiveQuerySubscription();
2006
- // Subscribe to refresh properties after they're created (for Node.js where liveQuery isn't available)
2007
- let previousState;
2008
- this._service.subscribe((snapshot) => {
2009
- // Check if we just transitioned to idle from creatingProperties state
2010
- if (snapshot.value === 'idle' && previousState === 'creatingProperties') {
2011
- // Small delay to ensure properties are written to DB
2012
- setTimeout(() => {
2013
- this._refreshPropertiesFromDb().catch((error) => {
2014
- logger(`[Model constructor] Error refreshing properties: ${error}`);
2015
- });
2016
- }, 200);
2017
- }
2018
- previousState = snapshot.value;
2019
- });
2020
- // Note: Property getters/setters are now handled by the Proxy in create()
2021
- }
2022
- /**
2023
- * Resolves once the model is idle and any properties passed to Model.create exist.
2024
- * Rejects if it errors, times out, or its properties can't be created.
2025
- */
2026
- static async waitUntilReady(instance, timeout) {
2027
- const startedAt = Date.now();
2028
- await waitForEntityIdle(instance, { timeout });
2029
- const propertyCreation = pendingPropertyCreation.get(instance.getService());
2030
- if (!propertyCreation)
2031
- return instance;
2032
- const remaining = Math.max(0, timeout - (Date.now() - startedAt));
2033
- let timer;
2034
- try {
2035
- await Promise.race([
2036
- propertyCreation,
2037
- new Promise((_, reject) => {
2038
- timer = setTimeout(() => {
2039
- const modelName = instance.getService().getSnapshot().context.modelName;
2040
- reject(new Error(`Model "${modelName}" did not finish creating its properties within ${timeout}ms`));
2041
- }, remaining);
2042
- }),
2043
- ]);
2044
- }
2045
- finally {
2046
- clearTimeout(timer);
2047
- }
2048
- return instance;
2049
- }
2050
- /**
2051
- * Gives an idle, valid model the row someone else wrote for its id, the way its own write would
2052
- * (writeSuccess with the row's id, which goes on to create its properties from their rows).
2053
- * Returns false when there is no such row, so the model writes it.
2054
- */
2055
- static async adoptPersistedRow(instance) {
2056
- const snapshot = instance.getService().getSnapshot();
2057
- if (snapshot.value !== 'idle' || snapshot.context._validationErrors?.length || !snapshot.context.id) {
2058
- return false;
2059
- }
2060
- const db = BaseDb.getAppDb();
2061
- if (!db)
2062
- return false;
2063
- const [row] = await db
2064
- .select({ id: models.id })
2065
- .from(models)
2066
- .where(eq(models.schemaFileId, snapshot.context.id))
2067
- .limit(1);
2068
- if (!row || isActorStopped(instance.getService()))
2069
- return false;
2070
- logger(`Model "${snapshot.context.modelName}" adopts model row ${row.id} written by its schema import`);
2071
- instance.getService().send({ type: 'writeSuccess', output: { _dbId: row.id } });
2072
- return true;
2073
- }
2074
- /**
2075
- * True when a name-cached instance belongs to a different model than the requested modelFileId.
2076
- * A schema re-imported under the same name can give its models new ids while the previous
2077
- * instances are still cached by name; Model.create must not hand those back for the new id.
2078
- */
2079
- static isOtherModelId(cachedId, requestedId) {
2080
- return !!requestedId && !!cachedId && cachedId !== requestedId;
2081
- }
2082
- /**
2083
- * Find a unique model name by checking for duplicates (case-insensitive) in the cache
2084
- * If duplicates are found, appends an incrementing number to make it unique
2085
- *
2086
- * @param modelName - The desired model name
2087
- * @param schemaName - The schema name
2088
- * @param skipAllChecks - If true, skip all duplicate checks and return original name (used when creating schema models to preserve original names)
2089
- * @returns A unique model name
2090
- */
2091
- static findUniqueModelName(modelName, schemaName, skipAllChecks = false) {
2092
- // If skipAllChecks is true (schema models), preserve original name
2093
- if (skipAllChecks) {
2094
- return modelName;
2095
- }
2096
- const lowerModelName = modelName.toLowerCase();
2097
- // Check all cached models for this schema
2098
- const existingNames = new Set();
2099
- const existingNumbers = new Set();
2100
- // Check name-based cache
2101
- for (const nameKey of this.instanceCacheByName.keys()) {
2102
- const [cachedSchemaName, cachedModelName] = nameKey.split(':', 2);
2103
- if (cachedSchemaName === schemaName && cachedModelName) {
2104
- const lowerCachedName = cachedModelName.toLowerCase();
2105
- existingNames.add(lowerCachedName);
2106
- // If it matches the base name (case-insensitive), check if it has a number suffix
2107
- if (lowerCachedName === lowerModelName) {
2108
- existingNumbers.add(0); // Base name exists
2109
- }
2110
- else if (lowerCachedName.startsWith(lowerModelName + ' ')) {
2111
- // Check if it's the base name followed by a space and a number
2112
- const suffix = lowerCachedName.slice(lowerModelName.length + 1);
2113
- const number = parseInt(suffix, 10);
2114
- if (!isNaN(number) && suffix === number.toString()) {
2115
- existingNumbers.add(number);
2116
- }
2117
- }
2118
- }
2119
- }
2120
- // Check legacy cache
2121
- for (const [nameKey] of this.instanceCache.keys()) {
2122
- const [cachedSchemaName, cachedModelName] = nameKey.split(':', 2);
2123
- if (cachedSchemaName === schemaName && cachedModelName) {
2124
- const lowerCachedName = cachedModelName.toLowerCase();
2125
- existingNames.add(lowerCachedName);
2126
- // If it matches the base name (case-insensitive), check if it has a number suffix
2127
- if (lowerCachedName === lowerModelName) {
2128
- existingNumbers.add(0); // Base name exists
2129
- }
2130
- else if (lowerCachedName.startsWith(lowerModelName + ' ')) {
2131
- // Check if it's the base name followed by a space and a number
2132
- const suffix = lowerCachedName.slice(lowerModelName.length + 1);
2133
- const number = parseInt(suffix, 10);
2134
- if (!isNaN(number) && suffix === number.toString()) {
2135
- existingNumbers.add(number);
2136
- }
2137
- }
2138
- }
2139
- }
2140
- // If no duplicates found (no base name match and no numbered variants), return original name
2141
- if (existingNumbers.size === 0) {
2142
- return modelName;
2143
- }
2144
- // Find the next available number, starting from 1
2145
- let nextNumber = 1;
2146
- while (existingNumbers.has(nextNumber)) {
2147
- nextNumber++;
2148
- }
2149
- // Return the name with the number appended
2150
- return `${modelName} ${nextNumber}`;
2151
- }
2152
- static create(modelName, schemaNameOrSchema, // Schema type - using any to avoid circular dependency
2153
- options) {
2154
- if (!modelName) {
2155
- throw new Error('Model name is required');
2156
- }
2157
- const waitForReady = options?.waitForReady !== false;
2158
- const readyTimeout = options?.readyTimeout ?? DEFAULT_READY_TIMEOUT_MS;
2159
- const holds = options?.hold === false ? 0 : 1;
2160
- // Extract schema name and instance
2161
- let schemaName;
2162
- let schemaInstance; // Using any to avoid circular dependency with Schema
2163
- if (typeof schemaNameOrSchema === 'string') {
2164
- schemaName = schemaNameOrSchema;
2165
- }
2166
- else {
2167
- schemaInstance = schemaNameOrSchema;
2168
- schemaName = schemaInstance.schemaName;
2169
- }
2170
- if (!schemaName) {
2171
- throw new Error('Schema name is required');
2172
- }
2173
- // Support both modelFileId (documented) and id (backward compatibility)
2174
- const id = options?.modelFileId || options?.id; // schemaFileId (string) - public ID
2175
- const registerWithSchema = options?.registerWithSchema !== false && schemaInstance !== undefined;
2176
- // Step 1: Check ID-based cache first (if ID provided)
2177
- const cachedById = id ? this.instanceCacheById.get(id) : undefined;
2178
- if (cachedById) {
2179
- cachedById.refCount += holds;
2180
- const { instance } = cachedById;
2181
- logger(`Model.create: Found instance in ID cache for "${modelName}" (ID: ${id})`);
2182
- if (!waitForReady)
2183
- return instance;
2184
- return this.waitUntilReady(instance, readyTimeout);
2185
- }
2186
- // Step 2: Check for duplicate names and generate unique name if needed (before checking cache)
2187
- // This allows us to create new models with renamed versions when duplicates exist
2188
- // Skip all duplicate checks if modelFileId is provided (schema models should preserve original names)
2189
- const skipAllChecks = !!id; // If id (modelFileId) is provided, this is a schema model - skip schema check to preserve name
2190
- const uniqueModelName = this.findUniqueModelName(modelName, schemaName, skipAllChecks);
2191
- // Step 3: Check name-based index with ORIGINAL name first (before using unique name)
2192
- // Only return existing instance if the unique name matches the original (no rename needed)
2193
- // If a rename is needed, we'll create a new model with the unique name
2194
- let resolvedId = id;
2195
- const originalNameKey = `${schemaName}:${modelName}`;
2196
- // Step 3: When we have no id (runtime create), do not return from name cache. Each create('My Model')
2197
- // must get a new instance with an incremented name (My Model, My Model 1, ...). If the only cached
2198
- // instance with this name is the schema model (_idFromSchema), ensure we fall through to create new.
2199
- if (!resolvedId) {
2200
- const cachedId = this.instanceCacheByName.get(originalNameKey);
2201
- if (cachedId && this.instanceCacheById.has(cachedId)) {
2202
- const { instance } = this.instanceCacheById.get(cachedId);
2203
- const ctx = instance._getSnapshotContext();
2204
- if (ctx._idFromSchema) {
2205
- resolvedId = undefined;
2206
- }
2207
- // Never return by name when !id (runtime create); fall through to create new with unique name
2208
- }
2209
- }
2210
- // Step 4: Check legacy cache with original name. Skip when !id (runtime create) so duplicate
2211
- // create('My Model') calls get new instances with incremented names.
2212
- if (id && uniqueModelName === modelName) {
2213
- const legacyKey = `${schemaName}:${modelName}`;
2214
- const legacyEntry = this.instanceCache.get(legacyKey);
2215
- if (legacyEntry) {
2216
- const { instance } = legacyEntry;
2217
- const ctx = instance._getSnapshotContext();
2218
- if (!ctx._idFromSchema && !this.isOtherModelId(ctx.id, id)) {
2219
- legacyEntry.refCount += holds;
2220
- logger(`Model.create: Found instance in legacy cache for "${modelName}"`);
2221
- const existingId = instance._getSnapshotContext().id;
2222
- if (existingId) {
2223
- this.instanceCacheById.set(existingId, legacyEntry);
2224
- this.instanceCacheByName.set(legacyKey, existingId);
2225
- }
2226
- if (!waitForReady)
2227
- return instance;
2228
- return this.waitUntilReady(instance, readyTimeout);
2229
- }
2230
- }
2231
- }
2232
- // Step 5: When a rename is needed (uniqueModelName !== modelName), do NOT return a cached
2233
- // instance. The only cached instance with the original name at this point is the
2234
- // schema-defined model (created with modelFileId); returning it would give the user the
2235
- // schema model instead of a new runtime model with the unique name (e.g. "New model 1").
2236
- if (uniqueModelName !== modelName) {
2237
- logger(`Model.create: Found duplicate name "${modelName}" in schema "${schemaName}", using unique name "${uniqueModelName}"`);
2238
- }
2239
- // Step 6a: Generate ID now when needed so we can use a placeholder cache key for runtime creates
2240
- if (!resolvedId) {
2241
- resolvedId = generateId();
2242
- logger(`Model.create: Generated new id (schemaFileId) "${resolvedId}" for model "${modelName}"`);
2243
- }
2244
- // Create name-based cache key. When no id (runtime create), use a placeholder so we don't
2245
- // overwrite the schema model's entry (e.g. "New model"); loadOrCreateModel will replace it with the final name.
2246
- const nameKey = id
2247
- ? `${schemaName}:${uniqueModelName}`
2248
- : `${schemaName}:__pending__${resolvedId}`;
2249
- // Step 6: Check legacy cache with unique name (backward compatibility during migration)
2250
- // This is a fallback in case an instance was cached with a unique name
2251
- if (this.instanceCache.has(nameKey) &&
2252
- !this.isOtherModelId(this.instanceCache.get(nameKey).instance._getSnapshotContext().id, id)) {
2253
- const legacyEntry = this.instanceCache.get(nameKey);
2254
- legacyEntry.refCount += holds;
2255
- const { instance } = legacyEntry;
2256
- logger(`Model.create: Found instance in legacy cache for "${modelName}"`);
2257
- // Migrate to new cache structure
2258
- const context = instance._getSnapshotContext();
2259
- const existingId = context.id; // id is now the schemaFileId (string)
2260
- if (existingId) {
2261
- this.instanceCacheById.set(existingId, legacyEntry);
2262
- this.instanceCacheByName.set(nameKey, existingId);
2263
- }
2264
- if (!waitForReady)
2265
- return instance;
2266
- return this.waitUntilReady(instance, readyTimeout);
2267
- }
2268
- // Step 7: Create new instance with id in initial context
2269
- // This ensures loadOrCreateModel sees the ID immediately
2270
- // Pass _pendingPropertyDefinitions in initial context to avoid race condition with loadOrCreateModel
2271
- // This is the proper XState way - include it in the initial input so it's available from the start
2272
- const initialContext = options?.properties
2273
- ? { _pendingPropertyDefinitions: JSON.parse(JSON.stringify(options.properties)) }
2274
- : undefined;
2275
- // Use the unique model name for the instance (idFromSchema: true when id was provided, so schema model is not renamed)
2276
- const newInstance = new this(uniqueModelName, schemaName, resolvedId, initialContext, !!id);
2277
- if (options?.properties) {
2278
- logger(`[Model.create] Created instance with _pendingPropertyDefinitions in initial context (${Object.keys(options.properties).length} properties) for model "${uniqueModelName}"`);
2279
- }
2280
- // Called by the write trigger below when the properties will never be created
2281
- let failPropertyCreation = () => { };
2282
- if (options?.properties && Object.keys(options.properties).length > 0) {
2283
- const service = newInstance.getService();
2284
- const propertyCreation = new Promise((resolve, reject) => {
2285
- const subscription = service.subscribe({
2286
- next: (snapshot) => {
2287
- if (snapshot.value === 'idle' && !snapshot.context._pendingPropertyDefinitions) {
2288
- subscription.unsubscribe();
2289
- resolve();
2290
- }
2291
- else if (snapshot.value === 'error') {
2292
- failPropertyCreation('the model is in an error state');
2293
- }
2294
- },
2295
- complete: () => failPropertyCreation('the model was unloaded'),
2296
- });
2297
- failPropertyCreation = (reason) => {
2298
- subscription.unsubscribe();
2299
- reject(new Error(`Model "${service.getSnapshot().context.modelName}" could not create its properties: ${reason}`));
2300
- };
2301
- });
2302
- // Only waitUntilReady reports this; without waitForReady nobody awaits it
2303
- propertyCreation.catch(() => { });
2304
- pendingPropertyCreation.set(service, propertyCreation);
2305
- }
2306
- // Step 7: Set initial model data if provided
2307
- // Note: description is not supported - JSON files can have it but we ignore it at runtime
2308
- if (options) {
2309
- const updates = {};
2310
- if (Object.keys(updates).length > 0) {
2311
- newInstance._service.send({
2312
- type: 'updateContext',
2313
- ...updates,
2314
- });
2315
- // Initialize original values
2316
- // Note: properties are not stored in context, they're computed from ModelProperty instances
2317
- newInstance._service.send({
2318
- type: 'initializeOriginalValues',
2319
- originalValues: {},
2320
- isEdited: false,
2321
- });
2322
- }
2323
- }
2324
- // Wrap instance in Proxy for reactive property access. `name` is an alias for `modelName`.
2325
- const sendUpdate = (target, prop, value) => {
2326
- if (prop === 'modelName') {
2327
- // Model name change - need to update Schema and database
2328
- const context = target._getSnapshotContext();
2329
- const oldName = context.modelName;
2330
- const newName = value;
2331
- if (oldName === newName) {
2332
- logger(`Model name unchanged: "${oldName}"`);
2333
- return;
2334
- }
2335
- logger(`Updating model name from "${oldName}" to "${newName}"`);
2336
- // Update Model instance cache using new ID-based structure
2337
- const modelFileId = context.id; // id is now the schemaFileId (string)
2338
- if (modelFileId) {
2339
- Model.updateNameIndex(oldName, newName, context.schemaName, modelFileId);
2340
- }
2341
- else {
2342
- // Fallback to legacy cache if no ID (shouldn't happen, but safety)
2343
- const oldCacheKey = `${context.schemaName}:${oldName}`;
2344
- const newCacheKey = `${context.schemaName}:${newName}`;
2345
- if (Model.instanceCache.has(oldCacheKey)) {
2346
- const entry = Model.instanceCache.get(oldCacheKey);
2347
- Model.instanceCache.delete(oldCacheKey);
2348
- Model.instanceCache.set(newCacheKey, entry);
2349
- }
2350
- }
2351
- // Update Model context
2352
- target._service.send({
2353
- type: 'updateContext',
2354
- modelName: newName,
2355
- });
2356
- // Mark model as draft
2357
- target._service.send({
2358
- type: 'markAsDraft',
2359
- propertyKey: 'modelName',
2360
- });
2361
- // Save draft to database immediately so changes persist
2362
- trackDraftSave(target._service, target._saveDraftToDb(oldName, newName), `Model "${newName}"`);
2363
- // NOTE: Schema is not notified of model name changes during edits.
2364
- // Schema will read from Model instances when persisting/saving.
2365
- }
2366
- else {
2367
- // Standard property update
2368
- target._service.send({
2369
- type: 'updateContext',
2370
- [prop]: value,
2371
- });
2372
- }
2373
- };
2374
- const proxiedInstance = createReactiveProxy({
2375
- instance: newInstance,
2376
- service: newInstance._service,
2377
- trackedProperties: TRACKED_PROPERTIES,
2378
- getContext: (target) => target._getSnapshotContext(),
2379
- sendUpdate,
2380
- resolve: (target, prop) => {
2381
- if (prop === 'name') {
2382
- return {
2383
- get: () => target._getSnapshotContext().modelName,
2384
- set: (value) => sendUpdate(target, 'modelName', value),
2385
- };
2386
- }
2387
- return undefined;
2388
- },
2389
- virtualKeys: () => ['name'],
2390
- onSet: (_target, prop) => {
2391
- if (prop === 'properties') {
2392
- // Properties are read-only computed values from ModelProperty instances
2393
- throw new Error('Cannot set model.properties directly. Properties are computed from ModelProperty instances.');
2394
- }
2395
- if (prop === 'description') {
2396
- // Description is not supported - JSON files can have it but we ignore it at runtime
2397
- throw new Error('Cannot set model.description. Description is not supported in runtime Model instances.');
2398
- }
2399
- },
2400
- });
2401
- // Step 8: Store in new ID-based cache
2402
- // resolvedId is guaranteed to be defined at this point (generated if not provided).
2403
- // The ID and legacy caches share one entry: one hold count for the instance.
2404
- const entry = { instance: proxiedInstance, refCount: holds };
2405
- this.instanceCacheById.set(resolvedId, entry);
2406
- // Step 9: Store in name-based index (placeholder key when !id; loadOrCreateModel will replace with final name)
2407
- this.instanceCacheByName.set(nameKey, resolvedId);
2408
- // Step 9b: When created with id (schema model) and unique name, also register original name so create(modelName) returns same instance
2409
- if (id && uniqueModelName !== modelName) {
2410
- this.instanceCacheByName.set(originalNameKey, resolvedId);
2411
- }
2412
- // Step 10: Also store in legacy cache (for backward compatibility during migration)
2413
- this.instanceCache.set(nameKey, entry);
2414
- logger(`Model.create: Created new instance for "${uniqueModelName}" (ID: ${id})`);
2415
- // Model instance is now cached and accessible via Model.getById() and Model.getByName()
2416
- // No wrapper or store registration needed
2417
- // Step 11: Register with schema if requested, OR trigger write if properties are provided
2418
- // If properties are provided, we need to write the model to get modelId for property creation
2419
- // If schema provided, trigger write process instead of registration
2420
- // When schemaName is passed as string (e.g. useCreateModel), we must trigger write so runtime-created
2421
- // models get persisted to DB and show up in useModels (which queries the database).
2422
- const hasProperties = !!options?.properties && Object.keys(options.properties).length > 0;
2423
- const shouldTriggerWrite = (registerWithSchema && schemaInstance) ||
2424
- hasProperties ||
2425
- typeof schemaNameOrSchema === 'string';
2426
- // Every DB load (createById, getByNameAsync, ...) also passes a string schema and no properties.
2427
- // Such a model adopts its existing row: writing it again rewrote each property row and cleared is_edited.
2428
- const adoptExistingRow = !!options?.persistedBy || (typeof schemaNameOrSchema === 'string' && !hasProperties);
2429
- if (shouldTriggerWrite) {
2430
- queueMicrotask(async () => {
2431
- try {
2432
- // Wait for model to reach idle state (validation complete)
2433
- await new Promise((resolve) => {
2434
- // Cleared once the model settles: a pending timer kept Node scripts alive for 10 s.
2435
- const timer = setTimeout(() => {
2436
- subscription.unsubscribe();
2437
- resolve();
2438
- }, 10000);
2439
- const subscription = proxiedInstance._service.subscribe((snapshot) => {
2440
- // Wait for idle state (validation complete) or error state
2441
- if (snapshot.value === 'idle' || snapshot.value === 'error') {
2442
- clearTimeout(timer);
2443
- subscription.unsubscribe();
2444
- resolve();
2445
- }
2446
- });
2447
- });
2448
- // The caller writes this model's rows; wait for that so two writers don't write them at once
2449
- if (options?.persistedBy) {
2450
- await options.persistedBy.catch(() => { });
2451
- }
2452
- // Unloaded or evicted (e.g. Schema.destroy) while it loaded: a stopped model never writes
2453
- if (isActorStopped(proxiedInstance._service)) {
2454
- failPropertyCreation('the model was unloaded');
2455
- return;
2456
- }
2457
- if (adoptExistingRow && (await Model.adoptPersistedRow(proxiedInstance))) {
2458
- return;
2459
- }
2460
- // Only write if validation passed (model is in idle state, not error)
2461
- const finalSnapshot = proxiedInstance._service.getSnapshot();
2462
- if (finalSnapshot.value === 'idle' && (!finalSnapshot.context._validationErrors || finalSnapshot.context._validationErrors.length === 0)) {
2463
- logger(`Model validation passed, triggering write process`);
2464
- // Get schema ID from schema instance or look it up by schema name
2465
- let schemaId;
2466
- if (schemaInstance) {
2467
- const schemaContext = schemaInstance._getSnapshotContext();
2468
- // First, try to use _dbId directly from schema context (most reliable)
2469
- // But verify it exists in database before using it
2470
- if (schemaContext._dbId && typeof schemaContext._dbId === 'number' && schemaContext._dbId > 0) {
2471
- try {
2472
- const db = BaseDb.getAppDb();
2473
- if (db) {
2474
- const verifySchema = await db
2475
- .select({ id: schemas.id })
2476
- .from(schemas)
2477
- .where(eq(schemas.id, schemaContext._dbId))
2478
- .limit(1);
2479
- if (verifySchema.length > 0) {
2480
- schemaId = schemaContext._dbId;
2481
- logger(`Using schema _dbId directly from Schema context: ${schemaId} (verified in database)`);
2482
- }
2483
- else {
2484
- logger(`WARNING: Schema _dbId ${schemaContext._dbId} from context does not exist in database. Falling back to schemaFileId lookup.`);
2485
- }
2486
- }
2487
- }
2488
- catch (error) {
2489
- logger(`WARNING: Could not verify schema _dbId: ${error}. Falling back to schemaFileId lookup.`);
2490
- }
2491
- }
2492
- // Fall back to database lookup by schemaFileId if _dbId wasn't available or invalid
2493
- if (!schemaId) {
2494
- // Fall back to database lookup by schemaFileId
2495
- const schemaFileId = schemaContext.id; // id is the schemaFileId (string) in SchemaMachineContext
2496
- if (schemaFileId) {
2497
- try {
2498
- const db = BaseDb.getAppDb();
2499
- if (db) {
2500
- const schemaRecords = await db
2501
- .select()
2502
- .from(schemas)
2503
- .where(eq(schemas.schemaFileId, schemaFileId))
2504
- .limit(1);
2505
- if (schemaRecords.length > 0) {
2506
- schemaId = schemaRecords[0].id;
2507
- logger(`Found schemaId ${schemaId} by schemaFileId lookup: ${schemaFileId}`);
2508
- }
2509
- else {
2510
- logger(`WARNING: Schema not found in database by schemaFileId: ${schemaFileId}. Schema may not be fully loaded yet.`);
2511
- }
2512
- }
2513
- }
2514
- catch (error) {
2515
- logger(`Error looking up schemaId: ${error}`);
2516
- }
2517
- }
2518
- else {
2519
- logger(`WARNING: Schema instance has no schemaFileId (id) in context`);
2520
- }
2521
- }
2522
- }
2523
- else if (schemaName) {
2524
- // If no schema instance but we have schema name, look it up by name
2525
- try {
2526
- const db = BaseDb.getAppDb();
2527
- if (db) {
2528
- const schemaRecords = await db
2529
- .select()
2530
- .from(schemas)
2531
- .where(eq(schemas.name, schemaName))
2532
- .limit(1);
2533
- if (schemaRecords.length > 0) {
2534
- schemaId = schemaRecords[0].id;
2535
- logger(`Found schemaId ${schemaId} by schema name lookup: ${schemaName}`);
2536
- }
2537
- else {
2538
- logger(`WARNING: Schema not found in database by name: ${schemaName}`);
2539
- }
2540
- }
2541
- }
2542
- catch (error) {
2543
- logger(`Error looking up schemaId by name: ${error}`);
2544
- }
2545
- }
2546
- // Validate schemaId before proceeding
2547
- if (!schemaId || !Number.isInteger(schemaId) || schemaId <= 0) {
2548
- logger(`ERROR: Invalid schemaId (${schemaId}) for model "${finalSnapshot.context.modelName}". Cannot trigger write process.`);
2549
- logger(`Schema context:`, {
2550
- schemaFileId: schemaInstance ? schemaInstance._getSnapshotContext().id : 'N/A',
2551
- _dbId: schemaInstance ? schemaInstance._getSnapshotContext()._dbId : 'N/A',
2552
- schemaName: schemaName || 'N/A',
2553
- });
2554
- failPropertyCreation(`its schema "${schemaName}" was not found in the database`);
2555
- }
2556
- else if (finalSnapshot.context.id) {
2557
- // Verify schema exists in database before proceeding (additional safety check)
2558
- try {
2559
- const db = BaseDb.getAppDb();
2560
- if (db) {
2561
- const schemaCheck = await db
2562
- .select({ id: schemas.id })
2563
- .from(schemas)
2564
- .where(eq(schemas.id, schemaId))
2565
- .limit(1);
2566
- if (schemaCheck.length === 0) {
2567
- const errorMsg = `Schema with id ${schemaId} does not exist in database. Cannot create model "${finalSnapshot.context.modelName}".`;
2568
- logger(`ERROR: ${errorMsg}`);
2569
- throw new Error(errorMsg);
2570
- }
2571
- logger(`Verified schema ${schemaId} exists in database before creating model "${finalSnapshot.context.modelName}"`);
2572
- }
2573
- else {
2574
- logger(`WARNING: Database not available for schema verification. Proceeding anyway.`);
2575
- }
2576
- }
2577
- catch (error) {
2578
- // If it's our validation error, re-throw it
2579
- if (error instanceof Error && error.message.includes('does not exist in database')) {
2580
- throw error;
2581
- }
2582
- logger(`WARNING: Could not verify schema exists in database: ${error}. Proceeding anyway.`);
2583
- }
2584
- const pendingModelFileId = finalSnapshot.context.id;
2585
- const pendingSchemaId = schemaId;
2586
- // Wait for writeProcess to be spawned (it's spawned in idle state entry action)
2587
- // Retry a few times if writeProcess isn't available yet
2588
- let retries = 0;
2589
- const maxRetries = 10;
2590
- const checkAndSend = async () => {
2591
- // Unloaded or evicted (e.g. Schema.destroy) before the write started: nothing to write to
2592
- if (isActorStopped(proxiedInstance._service)) {
2593
- failPropertyCreation('the model was unloaded');
2594
- return;
2595
- }
2596
- const currentSnapshot = proxiedInstance._service.getSnapshot();
2597
- if (currentSnapshot.context.writeProcess) {
2598
- // Track pending write (only once it really starts: a stopped model leaves none behind)
2599
- Model.trackPendingWrite(pendingModelFileId, pendingSchemaId); // id is now the schemaFileId (string)
2600
- logger(`Triggering write process for model "${finalSnapshot.context.modelName}" (schemaId: ${schemaId})`);
2601
- // Use pending property definitions if available, otherwise convert from ModelProperty instances
2602
- let propertiesObject = {};
2603
- if (currentSnapshot.context._pendingPropertyDefinitions) {
2604
- // Use the original property definitions for the write
2605
- // Convert dataType to type for validation compatibility
2606
- propertiesObject = JSON.parse(JSON.stringify(currentSnapshot.context._pendingPropertyDefinitions));
2607
- for (const [propName, propData] of Object.entries(propertiesObject)) {
2608
- if (propData.dataType && !propData.type) {
2609
- propertiesObject[propName] = {
2610
- ...propData,
2611
- type: propData.dataType,
2612
- };
2613
- }
2614
- }
2615
- }
2616
- else {
2617
- // Fallback: convert ModelProperty instances to object format
2618
- const properties = proxiedInstance.properties || [];
2619
- propertiesObject = modelPropertiesToObject(properties);
2620
- }
2621
- // Set up subscription to writeProcess to detect write success BEFORE sending request
2622
- const writeProcess = currentSnapshot.context.writeProcess;
2623
- // Check current state - if already in success, handle immediately
2624
- const currentWriteState = writeProcess.getSnapshot();
2625
- if (currentWriteState.value === 'success') {
2626
- proxiedInstance._service.send({
2627
- type: 'writeSuccess',
2628
- output: currentWriteState.context.entityData,
2629
- });
2630
- }
2631
- else {
2632
- // Set up subscription to catch future state changes
2633
- const writeSubscription = writeProcess.subscribe((writeSnapshot) => {
2634
- if (writeSnapshot.value === 'success') {
2635
- writeSubscription.unsubscribe();
2636
- const currentContext = proxiedInstance._service.getSnapshot().context;
2637
- logger(`[writeProcess subscription] Write succeeded, sending writeSuccess event. Has _pendingPropertyDefinitions: ${!!(currentContext._pendingPropertyDefinitions && Object.keys(currentContext._pendingPropertyDefinitions).length > 0)}`);
2638
- // Send writeSuccess event to model machine
2639
- proxiedInstance._service.send({
2640
- type: 'writeSuccess',
2641
- output: writeSnapshot.context.entityData,
2642
- });
2643
- }
2644
- else if (writeSnapshot.value === 'error') {
2645
- writeSubscription.unsubscribe();
2646
- const errorContext = writeSnapshot.context;
2647
- logger(`Write process failed for model "${finalSnapshot.context.modelName}": ${errorContext.error?.message || 'Unknown error'}`);
2648
- logger(`Write process error details:`, errorContext.error);
2649
- logger(`Validation errors:`, errorContext.validationErrors);
2650
- failPropertyCreation(`writing the model failed: ${errorContext.error?.message || 'Unknown error'}`);
2651
- }
2652
- });
2653
- }
2654
- proxiedInstance._service.send({
2655
- type: 'requestWrite',
2656
- data: {
2657
- modelFileId: finalSnapshot.context.id, // id is now the schemaFileId (string)
2658
- modelName: finalSnapshot.context.modelName,
2659
- schemaName: finalSnapshot.context.schemaName,
2660
- schemaId,
2661
- properties: propertiesObject,
2662
- },
2663
- });
2664
- }
2665
- else if (retries < maxRetries) {
2666
- retries++;
2667
- setTimeout(checkAndSend, 50); // Retry after 50ms
2668
- }
2669
- else {
2670
- logger(`ERROR: writeProcess not available after ${maxRetries} retries for model "${finalSnapshot.context.modelName}"`);
2671
- failPropertyCreation('its write process never started');
2672
- }
2673
- };
2674
- // Start checking immediately, but also allow time for entry action to complete
2675
- setTimeout(checkAndSend, 0);
2676
- }
2677
- else {
2678
- logger(`Cannot trigger write process: missing schemaId (${schemaId}) or id (${finalSnapshot.context.id})`);
2679
- failPropertyCreation('it has no id');
2680
- }
2681
- }
2682
- else {
2683
- logger(`Model validation failed or model in error state, skipping write process`);
2684
- failPropertyCreation('the model failed validation');
2685
- }
2686
- }
2687
- catch (error) {
2688
- logger(`Failed to trigger write process: ${error instanceof Error ? error.message : String(error)}`);
2689
- failPropertyCreation(error instanceof Error ? error.message : String(error));
2690
- }
2691
- });
2692
- }
2693
- if (!waitForReady)
2694
- return proxiedInstance;
2695
- return this.waitUntilReady(proxiedInstance, readyTimeout);
2696
- }
2697
- /**
2698
- * Get Model instance by modelFileId (O(1) lookup). Takes a hold on the instance; release it with
2699
- * `unload()`.
2700
- */
2701
- static getById(modelFileId) {
2702
- const entry = this.instanceCacheById.get(modelFileId);
2703
- if (!entry)
2704
- return undefined;
2705
- entry.refCount += 1;
2706
- return entry.instance;
2707
- }
2708
- /**
2709
- * @internal For a Schema loading its models: true when this load should take the schema's hold
2710
- * on the model (pass no `hold` option), false when the schema already holds it (pass
2711
- * `hold: false`). After a true, call `dropSchemaHold` if the load returns no model.
2712
- */
2713
- static takeSchemaHold(modelFileId) {
2714
- if (!modelFileId || this.schemaHeldModelIds.has(modelFileId))
2715
- return false;
2716
- this.schemaHeldModelIds.add(modelFileId);
2717
- return true;
2718
- }
2719
- /** @internal The load `takeSchemaHold` approved found no model: there is no hold to release. */
2720
- static dropSchemaHold(modelFileId) {
2721
- this.schemaHeldModelIds.delete(modelFileId);
2722
- }
2723
- /** @internal A Schema's last unload: releases its hold on each of its models. */
2724
- static releaseSchemaHolds(schemaName) {
2725
- for (const modelFileId of [...this.schemaHeldModelIds]) {
2726
- const model = this.peek(modelFileId);
2727
- if (!model) {
2728
- this.schemaHeldModelIds.delete(modelFileId);
2729
- continue;
2730
- }
2731
- if (model._getSnapshotContext().schemaName !== schemaName)
2732
- continue;
2733
- this.schemaHeldModelIds.delete(modelFileId);
2734
- model.unload();
2735
- }
2736
- }
2737
- /**
2738
- * The cached Model with this modelFileId, without taking a hold. For SDK code that reads the
2739
- * model and doesn't hand it to a caller; callers that keep it use `getById`, `find` or `create`.
2740
- */
2741
- static peek(modelFileId) {
2742
- return this.instanceCacheById.get(modelFileId)?.instance;
2743
- }
2744
- /**
2745
- * Get Model instance by name (O(1) lookup via name→ID mapping). Takes a hold on the instance;
2746
- * release it with `unload()`.
2747
- *
2748
- * @param modelName - The name of the model
2749
- * @param schemaName - The schema name (optional, will query DB if not provided)
2750
- * @returns The Model instance if found, undefined otherwise
2751
- */
2752
- static getByName(modelName, schemaName) {
2753
- const model = this.peekByName(modelName, schemaName);
2754
- return model?.id ? this.getById(model.id) : undefined;
2755
- }
2756
- /**
2757
- * `getByName` without taking a hold. Without `schemaName` it searches every cached schema and
2758
- * throws AmbiguousModelError when models from several schemas share the name; callers that know
2759
- * the schema or model id should use Model.resolve() instead.
2760
- */
2761
- static peekByName(modelName, schemaName) {
2762
- if (schemaName) {
2763
- const id = this.instanceCacheByName.get(`${schemaName}:${modelName}`);
2764
- return id ? this.peek(id) : undefined;
2765
- }
2766
- return this._getUniqueCachedModel(modelName, (cachedModelName) => cachedModelName === modelName);
2767
- }
2768
- /**
2769
- * The one cached Model whose name matches (no hold taken), or AmbiguousModelError if models from
2770
- * several schemas do.
2771
- */
2772
- static _getUniqueCachedModel(label, matches) {
2773
- const matchesById = new Map(); // modelFileId -> schemaName
2774
- for (const [nameKey, id] of this.instanceCacheByName.entries()) {
2775
- const separator = nameKey.lastIndexOf(':');
2776
- const cachedModelName = nameKey.slice(separator + 1);
2777
- if (id && cachedModelName && matches(cachedModelName) && this.instanceCacheById.has(id)) {
2778
- matchesById.set(id, nameKey.slice(0, separator));
2779
- }
2780
- }
2781
- if (matchesById.size > 1) {
2782
- throw new AmbiguousModelError(label, [...matchesById.values()]);
2783
- }
2784
- const [matchId] = matchesById.keys();
2785
- return matchId ? this.peek(matchId) : undefined;
2786
- }
2787
- /**
2788
- * Resolve a cached Model from the most specific scope available: modelFileId (Model.id), then
2789
- * schemaName + modelName, then modelName alone. Takes no hold.
2790
- */
2791
- static resolve(modelName, scope = {}) {
2792
- if (scope.modelFileId) {
2793
- const byId = this.peek(scope.modelFileId);
2794
- if (byId)
2795
- return byId;
2796
- }
2797
- if (!modelName)
2798
- return undefined;
2799
- if (scope.schemaName) {
2800
- const bySchema = this.peekByName(modelName, scope.schemaName);
2801
- if (bySchema)
2802
- return bySchema;
2803
- }
2804
- return this.peekByName(modelName);
2805
- }
2806
- /**
2807
- * Find Model by modelType (an EAS model name, as seeds.type stores it). Matched as ADR 0008
2808
- * matches EAS names: `new_model` finds "New model", and `htmlpage` (an older SDK's name) finds
2809
- * "HTMLPage", unless HTMLPage's nameMatch (ADR 0009) is `exact`. Only the tightest matching tier
2810
- * counts; matches in several schemas are ambiguous. Takes no hold.
2811
- */
2812
- static findByModelType(modelType) {
2813
- if (!modelType)
2814
- return undefined;
2815
- const cachedNames = [...this.instanceCacheByName.keys()].map((key) => key.slice(key.lastIndexOf(':') + 1));
2816
- // Each name at its nameMatch level (ADR 0009), as last loaded.
2817
- const levels = currentNameMatchLevels();
2818
- const best = localNamesInBestMatchTier(modelType, cachedNames, { level: (name) => levels.model(name) });
2819
- return this._getUniqueCachedModel(modelType, (cachedModelName) => best.has(cachedModelName));
2820
- }
2821
- /**
2822
- * Get all Model instances for a schema from cache only (synchronous). Takes no holds.
2823
- * Includes models created at runtime via Model.create() that may not yet be in schema context.
2824
- */
2825
- static getCachedInstancesForSchema(schemaName) {
2826
- const instances = [];
2827
- const seen = new Set();
2828
- for (const [nameKey, id] of this.instanceCacheByName.entries()) {
2829
- const [cachedSchemaName] = nameKey.split(':');
2830
- if (cachedSchemaName === schemaName && id && !seen.has(id)) {
2831
- const instance = this.peek(id);
2832
- if (instance) {
2833
- instances.push(instance);
2834
- seen.add(id);
2835
- }
2836
- }
2837
- }
2838
- for (const [nameKey, entry] of this.instanceCache.entries()) {
2839
- const [cachedSchemaName] = nameKey.split(':');
2840
- if (cachedSchemaName === schemaName) {
2841
- const context = entry.instance._getSnapshotContext();
2842
- const id = context.id;
2843
- if (id && !seen.has(id)) {
2844
- instances.push(entry.instance);
2845
- seen.add(id);
2846
- }
2847
- }
2848
- }
2849
- return instances;
2850
- }
2851
- /**
2852
- * Async Model.resolve(): exact cache hit by modelFileId or schemaName+modelName, else the DB row for
2853
- * that scope (instantiating the Model), and only then the name-only fallback. Takes no hold.
2854
- */
2855
- static async resolveAsync(modelName, scope = {}) {
2856
- if (scope.modelFileId) {
2857
- const byId = this.peek(scope.modelFileId);
2858
- if (byId)
2859
- return byId;
2860
- }
2861
- if (modelName && scope.schemaName) {
2862
- const bySchema = this.peekByName(modelName, scope.schemaName);
2863
- if (bySchema)
2864
- return bySchema;
2865
- }
2866
- if (scope.modelFileId || scope.schemaName) {
2867
- const db = BaseDb.getAppDb();
2868
- if (db) {
2869
- const evictionEpoch = currentEvictionEpoch();
2870
- try {
2871
- const rows = await db
2872
- .select({
2873
- modelFileId: models.schemaFileId,
2874
- modelName: models.name,
2875
- schemaName: schemas.name,
2876
- })
2877
- .from(models)
2878
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
2879
- .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
2880
- .where(scope.modelFileId
2881
- ? eq(models.schemaFileId, scope.modelFileId)
2882
- : and(eq(models.name, modelName ?? ''), eq(schemas.name, scope.schemaName)))
2883
- .limit(1);
2884
- const record = rows[0];
2885
- if (record?.modelName && record.schemaName && record.modelFileId) {
2886
- // Its schema was evicted, or it was destroyed, while we read the row: don't bring it back.
2887
- if (schemaEvictedSince(record.schemaName, evictionEpoch))
2888
- return undefined;
2889
- if (modelDestroyedSince(record.modelFileId, evictionEpoch))
2890
- return undefined;
2891
- return this.create(record.modelName, record.schemaName, { id: record.modelFileId, hold: false });
2892
- }
2893
- }
2894
- catch (error) {
2895
- logger(`Model.resolveAsync: scoped lookup failed for "${modelName}": ${error instanceof Error ? error.message : String(error)}`);
2896
- }
2897
- }
2898
- }
2899
- return modelName ? this.getByNameAsync(modelName, undefined, { hold: false }) : undefined;
2900
- }
2901
- /**
2902
- * Get Model instance by name, querying database if not in cache
2903
- * This is an async version that can query the database when schemaName is not provided
2904
- *
2905
- * @param modelName - The name of the model
2906
- * @param schemaName - Optional schema name (will query DB if not provided)
2907
- * @returns The Model instance if found, undefined otherwise. Takes a hold unless `hold: false`.
2908
- */
2909
- static async getByNameAsync(modelName, schemaName, options) {
2910
- // First try cache
2911
- const cached = options?.hold === false ? this.peekByName(modelName, schemaName) : this.getByName(modelName, schemaName);
2912
- if (cached) {
2913
- return cached;
2914
- }
2915
- // If not in cache and schemaName not provided, query database
2916
- if (!schemaName) {
2917
- const db = BaseDb.getAppDb();
2918
- if (!db) {
2919
- return undefined;
2920
- }
2921
- const evictionEpoch = options?.evictionEpoch ?? currentEvictionEpoch();
2922
- try {
2923
- // Query model by name (throws AmbiguousModelError when several schemas define it)
2924
- const _mod_10 = await import('./index-rOTW-2QZ.js');
2925
- const _ns_10 = _mod_10.be;
2926
- const { resolveModelRecord } = _ns_10;
2927
- const resolved = await resolveModelRecord(modelName, {}, db);
2928
- if (!resolved?.schemaFileId) {
2929
- return undefined;
2930
- }
2931
- const modelRecords = await db
2932
- .select({
2933
- modelFileId: models.schemaFileId,
2934
- modelName: models.name,
2935
- schemaName: schemas.name,
2936
- })
2937
- .from(models)
2938
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
2939
- .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
2940
- .where(eq(models.id, resolved.id))
2941
- .limit(1);
2942
- if (modelRecords.length === 0) {
2943
- return undefined;
2944
- }
2945
- const record = modelRecords[0];
2946
- if (!record.modelName || !record.schemaName || !record.modelFileId) {
2947
- return undefined;
2948
- }
2949
- // Its schema was evicted, or it was destroyed, while we read the row: don't bring it back.
2950
- if (schemaEvictedSince(record.schemaName, evictionEpoch)) {
2951
- return undefined;
2952
- }
2953
- if (modelDestroyedSince(record.modelFileId, evictionEpoch)) {
2954
- return undefined;
2955
- }
2956
- // Create model instance (will be cached)
2957
- return this.create(record.modelName, record.schemaName, {
2958
- id: record.modelFileId, // id is now the schemaFileId (string)
2959
- ...(options?.hold === false && { hold: false }),
2960
- });
2961
- }
2962
- catch (error) {
2963
- if (error instanceof AmbiguousModelError)
2964
- throw error;
2965
- logger(`Model.getByNameAsync: Error looking up model by name "${modelName}": ${error instanceof Error ? error.message : String(error)}`);
2966
- return undefined;
2967
- }
2968
- }
2969
- return undefined;
2970
- }
2971
- /**
2972
- * Create or get Model instance by modelFileId
2973
- * Checks cache first, then database if not found
2974
- *
2975
- * @param modelFileId - The model file ID to look up
2976
- * @returns The Model instance if found, undefined otherwise
2977
- */
2978
- static async createById(modelFileId, options) {
2979
- if (!modelFileId) {
2980
- return undefined;
2981
- }
2982
- // Step 1: Check cache first (fast path)
2983
- const cached = options?.hold === false ? this.peek(modelFileId) : this.getById(modelFileId);
2984
- if (cached) {
2985
- logger(`Model.createById: Found instance in cache for ID "${modelFileId}"`);
2986
- return cached;
2987
- }
2988
- // Step 2: Query database to get modelName and schemaName
2989
- const db = BaseDb.getAppDb();
2990
- if (!db) {
2991
- logger(`Model.createById: Database not available for ID "${modelFileId}"`);
2992
- return undefined;
2993
- }
2994
- const evictionEpoch = options?.evictionEpoch ?? currentEvictionEpoch();
2995
- try {
2996
- // Query model by schemaFileId and join to get schema name
2997
- const modelRecords = await db
2998
- .select({
2999
- modelId: models.id,
3000
- modelName: models.name,
3001
- schemaName: schemas.name,
3002
- })
3003
- .from(models)
3004
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
3005
- .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
3006
- .where(eq(models.schemaFileId, modelFileId))
3007
- .limit(1);
3008
- if (modelRecords.length === 0) {
3009
- logger(`Model.createById: Model not found in database for ID "${modelFileId}"`);
3010
- return undefined;
3011
- }
3012
- const { modelName, schemaName } = modelRecords[0];
3013
- if (!modelName || !schemaName) {
3014
- logger(`Model.createById: Missing modelName or schemaName for ID "${modelFileId}"`);
3015
- return undefined;
3016
- }
3017
- // Its schema was evicted while we read the row (e.g. a Schema live query in flight during
3018
- // Schema.destroy or test cleanup): don't bring the model back.
3019
- if (schemaEvictedSince(schemaName, evictionEpoch)) {
3020
- logger(`Model.createById: schema "${schemaName}" was evicted during the lookup of "${modelFileId}"`);
3021
- return undefined;
3022
- }
3023
- // Destroyed while we read the row: destroy() clears the caches before it deletes the rows
3024
- if (modelDestroyedSince(modelFileId, evictionEpoch)) {
3025
- logger(`Model.createById: model "${modelFileId}" was destroyed during the lookup`);
3026
- return undefined;
3027
- }
3028
- // Step 3: Create model using existing create method (which will handle caching)
3029
- logger(`Model.createById: Creating model "${modelName}" in schema "${schemaName}" from ID "${modelFileId}"`);
3030
- return this.create(modelName, schemaName, {
3031
- id: modelFileId, // id is now the schemaFileId (string)
3032
- ...(options?.hold === false && { hold: false }),
3033
- });
3034
- }
3035
- catch (error) {
3036
- logger(`Model.createById: Error looking up model by ID "${modelFileId}": ${error instanceof Error ? error.message : String(error)}`);
3037
- return undefined;
3038
- }
3039
- }
3040
- /**
3041
- * Find Model instance by modelFileId, modelName/schemaName, or both
3042
- * Waits for the model to be fully loaded (idle state) by default
3043
- * @param options - Find options including lookup parameters and wait configuration
3044
- * @returns Model instance if found, undefined otherwise
3045
- */
3046
- static async find({ modelFileId, modelName, schemaName, waitForReady = true, readyTimeout = DEFAULT_READY_TIMEOUT_MS, }) {
3047
- return await findEntity({
3048
- getById: (id) => Model.getById(id),
3049
- createById: (id) => Model.createById(id),
3050
- getByName: (name, ...args) => {
3051
- const schemaName = args[0];
3052
- return Model.getByName(name, schemaName);
3053
- },
3054
- createByName: async (name, ...args) => {
3055
- const schemaName = args[0];
3056
- return Model.getByNameAsync(name, schemaName);
3057
- },
3058
- }, {
3059
- id: modelFileId,
3060
- name: modelName,
3061
- schemaName,
3062
- }, {
3063
- waitForReady,
3064
- readyTimeout,
3065
- });
3066
- }
3067
- /**
3068
- * Get all Model instances for a schema by schemaFileId or schemaName
3069
- * Queries database for all models with the given schemaId/schemaName and returns Model instances
3070
- *
3071
- * @param schemaIdentifier - The schema file ID or schema name to get models for
3072
- * @returns Array of Model instances for the schema
3073
- */
3074
- static async createBySchemaId(schemaIdentifier) {
3075
- if (!schemaIdentifier) {
3076
- return [];
3077
- }
3078
- const db = BaseDb.getAppDb();
3079
- if (!db) {
3080
- logger(`Model.createBySchemaId: Database not available for schema "${schemaIdentifier}"`);
3081
- return [];
3082
- }
3083
- const evictionEpoch = currentEvictionEpoch();
3084
- try {
3085
- // Query all models for this schema - support both schemaFileId and schema name
3086
- const modelRecords = await db
3087
- .select({
3088
- modelFileId: models.schemaFileId,
3089
- modelName: models.name,
3090
- schemaName: schemas.name,
3091
- })
3092
- .from(schemas)
3093
- .innerJoin(modelSchemas, eq(schemas.id, modelSchemas.schemaId))
3094
- .innerJoin(models, eq(modelSchemas.modelId, models.id))
3095
- .where(or(eq(schemas.schemaFileId, schemaIdentifier), eq(schemas.name, schemaIdentifier)));
3096
- if (modelRecords.length === 0) {
3097
- logger(`Model.createBySchemaId: No models found in database for schema "${schemaIdentifier}"`);
3098
- return [];
3099
- }
3100
- // Create Model instances for each model (create() handles caching)
3101
- const modelInstances = [];
3102
- for (const record of modelRecords) {
3103
- if (!record.modelName || !record.schemaName || !record.modelFileId) {
3104
- continue;
3105
- }
3106
- // Its schema was evicted, or the model destroyed, while we read the rows: don't bring it back.
3107
- if (schemaEvictedSince(record.schemaName, evictionEpoch)) {
3108
- continue;
3109
- }
3110
- if (modelDestroyedSince(record.modelFileId, evictionEpoch)) {
3111
- continue;
3112
- }
3113
- // Check cache first
3114
- const cached = this.getById(record.modelFileId);
3115
- if (cached) {
3116
- modelInstances.push(cached);
3117
- }
3118
- else {
3119
- // Create model instance (will be cached)
3120
- const model = this.create(record.modelName, record.schemaName, {
3121
- id: record.modelFileId, // id is now the schemaFileId (string)
3122
- waitForReady: false,
3123
- });
3124
- modelInstances.push(model);
3125
- }
3126
- }
3127
- logger(`Model.createBySchemaId: Found ${modelInstances.length} models for schema "${schemaIdentifier}"`);
3128
- return modelInstances;
3129
- }
3130
- catch (error) {
3131
- logger(`Model.createBySchemaId: Error looking up models for schema "${schemaIdentifier}": ${error instanceof Error ? error.message : String(error)}`);
3132
- return [];
3133
- }
3134
- }
3135
- /**
3136
- * Get all Model instances, optionally filtered by schema.
3137
- * When DB is available, loads from DB via getModelsData; otherwise returns from cache.
3138
- * With waitForReady, waits for each model to be ready (idle, with any properties from Model.create
3139
- * created) and leaves out models that error or time out. Takes no holds: a caller that keeps a
3140
- * model uses `Model.find` or `Model.create`.
3141
- */
3142
- static async all(schemaName, options) {
3143
- const { waitForReady = false, readyTimeout = DEFAULT_READY_TIMEOUT_MS } = options ?? {};
3144
- const appDb = BaseDb.getAppDb();
3145
- if (!appDb) {
3146
- const instances = [];
3147
- const seen = new Set();
3148
- if (schemaName !== undefined && schemaName !== '') {
3149
- for (const [nameKey, id] of this.instanceCacheByName.entries()) {
3150
- const [cachedSchemaName] = nameKey.split(':');
3151
- if (cachedSchemaName === schemaName) {
3152
- const instance = this.peek(id);
3153
- if (instance && !seen.has(id)) {
3154
- instances.push(instance);
3155
- seen.add(id);
3156
- }
3157
- }
3158
- }
3159
- for (const [nameKey, entry] of this.instanceCache.entries()) {
3160
- const [cachedSchemaName] = nameKey.split(':');
3161
- if (cachedSchemaName === schemaName) {
3162
- const context = entry.instance._getSnapshotContext();
3163
- const id = context.id;
3164
- if (id && !seen.has(id)) {
3165
- instances.push(entry.instance);
3166
- seen.add(id);
3167
- }
3168
- }
3169
- }
3170
- }
3171
- else {
3172
- for (const [id, entry] of this.instanceCacheById.entries()) {
3173
- if (!seen.has(id)) {
3174
- instances.push(entry.instance);
3175
- seen.add(id);
3176
- }
3177
- }
3178
- for (const entry of this.instanceCache.values()) {
3179
- const context = entry.instance._getSnapshotContext();
3180
- const id = context.id;
3181
- if (id && !seen.has(id)) {
3182
- instances.push(entry.instance);
3183
- seen.add(id);
3184
- }
3185
- }
3186
- }
3187
- return instances;
3188
- }
3189
- const rows = await getModelsData(schemaName);
3190
- const instances = [];
3191
- const seen = new Set();
3192
- for (const row of rows) {
3193
- // A model can appear more than once (e.g. duplicate model_schemas links); return it once.
3194
- if (row.schemaFileId && !seen.has(row.schemaFileId)) {
3195
- const instance = await this.createById(row.schemaFileId, { hold: false });
3196
- if (instance && !seen.has(instance.id ?? row.schemaFileId)) {
3197
- instances.push(instance);
3198
- seen.add(instance.id ?? row.schemaFileId);
3199
- }
3200
- }
3201
- }
3202
- // Merge in any instances from cache for this schema (e.g. just-created model not yet visible in DB read).
3203
- if (schemaName !== undefined && schemaName !== '') {
3204
- for (const [nameKey, id] of this.instanceCacheByName.entries()) {
3205
- const [cachedSchemaName] = nameKey.split(':');
3206
- if (cachedSchemaName === schemaName && id && !seen.has(id)) {
3207
- const instance = this.peek(id);
3208
- if (instance) {
3209
- instances.push(instance);
3210
- seen.add(id);
3211
- }
3212
- }
3213
- }
3214
- for (const [nameKey, entry] of this.instanceCache.entries()) {
3215
- const [cachedSchemaName] = nameKey.split(':');
3216
- if (cachedSchemaName === schemaName) {
3217
- const context = entry.instance._getSnapshotContext();
3218
- const id = context.id;
3219
- if (id && !seen.has(id)) {
3220
- instances.push(entry.instance);
3221
- seen.add(id);
3222
- }
3223
- }
3224
- }
3225
- }
3226
- if (waitForReady && instances.length > 0) {
3227
- // Leave out models that errored or didn't get ready in time. Don't re-read their state afterwards:
3228
- // a ready model can already be validating again, and it still belongs in the result.
3229
- const ready = await Promise.all(instances.map((m) => this.waitUntilReady(m, readyTimeout).then(() => true, (error) => {
3230
- logger(`Model.all: leaving out a model that isn't ready: ${error instanceof Error ? error.message : String(error)}`);
3231
- return false;
3232
- })));
3233
- return instances.filter((_, i) => ready[i]);
3234
- }
3235
- return instances;
3236
- }
3237
- /**
3238
- * Update name index when model name changes
3239
- */
3240
- static updateNameIndex(oldName, newName, schemaName, modelFileId) {
3241
- const oldKey = `${schemaName}:${oldName}`;
3242
- const newKey = `${schemaName}:${newName}`;
3243
- // Update name index
3244
- if (this.instanceCacheByName.has(oldKey)) {
3245
- this.instanceCacheByName.delete(oldKey);
3246
- }
3247
- this.instanceCacheByName.set(newKey, modelFileId);
3248
- // Also update legacy cache if it exists
3249
- if (this.instanceCache.has(oldKey)) {
3250
- const entry = this.instanceCache.get(oldKey);
3251
- this.instanceCache.delete(oldKey);
3252
- this.instanceCache.set(newKey, entry);
3253
- }
3254
- logger(`Model.updateNameIndex: Updated name mapping from "${oldName}" to "${newName}" (ID: ${modelFileId})`);
3255
- }
3256
- getService() {
3257
- return this._service;
3258
- }
3259
- _getSnapshot() {
3260
- return this._service.getSnapshot();
3261
- }
3262
- _getSnapshotContext() {
3263
- return this._getSnapshot().context;
3264
- }
3265
- /**
3266
- * Check for conflicts between actor context and database
3267
- * @returns ConflictResult indicating if a conflict exists
3268
- */
3269
- async _checkForConflicts() {
3270
- const context = this._getSnapshotContext();
3271
- // If we don't have load metadata, can't check for conflicts
3272
- if (!context._dbUpdatedAt || !context.id) {
3273
- return { hasConflict: false };
3274
- }
3275
- try {
3276
- const db = BaseDb.getAppDb();
3277
- if (!db) {
3278
- return { hasConflict: false }; // Can't check without DB
3279
- }
3280
- // Get current DB record by id (schemaFileId) - most reliable lookup
3281
- const dbModels = await db
3282
- .select()
3283
- .from(models)
3284
- .where(eq(models.schemaFileId, context.id)) // id is now the schemaFileId (string)
3285
- .limit(1);
3286
- if (dbModels.length === 0) {
3287
- return { hasConflict: false }; // No DB record, no conflict
3288
- }
3289
- // Check if DB was updated after we loaded
3290
- // Note: models table doesn't have updatedAt, so we can't do timestamp comparison
3291
- // For now, we'll just check if the record exists and matches
3292
- // In the future, if models table gets updatedAt, we can compare timestamps
3293
- return { hasConflict: false };
3294
- }
3295
- catch (error) {
3296
- logger(`Error checking for conflicts: ${error instanceof Error ? error.message : String(error)}`);
3297
- return { hasConflict: false }; // On error, assume no conflict to allow save
3298
- }
3299
- }
3300
- /**
3301
- * Saves model name changes to the database immediately (draft save)
3302
- * This is called when modelName is changed to persist the change immediately
3303
- * @param oldName - The old model name (if name changed)
3304
- * @param newName - The new model name
3305
- */
3306
- async _saveDraftToDb(oldName, newName) {
3307
- saveDraftLogger(`_saveDraftToDb called for model (oldName: "${oldName}", newName: "${newName}")`);
3308
- // Don't save during initialization - models are being loaded from files, not created as drafts
3309
- // Check this FIRST before doing any expensive work like _getSnapshotContext()
3310
- if (typeof window !== 'undefined') {
3311
- const now = Date.now();
3312
- // Use cached check to avoid expensive operations
3313
- // Always check if cache is stale or if we previously got false (to allow recovery)
3314
- const cacheIsStale = Model.cachedClientInitialized === null || (now - Model.clientCheckTime) > Model.CLIENT_CHECK_CACHE_MS;
3315
- const shouldRecheck = cacheIsStale || Model.cachedClientInitialized === false;
3316
- saveDraftLogger(`Client check: cacheIsStale=${cacheIsStale}, shouldRecheck=${shouldRecheck}, cachedValue=${Model.cachedClientInitialized}, timeSinceCheck=${now - Model.clientCheckTime}ms`);
3317
- if (shouldRecheck) {
3318
- try {
3319
- // Use dynamic import for browser compatibility (require() doesn't work in browsers)
3320
- const client = getClient();
3321
- const clientSnapshot = client.getService().getSnapshot();
3322
- // Check if state is IDLE (primary check) - isInitialized is set in entry action so should be true
3323
- // But we check it as a secondary safeguard
3324
- const isIdle = clientSnapshot.value === ClientManagerState.IDLE;
3325
- const isInitialized = clientSnapshot.context.isInitialized;
3326
- // If state is IDLE, trust it even if isInitialized isn't set yet (entry action should set it)
3327
- // This aligns with useIsClientReady which only checks the state value
3328
- Model.cachedClientInitialized = isIdle && (isInitialized !== false);
3329
- Model.clientCheckTime = now;
3330
- saveDraftLogger(`Client state checked: state=${clientSnapshot.value}, isIdle=${isIdle}, isInitialized=${isInitialized}, result=${Model.cachedClientInitialized}`);
3331
- }
3332
- catch (error) {
3333
- // If we can't check client state, assume not initialized to be safe
3334
- // But only cache for a short time to allow recovery
3335
- Model.cachedClientInitialized = false;
3336
- Model.clientCheckTime = now;
3337
- saveDraftLogger(`Error checking client state: ${error instanceof Error ? error.message : String(error)}, setting cachedClientInitialized=false`);
3338
- }
3339
- }
3340
- else {
3341
- saveDraftLogger(`Using cached client state: cachedClientInitialized=${Model.cachedClientInitialized}`);
3342
- }
3343
- if (!Model.cachedClientInitialized) {
3344
- saveDraftLogger(`Client not initialized, skipping save (oldName: "${oldName}", newName: "${newName}")`);
3345
- // Skip silently during initialization to avoid log spam and reduce overhead
3346
- return;
3347
- }
3348
- }
3349
- saveDraftLogger(`Client is initialized, proceeding with save (oldName: "${oldName}", newName: "${newName}")`);
3350
- // Check for conflicts before saving
3351
- const conflictCheck = await this._checkForConflicts();
3352
- if (conflictCheck.hasConflict) {
3353
- const errorMessage = conflictCheck.message || 'Database was updated externally. Please reload the model and try again.';
3354
- saveDraftLogger(`CONFLICT DETECTED: ${errorMessage}`);
3355
- throw new ConflictError(errorMessage, conflictCheck);
3356
- }
3357
- const context = this._getSnapshotContext();
3358
- const modelFileId = context.id || ''; // id is now the schemaFileId (string)
3359
- const modelName = newName || context.modelName || oldName || '';
3360
- // Use modelFileId as the key if available, otherwise use schemaName:modelName
3361
- const saveKey = modelFileId || `${context.schemaName}:${modelName}`;
3362
- // Prevent concurrent saves for the same model
3363
- if (Model.savingModels.has(saveKey)) {
3364
- saveDraftLogger(`Model ${modelName} (key: ${saveKey}) is already being saved, skipping concurrent save`);
3365
- return;
3366
- }
3367
- Model.savingModels.add(saveKey);
3368
- saveDraftLogger(`Starting save for model ${modelName} (key: ${saveKey}, id: ${modelFileId})`);
3369
- try {
3370
- const db = BaseDb.getAppDb();
3371
- if (!db) {
3372
- throw new Error('Database not found');
3373
- }
3374
- // If name changed, use renameModelInDb
3375
- if (oldName && newName && oldName !== newName) {
3376
- saveDraftLogger(`Renaming model from "${oldName}" to "${newName}"`);
3377
- await renameModelInDb(oldName, newName, context.schemaName);
3378
- saveDraftLogger(`Successfully renamed model from "${oldName}" to "${newName}"`);
3379
- }
3380
- else {
3381
- // For other tracked properties, we would save here
3382
- // Currently only modelName is saved immediately, so this is a no-op
3383
- saveDraftLogger(`No name change detected, skipping save (modelName: "${modelName}")`);
3384
- }
3385
- // Update conflict detection metadata after successful save
3386
- // Note: models table doesn't have updatedAt, so we use current timestamp
3387
- const now = Date.now();
3388
- this._service.send({
3389
- type: 'updateContext',
3390
- _dbUpdatedAt: now,
3391
- });
3392
- }
3393
- catch (error) {
3394
- logger(`Failed to save draft to database: ${error instanceof Error ? error.message : String(error)}`);
3395
- // Don't throw - allow operation to continue
3396
- }
3397
- finally {
3398
- // Always remove from saving set
3399
- Model.savingModels.delete(saveKey);
3400
- saveDraftLogger(`Finished save for model ${modelName} (key: ${saveKey})`);
3401
- }
3402
- }
3403
- get status() {
3404
- return this._getSnapshot().value;
3405
- }
3406
- /** The error from the last draft save a rename started, or undefined once a later one succeeds. */
3407
- get lastSaveError() {
3408
- return getDraftSaveError(this._service);
3409
- }
3410
- get validationErrors() {
3411
- return this._getSnapshotContext()._validationErrors || [];
3412
- }
3413
- get isValid() {
3414
- const errors = this.validationErrors;
3415
- return errors.length === 0;
3416
- }
3417
- get isEdited() {
3418
- const context = this._getSnapshotContext();
3419
- // First check in-memory state
3420
- if (context._isEdited !== undefined) {
3421
- return context._isEdited;
3422
- }
3423
- // Fall back to reading from database if we have _dbId
3424
- if (context._dbId) {
3425
- // Load from database asynchronously (fire-and-forget for now)
3426
- // For synchronous getter, we'll need to cache it or make it async
3427
- // For now, return false if not in context
3428
- return false;
3429
- }
3430
- return false;
3431
- }
3432
- get id() {
3433
- return this._getSnapshotContext().id; // id is now the schemaFileId (string)
3434
- }
3435
- get name() {
3436
- return this._getSnapshotContext().modelName;
3437
- }
3438
- /**
3439
- * Returns ModelProperty instances for this model
3440
- * This is a computed property that reads from the service context
3441
- * Note: This is NOT reactive - use useModelProperties() hook for reactivity
3442
- */
3443
- get properties() {
3444
- const snapshot = this._service.getSnapshot();
3445
- const context = snapshot.context;
3446
- // Get property IDs from liveQuery (stored in context, not instanceState)
3447
- const liveQueryIds = context._liveQueryPropertyIds || [];
3448
- // Get _dbId from context for pending writes lookup
3449
- const dbId = context._dbId; // _dbId is the database integer ID
3450
- // Get pending property IDs (synchronous - uses static Map)
3451
- // Lazy import ModelProperty to avoid circular dependency
3452
- const ModelProperty = getModelProperty();
3453
- if (!ModelProperty) {
3454
- // If ModelProperty is not yet loaded, return empty array
3455
- // It will be available on subsequent accesses after the async import completes
3456
- return [];
3457
- }
3458
- const pendingIds = dbId ? ModelProperty.getPendingPropertyIds(dbId) : [];
3459
- // Combine and deduplicate
3460
- const allPropertyIds = [...new Set([...liveQueryIds, ...pendingIds])];
3461
- // Get ModelProperty instances from static cache (synchronous)
3462
- const propertyInstances = [];
3463
- for (const propertyFileId of allPropertyIds) {
3464
- const property = ModelProperty.getById(propertyFileId);
3465
- if (property) {
3466
- propertyInstances.push(property);
3467
- }
3468
- // Note: Cannot create properties asynchronously in this synchronous getter
3469
- // Properties will be created elsewhere when needed
3470
- }
3471
- // Return a new array reference (snapshot at time of access)
3472
- return [...propertyInstances];
3473
- }
3474
- /**
3475
- * Validate the model
3476
- * @returns Validation result
3477
- */
3478
- async validate() {
3479
- return new Promise((resolve) => {
3480
- let resolved = false;
3481
- const subscription = this._service.subscribe((snapshot) => {
3482
- if (snapshot.value === 'idle' || snapshot.value === 'error') {
3483
- if (!resolved) {
3484
- resolved = true;
3485
- subscription.unsubscribe();
3486
- const errors = snapshot.context._validationErrors || [];
3487
- resolve({
3488
- isValid: errors.length === 0,
3489
- errors,
3490
- });
3491
- }
3492
- }
3493
- });
3494
- this._service.send({ type: 'validateModel' });
3495
- // Timeout fallback to ensure we always resolve (15 seconds - longer than actor timeout)
3496
- setTimeout(() => {
3497
- if (!resolved) {
3498
- resolved = true;
3499
- subscription.unsubscribe();
3500
- const errors = this._getSnapshotContext()._validationErrors || [];
3501
- resolve({
3502
- isValid: errors.length === 0,
3503
- errors,
3504
- });
3505
- }
3506
- }, 15000);
3507
- });
3508
- }
3509
- /**
3510
- * Create a new item instance from this model
3511
- * Automatically injects the model name, so you don't need to pass it explicitly.
3512
- *
3513
- * @example
3514
- * const Post = Model.create('Post', schema)
3515
- * const post = await Post.create({ title: 'My Post', content: '...' })
3516
- *
3517
- * @param values - Item property values (modelName is automatically injected)
3518
- * @returns The created item instance
3519
- */
3520
- async create(values) {
3521
- if (!this.modelName) {
3522
- throw new Error('Cannot create item: model name is not set on this Model instance');
3523
- }
3524
- const item = await Item.create({
3525
- modelName: this.modelName,
3526
- schemaName: this.schemaName,
3527
- modelInstance: this,
3528
- ...values,
3529
- });
3530
- return item;
3531
- }
3532
- /**
3533
- * Reload model from database
3534
- * This refreshes the actor context with the latest data from the database
3535
- */
3536
- async reload() {
3537
- logger(`Reloading model ${this.modelName} from database`);
3538
- // Send reload event to machine (which will trigger loadOrCreateModel)
3539
- this._service.send({ type: 'reloadFromDb' });
3540
- // Wait for reload to complete
3541
- return new Promise((resolve, reject) => {
3542
- // Cleared once the reload settles: a pending timer kept Node scripts alive for 10 s.
3543
- const timer = setTimeout(() => {
3544
- subscription.unsubscribe();
3545
- reject(new Error('Timeout waiting for model reload'));
3546
- }, 10000);
3547
- const subscription = this._service.subscribe((snapshot) => {
3548
- if (snapshot.value === 'idle') {
3549
- clearTimeout(timer);
3550
- subscription.unsubscribe();
3551
- resolve();
3552
- }
3553
- else if (snapshot.value === 'error') {
3554
- clearTimeout(timer);
3555
- subscription.unsubscribe();
3556
- reject(new Error('Failed to reload model from database'));
3557
- }
3558
- });
3559
- });
3560
- }
3561
- /**
3562
- * Force-evict every cached Model belonging to a schema, ignoring refCounts, and stop their services.
3563
- * Used by Schema.destroy(): once the DB rows are gone, cached instances would keep a dangling _dbId
3564
- * and be handed back by Model.create/Model.all after the schema is re-imported.
3565
- * @returns names of the evicted models (so callers can evict their ModelProperty instances)
3566
- */
3567
- static evictForSchema(schemaName) {
3568
- // Work already running for this schema's models must not re-cache them when it finishes.
3569
- recordSchemaEviction(schemaName);
3570
- const instances = new Set();
3571
- for (const { instance } of this.instanceCacheById.values())
3572
- instances.add(instance);
3573
- for (const { instance } of this.instanceCache.values())
3574
- instances.add(instance);
3575
- const evictedNames = [];
3576
- for (const instance of instances) {
3577
- let context;
3578
- try {
3579
- context = instance._getSnapshotContext();
3580
- }
3581
- catch {
3582
- continue;
3583
- }
3584
- if (context.schemaName !== schemaName)
3585
- continue;
3586
- const nameKey = `${context.schemaName}:${context.modelName}`;
3587
- if (context.id)
3588
- this.schemaHeldModelIds.delete(context.id);
3589
- clearDestroySubscriptions(instance, { instanceState: modelInstanceState });
3590
- forceRemoveFromCaches(instance, {
3591
- getCacheKeys: () => (context.id ? [context.id, nameKey] : [nameKey]),
3592
- caches: [
3593
- Model.instanceCacheById,
3594
- Model.instanceCacheByName,
3595
- Model.instanceCache,
3596
- ],
3597
- });
3598
- try {
3599
- instance._service.stop();
3600
- }
3601
- catch {
3602
- // Service might already be stopped
3603
- }
3604
- if (context.modelName)
3605
- evictedNames.push(context.modelName);
3606
- }
3607
- return evictedNames;
3608
- }
3609
- /**
3610
- * Unload the model instance and clean up resources
3611
- */
3612
- unload() {
3613
- try {
3614
- const context = this._getSnapshotContext();
3615
- const modelFileId = context.id; // id is now the schemaFileId (string)
3616
- const nameKey = `${context.schemaName}:${context.modelName}`;
3617
- const cacheKeys = [];
3618
- if (modelFileId) {
3619
- cacheKeys.push(modelFileId);
3620
- }
3621
- cacheKeys.push(nameKey);
3622
- unloadEntity(this, {
3623
- getCacheKeys: () => cacheKeys,
3624
- caches: [Model.instanceCacheById, Model.instanceCache],
3625
- secondaryCaches: [Model.instanceCacheByName],
3626
- instanceState: modelInstanceState,
3627
- getService: (instance) => instance._service,
3628
- });
3629
- }
3630
- catch (error) {
3631
- logger(`Error during unload: ${error instanceof Error ? error.message : String(error)}`);
3632
- // Still try to stop service
3633
- try {
3634
- this._service.stop();
3635
- }
3636
- catch {
3637
- // Service might already be stopped
3638
- }
3639
- }
3640
- }
3641
- /**
3642
- * Destroy the model instance completely: remove from caches, delete from database,
3643
- * update Schema context, stop service. Uses shared destroy helpers.
3644
- */
3645
- async destroy() {
3646
- const context = this._getSnapshotContext();
3647
- const modelFileId = context.id;
3648
- const modelName = context.modelName;
3649
- const schemaName = context.schemaName;
3650
- const nameKey = schemaName && modelName ? `${schemaName}:${modelName}` : '';
3651
- clearDestroySubscriptions(this, { instanceState: modelInstanceState });
3652
- // Before the caches are cleared, so a lookup by id already reading the row doesn't re-cache it
3653
- if (modelFileId)
3654
- recordModelDestroy(modelFileId);
3655
- if (modelFileId)
3656
- Model.schemaHeldModelIds.delete(modelFileId);
3657
- const cacheKeys = [];
3658
- if (modelFileId)
3659
- cacheKeys.push(modelFileId);
3660
- if (nameKey)
3661
- cacheKeys.push(nameKey);
3662
- forceRemoveFromCaches(this, {
3663
- getCacheKeys: () => cacheKeys,
3664
- caches: [
3665
- Model.instanceCacheById,
3666
- Model.instanceCacheByName,
3667
- Model.instanceCache,
3668
- ],
3669
- });
3670
- const destroying = runDestroyLifecycle(this, {
3671
- getService: (instance) => instance._service,
3672
- doDestroy: async () => {
3673
- const db = BaseDb.getAppDb();
3674
- if (db && modelFileId) {
3675
- const drizzleMod = await import('drizzle-orm');
3676
- const { eq } = drizzleMod;
3677
- const modelRecords = await db
3678
- .select()
3679
- .from(models)
3680
- .where(eq(models.schemaFileId, modelFileId))
3681
- .limit(1);
3682
- if (modelRecords.length > 0) {
3683
- const modelId = modelRecords[0].id;
3684
- const { detachModelRows } = await import('./detachRowsBeforeDelete-Di1enVUF.js');
3685
- await detachModelRows(db, [typeof modelId === 'string' ? parseInt(modelId, 10) : modelId]);
3686
- await db.delete(modelSchemas).where(eq(modelSchemas.modelId, modelId));
3687
- await db
3688
- .delete(properties)
3689
- .where(eq(properties.modelId, typeof modelId === 'string' ? parseInt(modelId, 10) : modelId));
3690
- await db.delete(models).where(eq(models.id, modelId));
3691
- }
3692
- }
3693
- if (schemaName && modelName) {
3694
- // Imported here, not at module load: Model and Schema import each other.
3695
- const _mod_11 = await import('./Schema-BS8oR7QV.js');
3696
- const _ns_11 = _mod_11.a;
3697
- const { Schema } = _ns_11;
3698
- const schema = Schema.create(schemaName, { waitForReady: false, hold: false });
3699
- const schemaContext = schema.getService().getSnapshot().context;
3700
- if (schemaContext.models?.[modelName]) {
3701
- const updatedModels = { ...schemaContext.models };
3702
- delete updatedModels[modelName];
3703
- schema.getService().send({ type: 'updateContext', models: updatedModels });
3704
- schema.getService().send({ type: 'markAsDraft', propertyKey: `model:${modelName}` });
3705
- await new Promise((resolve) => setTimeout(resolve, 100));
3706
- }
3707
- }
3708
- },
3709
- });
3710
- try {
3711
- await destroying;
3712
- }
3713
- finally {
3714
- if (modelFileId)
3715
- finishModelDestroy(modelFileId);
3716
- }
3717
- }
3718
- /**
3719
- * Set up liveQuery subscription to watch for property changes in the database
3720
- * This enables cross-instance synchronization (e.g., changes in other tabs/windows)
3721
- */
3722
- _setupLiveQuerySubscription() {
3723
- setupEntityLiveQuery(this, {
3724
- getEntityId: async (model) => {
3725
- const context = model._getSnapshotContext();
3726
- let dbId = context._dbId; // _dbId is the database integer ID
3727
- const modelName = context.modelName;
3728
- if (!modelName) {
3729
- return undefined;
3730
- }
3731
- // If we don't have _dbId yet, try to find it in the database
3732
- if (!dbId) {
3733
- try {
3734
- const db = BaseDb.getAppDb();
3735
- if (!db) {
3736
- return undefined;
3737
- }
3738
- // Model names are only unique per schema: find this model's row by its id (schemaFileId).
3739
- const _mod_12 = await import('./index-rOTW-2QZ.js');
3740
- const _ns_12 = _mod_12.be;
3741
- const { resolveModelRecord } = _ns_12;
3742
- const modelRecord = await resolveModelRecord(modelName, { modelFileId: context.id, schemaName: context.schemaName }, db);
3743
- if (!modelRecord?.id) {
3744
- return undefined;
3745
- }
3746
- // Until this model's own row is written, the lookup falls back to the name and can find a
3747
- // same-named model in another schema. Taking that row would load its properties into this
3748
- // model; wait for the row with this model's id instead (the caller retries).
3749
- if (context.id && modelRecord.schemaFileId !== context.id) {
3750
- return undefined;
3751
- }
3752
- dbId = modelRecord.id;
3753
- // Update context with _dbId (unless the model was stopped while the lookup ran)
3754
- if (isActorStopped(model._service))
3755
- return undefined;
3756
- model._service.send({
3757
- type: 'updateContext',
3758
- _dbId: dbId,
3759
- });
3760
- }
3761
- catch (error) {
3762
- logger(`[Model._setupLiveQuerySubscription] Error finding model: ${error}`);
3763
- return undefined;
3764
- }
3765
- }
3766
- return dbId;
3767
- },
3768
- buildQuery: async (modelId) => {
3769
- return BaseDb.liveQuery((sql) => sql `
3770
- SELECT id, name, data_type as dataType, model_id as modelId, ref_model_id as refModelId, ref_value_type as refValueType, schema_file_id as schemaFileId
3771
- FROM properties
3772
- WHERE model_id = ${modelId}
3773
- `);
3774
- },
3775
- extractEntityIds: (rows) => rows
3776
- .map(row => row.schemaFileId)
3777
- .filter((id) => id !== null && id !== undefined),
3778
- updateContext: (model, ids) => {
3779
- model._service.send({
3780
- type: 'updateContext',
3781
- _liveQueryPropertyIds: ids, // Store in service context for reactivity
3782
- _propertiesUpdated: Date.now(), // Internal field for tracking
3783
- });
3784
- },
3785
- createChildInstances: async (ids) => {
3786
- await modelPropertyImportPromise;
3787
- const ModelProperty = getModelProperty();
3788
- if (!ModelProperty) {
3789
- logger('[Model._setupLiveQuerySubscription] ModelProperty not yet loaded');
3790
- return;
3791
- }
3792
- const createPromises = ids.map(async (propertyFileId) => {
3793
- try {
3794
- await ModelProperty.createById(propertyFileId);
3795
- }
3796
- catch (error) {
3797
- logger(`[Model._setupLiveQuerySubscription] Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
3798
- }
3799
- });
3800
- await Promise.all(createPromises);
3801
- },
3802
- queryInitialData: async (modelId) => {
3803
- const db = BaseDb.getAppDb();
3804
- if (!db) {
3805
- return [];
3806
- }
3807
- const initialProperties = await db
3808
- .select({ schemaFileId: properties.schemaFileId })
3809
- .from(properties)
3810
- .where(eq(properties.modelId, typeof modelId === 'string' ? parseInt(modelId, 10) : modelId));
3811
- return initialProperties.map((row) => ({
3812
- schemaFileId: row.schemaFileId,
3813
- }));
3814
- },
3815
- // The model writes from its writeProcess child while itself idle; wait for that write to end.
3816
- canRetryEntityId: (model) => {
3817
- const writeProcess = model._getSnapshotContext().writeProcess;
3818
- const writeState = writeProcess?.getSnapshot().value;
3819
- return writeState !== 'validating' && writeState !== 'writing';
3820
- },
3821
- instanceState: modelInstanceState,
3822
- loggerName: 'seedSdk:model:liveQuery',
3823
- });
3824
- }
3825
- /**
3826
- * Refresh property IDs from database (useful in Node.js where liveQuery isn't available)
3827
- */
3828
- async _refreshPropertiesFromDb() {
3829
- const snapshot = this._service.getSnapshot();
3830
- const dbId = snapshot.context._dbId; // _dbId is the database integer ID
3831
- const modelName = snapshot.context.modelName;
3832
- if (!dbId || !modelName) {
3833
- return;
3834
- }
3835
- try {
3836
- const db = BaseDb.getAppDb();
3837
- if (!db) {
3838
- return;
3839
- }
3840
- const propertyRows = await db
3841
- .select({ schemaFileId: properties.schemaFileId })
3842
- .from(properties)
3843
- .where(eq(properties.modelId, dbId)); // Use _dbId (database integer ID)
3844
- // Stopped while querying: don't re-cache ModelProperty instances for an evicted/unloaded model
3845
- if (isActorStopped(this._service))
3846
- return;
3847
- const propertyIds = propertyRows
3848
- .map((row) => row.schemaFileId)
3849
- .filter((id) => id !== null && id !== undefined);
3850
- // CRITICAL: Create ModelProperty instances BEFORE updating context
3851
- // This ensures they're in the cache when the properties getter is called
3852
- if (propertyIds.length > 0) {
3853
- try {
3854
- await modelPropertyImportPromise;
3855
- const ModelProperty = getModelProperty();
3856
- if (!ModelProperty) {
3857
- logger('[Model._refreshPropertiesFromDb] ModelProperty not yet loaded');
3858
- }
3859
- else {
3860
- const createPromises = propertyIds.map(async (propertyFileId) => {
3861
- try {
3862
- const property = await ModelProperty.createById(propertyFileId);
3863
- if (property) {
3864
- logger(`[Model._refreshPropertiesFromDb] Created/cached ModelProperty instance for propertyFileId "${propertyFileId}"`);
3865
- }
3866
- else {
3867
- logger(`[Model._refreshPropertiesFromDb] ModelProperty.createById returned undefined for propertyFileId "${propertyFileId}"`);
3868
- }
3869
- }
3870
- catch (error) {
3871
- logger(`[Model._refreshPropertiesFromDb] Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
3872
- }
3873
- });
3874
- await Promise.all(createPromises);
3875
- }
3876
- }
3877
- catch (error) {
3878
- logger(`[Model._refreshPropertiesFromDb] Error importing ModelProperty or creating instances: ${error}`);
3879
- }
3880
- }
3881
- // Update context with refreshed property IDs AFTER creating instances (the awaits above can
3882
- // outlive the instance)
3883
- if (isActorStopped(this._service))
3884
- return;
3885
- this._service.send({
3886
- type: 'updateContext',
3887
- _liveQueryPropertyIds: propertyIds,
3888
- _propertiesUpdated: Date.now(),
3889
- });
3890
- }
3891
- catch (error) {
3892
- logger(`[Model._refreshPropertiesFromDb] Error refreshing properties: ${error}`);
3893
- }
3894
- }
3895
- /**
3896
- * Public method to refresh properties from database
3897
- */
3898
- async refreshProperties() {
3899
- await this._refreshPropertiesFromDb();
3900
- }
3901
- /**
3902
- * Refresh the property ids of every cached Model whose DB row is `dbId`. Called when a property
3903
- * write for that model finishes: until then `properties` includes it as a pending write, and in Node
3904
- * (no reactive liveQuery) nothing else adds it to `_liveQueryPropertyIds`.
3905
- */
3906
- static async refreshPropertiesForDbId(dbId) {
3907
- const instances = new Set();
3908
- for (const { instance } of this.instanceCacheById.values())
3909
- instances.add(instance);
3910
- for (const { instance } of this.instanceCache.values())
3911
- instances.add(instance);
3912
- for (const instance of instances) {
3913
- let context;
3914
- try {
3915
- context = instance._getSnapshotContext();
3916
- }
3917
- catch {
3918
- continue;
3919
- }
3920
- if (context._dbId !== dbId)
3921
- continue;
3922
- await instance._refreshPropertiesFromDb();
3923
- }
3924
- }
3925
- }
3926
- // Primary cache: ID-based (never changes on rename)
3927
- Model.instanceCacheById = new Map();
3928
- // Secondary index: name lookup (updated on rename)
3929
- Model.instanceCacheByName = new Map();
3930
- // Legacy cache: kept temporarily for backward compatibility during migration
3931
- Model.instanceCache = new Map();
3932
- // Static tracking for pending writes
3933
- Model.pendingWrites = new Map();
3934
- // Track which models are currently being saved to prevent concurrent saves
3935
- Model.savingModels = new Set();
3936
- // Cache client initialization state globally to avoid repeated checks
3937
- Model.cachedClientInitialized = null;
3938
- Model.clientCheckTime = 0;
3939
- Model.CLIENT_CHECK_CACHE_MS = 50; // Cache for 50ms to avoid excessive checks
3940
- /**
3941
- * Models their schema holds, by modelFileId. A schema's first load of a model takes one hold for
3942
- * the schema, and the schema's last unload releases it (releaseSchemaHolds), so a user's unload
3943
- * can't drop a model out of `schema.models` while the schema is loaded.
3944
- */
3945
- Model.schemaHeldModelIds = new Set();
3946
-
3947
- var Model$1 = /*#__PURE__*/Object.freeze({
3948
- __proto__: null,
3949
- Model: Model
3950
- });
3951
-
3952
- // Export the Model class
3953
- const TModelValues = Type.Record(Type.String(), Type.Any());
3954
- const TModelSchema = Type.Record(Type.String(), TProperty);
3955
-
3956
- /**
3957
- * Thrown by Item.create when values don't fit the model: unknown keys, wrong types, or values
3958
- * that break the property's validation rules. Nothing is left written: most values are checked
3959
- * before anything is written, and File/Image/Html/ItemStorage values, checked as they're saved,
3960
- * have their draft removed.
3961
- */
3962
- class ItemCreateValidationError extends Error {
3963
- constructor(modelName, problems) {
3964
- super(`Cannot create ${modelName}: ` + problems.map((p) => `${p.property}: ${p.message}`).join('; '));
3965
- this.name = 'ItemCreateValidationError';
3966
- this.modelName = modelName;
3967
- this.problems = problems;
3968
- Object.setPrototypeOf(this, ItemCreateValidationError.prototype);
3969
- }
3970
- }
3971
- /**
3972
- * Thrown by Item.create when a File/Image/Html/ItemStorage value couldn't be saved for a reason
3973
- * other than its rules (those throw ItemCreateValidationError). The draft is removed, as for
3974
- * ItemCreateValidationError. `cause` holds the save's own error, e.g. an OwnershipError.
3975
- */
3976
- class ItemCreateError extends Error {
3977
- constructor(message, details) {
3978
- super(message, details.cause === undefined ? undefined : { cause: details.cause });
3979
- this.name = 'ItemCreateError';
3980
- this.code = details.code;
3981
- this.modelName = details.modelName;
3982
- this.property = details.property;
3983
- this.seedLocalId = details.seedLocalId;
3984
- Object.setPrototypeOf(this, ItemCreateError.prototype);
3985
- }
3986
- }
3987
- /** Thrown by Item.create when no model with that name exists (in the given schema, if any). */
3988
- class UnknownModelError extends Error {
3989
- constructor(modelName, schemaName) {
3990
- super(`Model "${modelName}" not found${schemaName ? ` in schema "${schemaName}"` : ''}.`);
3991
- this.name = 'UnknownModelError';
3992
- this.modelName = modelName;
3993
- this.schemaName = schemaName;
3994
- Object.setPrototypeOf(this, UnknownModelError.prototype);
3995
- }
3996
- }
3997
-
3998
- var errors = /*#__PURE__*/Object.freeze({
3999
- __proto__: null,
4000
- ItemCreateError: ItemCreateError,
4001
- ItemCreateValidationError: ItemCreateValidationError,
4002
- UnknownModelError: UnknownModelError
4003
- });
4004
-
4005
- export { ConflictError as C, ItemCreateValidationError as I, Model as M, TModelSchema as T, UnknownModelError as U, TModelValues as a, anyEvictionSince as b, currentEvictionEpoch as c, ItemCreateError as d, setupEntityLiveQuery as e, finishSchemaDestroy as f, getDraftSaveError as g, writeToDatabase$1 as h, Model$1 as i, errors as j, recordSchemaDestroy as r, schemaEvictedSince as s, trackDraftSave as t, writeProcessMachine as w };
4006
- //# sourceMappingURL=errors-CS3nDpUU.js.map