@seedprotocol/sdk 0.6.10 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (399) hide show
  1. package/dist/{BasePathResolver-B6CKqoKw.js → BasePathResolver-DRT09oVv.js} +36 -2
  2. package/dist/BasePathResolver-DRT09oVv.js.map +1 -0
  3. package/dist/ClientManager-DngaZdw6.js +4763 -0
  4. package/dist/ClientManager-DngaZdw6.js.map +1 -0
  5. package/dist/{ClientManager-ChcmUL47.js → Item-ZUAmf943.js} +1175 -5903
  6. package/dist/Item-ZUAmf943.js.map +1 -0
  7. package/dist/{ModelProperty-D79uV42p.js → ModelProperty-6llK4MTH.js} +326 -109
  8. package/dist/ModelProperty-6llK4MTH.js.map +1 -0
  9. package/dist/{Schema-a8uO-hIZ.js → Schema-D-Ud-XYw.js} +164 -885
  10. package/dist/Schema-D-Ud-XYw.js.map +1 -0
  11. package/dist/{SchemaValidationService-Y49vqast.js → SchemaValidationService-IoS7JquP.js} +26 -18
  12. package/dist/SchemaValidationService-IoS7JquP.js.map +1 -0
  13. package/dist/{backfillMetadataPropertyIds-CBsf7LnU.js → backfillMetadataPropertyIds-BlVeE7w7.js} +11 -6
  14. package/dist/backfillMetadataPropertyIds-BlVeE7w7.js.map +1 -0
  15. package/dist/cjs/{ModelProperty-D9FGaGuE.js → ModelProperty-Dv7AQfmQ.js} +297 -89
  16. package/dist/cjs/ModelProperty-Dv7AQfmQ.js.map +1 -0
  17. package/dist/cjs/{Schema-BiJIPLEL.js → Schema-BHVp1g-M.js} +136 -868
  18. package/dist/cjs/Schema-BHVp1g-M.js.map +1 -0
  19. package/dist/cjs/{SchemaValidationService-DtYs_CNY.js → SchemaValidationService-BSU9knjf.js} +18 -11
  20. package/dist/cjs/SchemaValidationService-BSU9knjf.js.map +1 -0
  21. package/dist/cjs/{backfillMetadataPropertyIds-2XaN3b9S.js → backfillMetadataPropertyIds-WIovQ_d8.js} +8 -3
  22. package/dist/cjs/backfillMetadataPropertyIds-WIovQ_d8.js.map +1 -0
  23. package/dist/cjs/{eas-CMNppmGE.js → eas-CUYXdr2t.js} +4 -4
  24. package/dist/cjs/{eas-CMNppmGE.js.map → eas-CUYXdr2t.js.map} +1 -1
  25. package/dist/cjs/{getItem-B6jEINjW.js → getItem-z1tHu0hu.js} +2 -2
  26. package/dist/cjs/{getItem-B6jEINjW.js.map → getItem-z1tHu0hu.js.map} +1 -1
  27. package/dist/cjs/getLatestVersionRow-CcaabxIW.js +59 -0
  28. package/dist/cjs/getLatestVersionRow-CcaabxIW.js.map +1 -0
  29. package/dist/cjs/{getPublishPayload-D5DhLGvN.js → getPublishPayload-CEP_e-Kv.js} +283 -138
  30. package/dist/cjs/getPublishPayload-CEP_e-Kv.js.map +1 -0
  31. package/dist/cjs/{getPublishUploads-DMhCCKYF.js → getPublishUploads-Ctn5k1k6.js} +65 -19
  32. package/dist/cjs/getPublishUploads-Ctn5k1k6.js.map +1 -0
  33. package/dist/cjs/{index-CSNtEnbF.js → index-Cvvt0-5B.js} +2698 -1725
  34. package/dist/cjs/index-Cvvt0-5B.js.map +1 -0
  35. package/dist/cjs/{index-DPp9rdRL.js → index-DfGhF1dc.js} +3 -3
  36. package/dist/cjs/index-DfGhF1dc.js.map +1 -0
  37. package/dist/cjs/{property-DlKiAWt4.js → property-DbEPQ1m9.js} +40 -35
  38. package/dist/cjs/property-DbEPQ1m9.js.map +1 -0
  39. package/dist/cjs/publishDraftGraph-C38gf302.js +403 -0
  40. package/dist/cjs/publishDraftGraph-C38gf302.js.map +1 -0
  41. package/dist/cjs/resetAmbiguousLegacyItemData-CfMhRHwK.js +116 -0
  42. package/dist/cjs/resetAmbiguousLegacyItemData-CfMhRHwK.js.map +1 -0
  43. package/dist/cjs/resolveModelForSyncedSeed-CNYaLOir.js +49 -0
  44. package/dist/cjs/resolveModelForSyncedSeed-CNYaLOir.js.map +1 -0
  45. package/dist/cjs/syncDbWithEas-d1iOdTTI.js +886 -0
  46. package/dist/cjs/syncDbWithEas-d1iOdTTI.js.map +1 -0
  47. package/dist/{configurePlatform-CE0Vew17.js → configurePlatform-CZpjyuCo.js} +4 -4
  48. package/dist/{configurePlatform-CE0Vew17.js.map → configurePlatform-CZpjyuCo.js.map} +1 -1
  49. package/dist/db/drizzle/0015_add_model_file_id_to_seeds.sql +11 -0
  50. package/dist/db/drizzle/0016_add_revoked_at_to_metadata.sql +1 -0
  51. package/dist/db/drizzle/0017_add_revoked_at_to_versions.sql +1 -0
  52. package/dist/db/drizzle/0018_add_storage_settings_to_properties.sql +53 -0
  53. package/dist/db/drizzle/0019_add_derived_from_uid_to_metadata.sql +35 -0
  54. package/dist/db/drizzle/meta/0011_snapshot.json +1182 -0
  55. package/dist/db/drizzle/meta/0012_snapshot.json +1244 -0
  56. package/dist/db/drizzle/meta/0013_snapshot.json +63 -1
  57. package/dist/db/drizzle/meta/0014_snapshot.json +62 -0
  58. package/dist/db/drizzle/meta/0015_snapshot.json +1338 -0
  59. package/dist/db/drizzle/meta/0016_snapshot.json +1345 -0
  60. package/dist/db/drizzle/meta/0017_snapshot.json +1352 -0
  61. package/dist/db/drizzle/meta/0018_snapshot.json +1373 -0
  62. package/dist/db/drizzle/meta/0019_snapshot.json +1380 -0
  63. package/dist/db/drizzle/meta/_journal.json +35 -0
  64. package/dist/{db-WbrzEf7c.js → db-Lw0KVZmc.js} +546 -106
  65. package/dist/db-Lw0KVZmc.js.map +1 -0
  66. package/dist/{eas-CfYCu4IX.js → eas-CQ-Smzk2.js} +7 -7
  67. package/dist/eas-CQ-Smzk2.js.map +1 -0
  68. package/dist/errors-CrBxGWah.js +17 -0
  69. package/dist/errors-CrBxGWah.js.map +1 -0
  70. package/dist/{getItem-Br-bGNFO.js → getItem-CSfSok9f.js} +8 -10
  71. package/dist/{getItem-Br-bGNFO.js.map → getItem-CSfSok9f.js.map} +1 -1
  72. package/dist/getLatestVersionRow-72Atl9vB.js +46 -0
  73. package/dist/getLatestVersionRow-72Atl9vB.js.map +1 -0
  74. package/dist/{getPublishPayload-BIrTVUxb.js → getPublishPayload-76_KfB5Y.js} +297 -154
  75. package/dist/getPublishPayload-76_KfB5Y.js.map +1 -0
  76. package/dist/{getPublishUploads-C6cDeZdz.js → getPublishUploads-CqgMBD8f.js} +68 -21
  77. package/dist/getPublishUploads-CqgMBD8f.js.map +1 -0
  78. package/dist/{index-Djd3667v.js → index-5b3JsS55.js} +2 -2
  79. package/dist/index-5b3JsS55.js.map +1 -0
  80. package/dist/{index-ClSv5pTA.js → index-BpgD305l.js} +12 -11
  81. package/dist/index-BpgD305l.js.map +1 -0
  82. package/dist/{index-HRHkbVQd.js → index-Y-yYyRBf.js} +530 -196
  83. package/dist/index-Y-yYyRBf.js.map +1 -0
  84. package/dist/index.browser-DaTzn79x.js +4139 -0
  85. package/dist/index.browser-DaTzn79x.js.map +1 -0
  86. package/dist/{index.node-DBtQkEfz.js → index.node-NYKqFOoe.js} +9 -4
  87. package/dist/index.node-NYKqFOoe.js.map +1 -0
  88. package/dist/main.cjs +1 -1
  89. package/dist/main.js +886 -560
  90. package/dist/main.js.map +1 -1
  91. package/dist/node.js +19 -15
  92. package/dist/node.js.map +1 -1
  93. package/dist/platform.browser.js +18 -3719
  94. package/dist/platform.browser.js.map +1 -1
  95. package/dist/platform.node.js +3 -3
  96. package/dist/{property-CQjuZ4JW.js → property-ByaiSyUK.js} +53 -48
  97. package/dist/property-ByaiSyUK.js.map +1 -0
  98. package/dist/publishDraftGraph-BRm6Pq3Q.js +405 -0
  99. package/dist/publishDraftGraph-BRm6Pq3Q.js.map +1 -0
  100. package/dist/resetAmbiguousLegacyItemData-B4dX6ZA1.js +88 -0
  101. package/dist/resetAmbiguousLegacyItemData-B4dX6ZA1.js.map +1 -0
  102. package/dist/resolveModelForSyncedSeed-B0tco3zG.js +46 -0
  103. package/dist/resolveModelForSyncedSeed-B0tco3zG.js.map +1 -0
  104. package/dist/seedSchema/MetadataSchema.ts +9 -0
  105. package/dist/seedSchema/ModelSchema.ts +5 -0
  106. package/dist/seedSchema/SeedSchema.ts +3 -0
  107. package/dist/seedSchema/VersionSchema.ts +5 -0
  108. package/dist/src/Item/Item.d.ts +21 -0
  109. package/dist/src/Item/Item.d.ts.map +1 -1
  110. package/dist/src/Item/service/actors/loadOrCreateItem.d.ts.map +1 -1
  111. package/dist/src/Item/service/actors/waitForDb.d.ts.map +1 -1
  112. package/dist/src/Item/service/itemMachineSingle.d.ts +3 -29
  113. package/dist/src/Item/service/itemMachineSingle.d.ts.map +1 -1
  114. package/dist/src/ItemProperty/ItemProperty.d.ts +13 -4
  115. package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
  116. package/dist/src/ItemProperty/service/actors/hydrateFromDb.d.ts.map +1 -1
  117. package/dist/src/ItemProperty/service/actors/loadOrCreateProperty.d.ts.map +1 -1
  118. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveFile.d.ts.map +1 -1
  119. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveHtml.d.ts.map +1 -1
  120. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveImage.d.ts.map +1 -1
  121. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts.map +1 -1
  122. package/dist/src/ItemProperty/service/propertyMachine.d.ts +4 -9
  123. package/dist/src/ItemProperty/service/propertyMachine.d.ts.map +1 -1
  124. package/dist/src/Model/Model.d.ts +52 -2
  125. package/dist/src/Model/Model.d.ts.map +1 -1
  126. package/dist/src/Model/errors.d.ts +10 -0
  127. package/dist/src/Model/errors.d.ts.map +1 -0
  128. package/dist/src/Model/service/actors/createModelProperties.d.ts.map +1 -1
  129. package/dist/src/Model/service/actors/loadOrCreateModel.d.ts.map +1 -1
  130. package/dist/src/Model/service/actors/validateModel.d.ts.map +1 -1
  131. package/dist/src/Model/service/modelMachine.d.ts +0 -8
  132. package/dist/src/Model/service/modelMachine.d.ts.map +1 -1
  133. package/dist/src/ModelProperty/ModelProperty.d.ts +42 -2
  134. package/dist/src/ModelProperty/ModelProperty.d.ts.map +1 -1
  135. package/dist/src/ModelProperty/service/actors/compareAndMarkDraft.d.ts.map +1 -1
  136. package/dist/src/ModelProperty/service/actors/saveToSchema.d.ts.map +1 -1
  137. package/dist/src/ModelProperty/service/actors/validateProperty.d.ts.map +1 -1
  138. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +2 -9
  139. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts.map +1 -1
  140. package/dist/src/Schema/Schema.d.ts +8 -2
  141. package/dist/src/Schema/Schema.d.ts.map +1 -1
  142. package/dist/src/Schema/service/actors/checkExistingSchema.d.ts.map +1 -1
  143. package/dist/src/Schema/service/actors/createModelInstances.d.ts.map +1 -1
  144. package/dist/src/Schema/service/actors/loadOrCreateSchema.d.ts.map +1 -1
  145. package/dist/src/Schema/service/schemaMachine.d.ts +1 -217
  146. package/dist/src/Schema/service/schemaMachine.d.ts.map +1 -1
  147. package/dist/src/Schema/service/validation/SchemaValidationService.d.ts.map +1 -1
  148. package/dist/src/browser/db/Db.d.ts +3 -0
  149. package/dist/src/browser/db/Db.d.ts.map +1 -1
  150. package/dist/src/browser/db/drizzleFiles.d.ts +7 -2
  151. package/dist/src/browser/db/drizzleFiles.d.ts.map +1 -1
  152. package/dist/src/browser/db/embeddedMigrations.d.ts +11 -2
  153. package/dist/src/browser/db/embeddedMigrations.d.ts.map +1 -1
  154. package/dist/src/browser/db/sqliteBusyRetry.d.ts +14 -0
  155. package/dist/src/browser/db/sqliteBusyRetry.d.ts.map +1 -0
  156. package/dist/src/browser/helpers/FileManager.d.ts +1 -0
  157. package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
  158. package/dist/src/browser/helpers/QueryClient.d.ts +1 -0
  159. package/dist/src/browser/helpers/QueryClient.d.ts.map +1 -1
  160. package/dist/src/browser/helpers/opfsErrors.d.ts +2 -0
  161. package/dist/src/browser/helpers/opfsErrors.d.ts.map +1 -0
  162. package/dist/src/browser/helpers/tolerantWebAccess.d.ts +47 -0
  163. package/dist/src/browser/helpers/tolerantWebAccess.d.ts.map +1 -0
  164. package/dist/src/browser/workers/FileDownloader.d.ts.map +1 -1
  165. package/dist/src/browser/workers/ImageResizer.d.ts.map +1 -1
  166. package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
  167. package/dist/src/browser/workers/imageResize.d.ts.map +1 -1
  168. package/dist/src/client/ClientManager.d.ts.map +1 -1
  169. package/dist/src/client/actors/addModelsToDb.d.ts +14 -0
  170. package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
  171. package/dist/src/client/actors/platformClassesInit.d.ts.map +1 -1
  172. package/dist/src/client/actors/processSchemaFiles.d.ts.map +1 -1
  173. package/dist/src/client/actors/saveConfig.d.ts +11 -0
  174. package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
  175. package/dist/src/client/constants.d.ts +0 -8
  176. package/dist/src/client/constants.d.ts.map +1 -1
  177. package/dist/src/db/backfillMetadataPropertyIds.d.ts.map +1 -1
  178. package/dist/src/db/configs/{dev.schema.config.d.ts → migrations.config.d.ts} +1 -1
  179. package/dist/src/db/configs/migrations.config.d.ts.map +1 -0
  180. package/dist/src/db/read/batchLatestPublishedVersionBySeedLocalIds.d.ts +1 -1
  181. package/dist/src/db/read/batchLatestPublishedVersionBySeedLocalIds.d.ts.map +1 -1
  182. package/dist/src/db/read/getItemData.d.ts.map +1 -1
  183. package/dist/src/db/read/getItemProperties.d.ts.map +1 -1
  184. package/dist/src/db/read/getItems.d.ts +7 -2
  185. package/dist/src/db/read/getItems.d.ts.map +1 -1
  186. package/dist/src/db/read/getLatestPublishedVersionRow.d.ts +2 -1
  187. package/dist/src/db/read/getLatestPublishedVersionRow.d.ts.map +1 -1
  188. package/dist/src/db/read/getLatestVersionRow.d.ts +13 -0
  189. package/dist/src/db/read/getLatestVersionRow.d.ts.map +1 -0
  190. package/dist/src/db/read/getModelSchemas.d.ts.map +1 -1
  191. package/dist/src/db/read/getPropertyData.d.ts.map +1 -1
  192. package/dist/src/db/read/getPublishPayload.d.ts +18 -11
  193. package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
  194. package/dist/src/db/read/getPublishPendingDiff.d.ts +2 -1
  195. package/dist/src/db/read/getPublishPendingDiff.d.ts.map +1 -1
  196. package/dist/src/db/read/getPublishUploads.d.ts +5 -0
  197. package/dist/src/db/read/getPublishUploads.d.ts.map +1 -1
  198. package/dist/src/db/read/getSchemaUidForModel.d.ts.map +1 -1
  199. package/dist/src/db/read/getSeedPublishState.d.ts +8 -2
  200. package/dist/src/db/read/getSeedPublishState.d.ts.map +1 -1
  201. package/dist/src/db/read/isSeedRevoked.d.ts +7 -0
  202. package/dist/src/db/read/isSeedRevoked.d.ts.map +1 -0
  203. package/dist/src/db/read/publishDraftGraph.d.ts +57 -0
  204. package/dist/src/db/read/publishDraftGraph.d.ts.map +1 -0
  205. package/dist/src/db/read/publishErrors.d.ts +37 -0
  206. package/dist/src/db/read/publishErrors.d.ts.map +1 -0
  207. package/dist/src/db/read/resolveModelForSyncedSeed.d.ts +39 -0
  208. package/dist/src/db/read/resolveModelForSyncedSeed.d.ts.map +1 -0
  209. package/dist/src/db/read/resolveModelRecord.d.ts +69 -0
  210. package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -0
  211. package/dist/src/db/read/resolveRelatedSeedRef.d.ts +34 -0
  212. package/dist/src/db/read/resolveRelatedSeedRef.d.ts.map +1 -0
  213. package/dist/src/db/read/subqueries/liveVersion.d.ts +12 -0
  214. package/dist/src/db/read/subqueries/liveVersion.d.ts.map +1 -0
  215. package/dist/src/db/read/subqueries/metadataLatest.d.ts +4 -0
  216. package/dist/src/db/read/subqueries/metadataLatest.d.ts.map +1 -1
  217. package/dist/src/db/read/subqueries/versionData.d.ts +7 -2
  218. package/dist/src/db/read/subqueries/versionData.d.ts.map +1 -1
  219. package/dist/src/db/read/summarizePublishWork.d.ts +10 -2
  220. package/dist/src/db/read/summarizePublishWork.d.ts.map +1 -1
  221. package/dist/src/db/resetAmbiguousLegacyItemData.d.ts +17 -0
  222. package/dist/src/db/resetAmbiguousLegacyItemData.d.ts.map +1 -0
  223. package/dist/src/db/sqlParamBatches.d.ts +27 -0
  224. package/dist/src/db/sqlParamBatches.d.ts.map +1 -0
  225. package/dist/src/db/write/addExcludedTransactions.d.ts +7 -0
  226. package/dist/src/db/write/addExcludedTransactions.d.ts.map +1 -0
  227. package/dist/src/db/write/applyPropertyAttestationUidsFromPublish.d.ts +6 -0
  228. package/dist/src/db/write/applyPropertyAttestationUidsFromPublish.d.ts.map +1 -1
  229. package/dist/src/db/write/createMetadata.d.ts +2 -0
  230. package/dist/src/db/write/createMetadata.d.ts.map +1 -1
  231. package/dist/src/db/write/createNewItem.d.ts +3 -1
  232. package/dist/src/db/write/createNewItem.d.ts.map +1 -1
  233. package/dist/src/db/write/createSeed.d.ts +2 -1
  234. package/dist/src/db/write/createSeed.d.ts.map +1 -1
  235. package/dist/src/db/write/createSeeds.d.ts +1 -1
  236. package/dist/src/db/write/createSeeds.d.ts.map +1 -1
  237. package/dist/src/db/write/linkModelToSchema.d.ts +12 -0
  238. package/dist/src/db/write/linkModelToSchema.d.ts.map +1 -0
  239. package/dist/src/db/write/updateItemPropertyValue.d.ts.map +1 -1
  240. package/dist/src/db/write/updateMetadata.d.ts.map +1 -1
  241. package/dist/src/db/write/updateSeedRevokedAt.d.ts +11 -1
  242. package/dist/src/db/write/updateSeedRevokedAt.d.ts.map +1 -1
  243. package/dist/src/db/write/updateSeedUid.d.ts.map +1 -1
  244. package/dist/src/db/write/updateVersionUid.d.ts +2 -1
  245. package/dist/src/db/write/updateVersionUid.d.ts.map +1 -1
  246. package/dist/src/events/files/download.d.ts.map +1 -1
  247. package/dist/src/events/item/easSyncManager.d.ts +2 -0
  248. package/dist/src/events/item/easSyncManager.d.ts.map +1 -1
  249. package/dist/src/events/item/index.d.ts.map +1 -1
  250. package/dist/src/events/item/syncDbWithEas.d.ts +16 -0
  251. package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
  252. package/dist/src/helpers/FileManager/BaseFileManager.d.ts +1 -0
  253. package/dist/src/helpers/FileManager/BaseFileManager.d.ts.map +1 -1
  254. package/dist/src/helpers/FileManager/IFileManager.d.ts +5 -0
  255. package/dist/src/helpers/FileManager/IFileManager.d.ts.map +1 -1
  256. package/dist/src/helpers/FileManager/errors.d.ts +10 -0
  257. package/dist/src/helpers/FileManager/errors.d.ts.map +1 -0
  258. package/dist/src/helpers/compareMetadataRowsLatestFirst.d.ts +21 -3
  259. package/dist/src/helpers/compareMetadataRowsLatestFirst.d.ts.map +1 -1
  260. package/dist/src/helpers/constants.d.ts +9 -0
  261. package/dist/src/helpers/constants.d.ts.map +1 -1
  262. package/dist/src/helpers/crypto.d.ts +5 -0
  263. package/dist/src/helpers/crypto.d.ts.map +1 -1
  264. package/dist/src/helpers/db.d.ts +25 -5
  265. package/dist/src/helpers/db.d.ts.map +1 -1
  266. package/dist/src/helpers/easPropertyCanonical.d.ts +1 -1
  267. package/dist/src/helpers/easPropertyCanonical.d.ts.map +1 -1
  268. package/dist/src/helpers/entity/entityCommon.d.ts +10 -0
  269. package/dist/src/helpers/entity/entityCommon.d.ts.map +1 -1
  270. package/dist/src/helpers/entity/entityDestroy.d.ts +1 -1
  271. package/dist/src/helpers/entity/entityDestroy.d.ts.map +1 -1
  272. package/dist/src/helpers/entity/entityLiveQuery.d.ts +5 -0
  273. package/dist/src/helpers/entity/entityLiveQuery.d.ts.map +1 -1
  274. package/dist/src/helpers/entity/evictionEpoch.d.ts +18 -0
  275. package/dist/src/helpers/entity/evictionEpoch.d.ts.map +1 -0
  276. package/dist/src/helpers/file/fetchAll/actors.d.ts.map +1 -1
  277. package/dist/src/helpers/getSegmentedItemProperties.d.ts.map +1 -1
  278. package/dist/src/helpers/htmlEmbeddedDataUriPublish.d.ts +31 -3
  279. package/dist/src/helpers/htmlEmbeddedDataUriPublish.d.ts.map +1 -1
  280. package/dist/src/helpers/isPublishedMetadataRow.d.ts +10 -0
  281. package/dist/src/helpers/isPublishedMetadataRow.d.ts.map +1 -0
  282. package/dist/src/helpers/ownership.d.ts.map +1 -1
  283. package/dist/src/helpers/property.d.ts +6 -1
  284. package/dist/src/helpers/property.d.ts.map +1 -1
  285. package/dist/src/helpers/schema.d.ts +14 -4
  286. package/dist/src/helpers/schema.d.ts.map +1 -1
  287. package/dist/src/helpers/tabCoordinator.d.ts +45 -0
  288. package/dist/src/helpers/tabCoordinator.d.ts.map +1 -0
  289. package/dist/src/helpers/tabEvents.d.ts +27 -0
  290. package/dist/src/helpers/tabEvents.d.ts.map +1 -0
  291. package/dist/src/helpers/tabLocks.d.ts +28 -0
  292. package/dist/src/helpers/tabLocks.d.ts.map +1 -0
  293. package/dist/src/helpers/updateSchema.d.ts.map +1 -1
  294. package/dist/src/imports/json.d.ts.map +1 -1
  295. package/dist/src/imports/markdown.d.ts.map +1 -1
  296. package/dist/src/index.d.ts +13 -3
  297. package/dist/src/index.d.ts.map +1 -1
  298. package/dist/src/node/db/Db.d.ts.map +1 -1
  299. package/dist/src/node/helpers/FileManager.d.ts +2 -0
  300. package/dist/src/node/helpers/FileManager.d.ts.map +1 -1
  301. package/dist/src/query/createLocalQueryDataSource.d.ts +4 -0
  302. package/dist/src/query/createLocalQueryDataSource.d.ts.map +1 -1
  303. package/dist/src/seedSchema/MetadataSchema.d.ts +36 -0
  304. package/dist/src/seedSchema/MetadataSchema.d.ts.map +1 -1
  305. package/dist/src/seedSchema/ModelSchema.d.ts +57 -0
  306. package/dist/src/seedSchema/ModelSchema.d.ts.map +1 -1
  307. package/dist/src/seedSchema/SeedSchema.d.ts +19 -0
  308. package/dist/src/seedSchema/SeedSchema.d.ts.map +1 -1
  309. package/dist/src/seedSchema/VersionSchema.d.ts +17 -0
  310. package/dist/src/seedSchema/VersionSchema.d.ts.map +1 -1
  311. package/dist/src/services/write/actors/validateEntity.d.ts.map +1 -1
  312. package/dist/src/services/write/actors/writeToDatabase.d.ts +5 -0
  313. package/dist/src/services/write/actors/writeToDatabase.d.ts.map +1 -1
  314. package/dist/src/services/write/writeProcessMachine.d.ts.map +1 -1
  315. package/dist/src/stores/eas.d.ts +1 -1
  316. package/dist/src/stores/eas.d.ts.map +1 -1
  317. package/dist/src/types/index.d.ts +7 -0
  318. package/dist/src/types/index.d.ts.map +1 -1
  319. package/dist/src/types/item.d.ts +19 -11
  320. package/dist/src/types/item.d.ts.map +1 -1
  321. package/dist/src/types/property.d.ts +3 -2
  322. package/dist/src/types/property.d.ts.map +1 -1
  323. package/dist/syncDbWithEas-BPap9Sru.js +855 -0
  324. package/dist/syncDbWithEas-BPap9Sru.js.map +1 -0
  325. package/dist/{index-280bL-n4.js → tabEvents-C6NXS6Jl.js} +231 -68
  326. package/dist/tabEvents-C6NXS6Jl.js.map +1 -0
  327. package/dist/tolerantWebAccess-NBQ1NB4i.js +160 -0
  328. package/dist/tolerantWebAccess-NBQ1NB4i.js.map +1 -0
  329. package/dist/updateSeedRevokedAt-CYwgVNlM.js +32 -0
  330. package/dist/updateSeedRevokedAt-CYwgVNlM.js.map +1 -0
  331. package/package.json +8 -10
  332. package/dist/BasePathResolver-B6CKqoKw.js.map +0 -1
  333. package/dist/ClientManager-ChcmUL47.js.map +0 -1
  334. package/dist/ModelProperty-D79uV42p.js.map +0 -1
  335. package/dist/Schema-a8uO-hIZ.js.map +0 -1
  336. package/dist/SchemaValidationService-Y49vqast.js.map +0 -1
  337. package/dist/backfillMetadataPropertyIds-CBsf7LnU.js.map +0 -1
  338. package/dist/cjs/ModelProperty-D9FGaGuE.js.map +0 -1
  339. package/dist/cjs/Schema-BiJIPLEL.js.map +0 -1
  340. package/dist/cjs/SchemaValidationService-DtYs_CNY.js.map +0 -1
  341. package/dist/cjs/backfillMetadataPropertyIds-2XaN3b9S.js.map +0 -1
  342. package/dist/cjs/getPublishPayload-D5DhLGvN.js.map +0 -1
  343. package/dist/cjs/getPublishUploads-DMhCCKYF.js.map +0 -1
  344. package/dist/cjs/getSegmentedItemProperties-CU8rtBAo.js +0 -148
  345. package/dist/cjs/getSegmentedItemProperties-CU8rtBAo.js.map +0 -1
  346. package/dist/cjs/index-CSNtEnbF.js.map +0 -1
  347. package/dist/cjs/index-DPp9rdRL.js.map +0 -1
  348. package/dist/cjs/property-DlKiAWt4.js.map +0 -1
  349. package/dist/cjs/syncDbWithEas-C6n34o_Q.js +0 -606
  350. package/dist/cjs/syncDbWithEas-C6n34o_Q.js.map +0 -1
  351. package/dist/db-WbrzEf7c.js.map +0 -1
  352. package/dist/eas-CfYCu4IX.js.map +0 -1
  353. package/dist/getPublishPayload-BIrTVUxb.js.map +0 -1
  354. package/dist/getPublishUploads-C6cDeZdz.js.map +0 -1
  355. package/dist/getSegmentedItemProperties-BmKE71vU.js +0 -148
  356. package/dist/getSegmentedItemProperties-BmKE71vU.js.map +0 -1
  357. package/dist/index-280bL-n4.js.map +0 -1
  358. package/dist/index-ClSv5pTA.js.map +0 -1
  359. package/dist/index-Djd3667v.js.map +0 -1
  360. package/dist/index-HRHkbVQd.js.map +0 -1
  361. package/dist/index.node-DBtQkEfz.js.map +0 -1
  362. package/dist/property-CQjuZ4JW.js.map +0 -1
  363. package/dist/src/Item/service/actors/fetchDataFromEas.d.ts +0 -4
  364. package/dist/src/Item/service/actors/fetchDataFromEas.d.ts.map +0 -1
  365. package/dist/src/Item/service/actors/hydrateExistingItem.d.ts +0 -4
  366. package/dist/src/Item/service/actors/hydrateExistingItem.d.ts.map +0 -1
  367. package/dist/src/Item/service/actors/hydrateNewItem.d.ts +0 -4
  368. package/dist/src/Item/service/actors/hydrateNewItem.d.ts.map +0 -1
  369. package/dist/src/Item/service/actors/initialize.d.ts +0 -4
  370. package/dist/src/Item/service/actors/initialize.d.ts.map +0 -1
  371. package/dist/src/Item/service/actors/reload.d.ts +0 -5
  372. package/dist/src/Item/service/actors/reload.d.ts.map +0 -1
  373. package/dist/src/Item/service/actors/saveDataToDb.d.ts +0 -4
  374. package/dist/src/Item/service/actors/saveDataToDb.d.ts.map +0 -1
  375. package/dist/src/ItemProperty/service/actors/resolveRemoteStorage.d.ts +0 -5
  376. package/dist/src/ItemProperty/service/actors/resolveRemoteStorage.d.ts.map +0 -1
  377. package/dist/src/Schema/service/addModelsMachine.d.ts +0 -123
  378. package/dist/src/Schema/service/addModelsMachine.d.ts.map +0 -1
  379. package/dist/src/browser/helpers/index.d.ts +0 -4
  380. package/dist/src/browser/helpers/index.d.ts.map +0 -1
  381. package/dist/src/db/configs/dev.schema.config.d.ts.map +0 -1
  382. package/dist/src/helpers/file/download/actors.d.ts +0 -17
  383. package/dist/src/helpers/file/download/actors.d.ts.map +0 -1
  384. package/dist/src/helpers/file/download/index.d.ts +0 -70
  385. package/dist/src/helpers/file/download/index.d.ts.map +0 -1
  386. package/dist/src/services/publish/actors/createPublishAttempt.d.ts +0 -4
  387. package/dist/src/services/publish/actors/createPublishAttempt.d.ts.map +0 -1
  388. package/dist/src/services/publish/actors/preparePublishRequestData.d.ts +0 -4
  389. package/dist/src/services/publish/actors/preparePublishRequestData.d.ts.map +0 -1
  390. package/dist/src/services/publish/actors/upload.d.ts +0 -4
  391. package/dist/src/services/publish/actors/upload.d.ts.map +0 -1
  392. package/dist/src/services/publish/actors/validateItemData.d.ts +0 -4
  393. package/dist/src/services/publish/actors/validateItemData.d.ts.map +0 -1
  394. package/dist/src/services/publish/publishMachine.d.ts +0 -32
  395. package/dist/src/services/publish/publishMachine.d.ts.map +0 -1
  396. package/dist/syncDbWithEas-CuARXfyN.js +0 -598
  397. package/dist/syncDbWithEas-CuARXfyN.js.map +0 -1
  398. package/dist/updateSeedRevokedAt-CBcLfHWs.js +0 -22
  399. package/dist/updateSeedRevokedAt-CBcLfHWs.js.map +0 -1
@@ -0,0 +1,4763 @@
1
+ import debug from 'debug';
2
+ import { createActor, setup, assign, fromPromise, fromCallback, waitFor } from 'xstate';
3
+ import { m as eventEmitter, C as normalizeAddressConfig, E as normalizeAddressList, S as loadModelsFromDbForSchema, O as addSchemaToDb, n as normalizeDataType, U as linkModelToSchema, s as normalizePublisher, V as getWatchedAddressesFromDb, W as getOwnedAddressesFromDb } from './db-Lw0KVZmc.js';
4
+ import { u as MachineIds, W as ClientManagerEvents, C as ClientManagerState, X as resolvePublisherFromSeedRow, G as isLocalUnsealedDraft, I as Item } from './Item-ZUAmf943.js';
5
+ import { B as BaseDb, e as easSyncProcesses, t as BasePathResolver, b as appState, a as BaseFileManager, j as models$1, i as modelUids, r as seeds, s as schemas, K as isInternalSchema, L as INTERNAL_SCHEMA_IDS, F as SEED_PROTOCOL_SCHEMA_NAME, g as modelSchemas, m as metadata$1, v as versions, h as htmlEmbeddedImageCoPublish, c as arweaveL1FinalizeJobs, q as publishProcesses, M as CLIENT_NOT_INITIALIZED } from './BasePathResolver-DRT09oVv.js';
6
+ import { eq, inArray, and, isNotNull, ne, desc, countDistinct, or, isNull } from 'drizzle-orm';
7
+ import { o as withDbTabLock, j as isLeaderTab, v as postTabMessage, x as onTabMessage, w as whenLeaderTab, y as listenForOtherTabsEvents, z as isNode, B as isBrowser, C as startTabCoordination, D as downloadAllFilesRequestHandler, E as downloadAllFilesBinaryRequestHandler, t as toSnakeCase, u as withSeedDbLock, p as parseAddressesPersistedPayload, e as emitAcrossTabs, A as ADDRESSES_PERSISTED_EVENT, L as LOCAL_COPIES_REMOVED_EVENT } from './tabEvents-C6NXS6Jl.js';
8
+ import { seedGatewayConfigFromSeedConfig, setPreferredArweaveReadBaseUrls, resolveSeedGatewayEndpoints, setResolvedSeedGatewayEndpoints, BaseArweaveClient, ensureReadGatewaySelected, DEFAULT_ARWEAVE_HOST } from '@seedprotocol/arweave';
9
+ import { configureEasReadChain, BaseQueryClient, BaseEasClient, GET_SCHEMAS, whenEasReadChainSettled, getEasReadChainId, DEFAULT_EAS_CHAIN_ID, isEasReadChainConfigured, isValidEasAttestationUid } from '@seedprotocol/eas';
10
+ import 'pluralize';
11
+ import { sha3_256 } from '@noble/hashes/sha3';
12
+ import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
13
+ import { generateId } from './generateId-BopUwD8F.js';
14
+ import { clearLocalQuerySource, registerLocalQuerySource, getSeed } from '@seedprotocol/query';
15
+ import { startCase } from 'lodash-es';
16
+
17
+ let writeTail = Promise.resolve();
18
+ function enqueueEasSyncDbWrite(fn) {
19
+ const next = writeTail.then(fn);
20
+ writeTail = next.catch(() => { });
21
+ return next;
22
+ }
23
+ const MAX_ERROR_MESSAGE_LENGTH = 500;
24
+ const MAX_ERROR_DETAILS_LENGTH = 2000;
25
+ async function insertEasSyncProcessRow(params) {
26
+ return enqueueEasSyncDbWrite(async () => {
27
+ const db = BaseDb.getAppDb();
28
+ if (!db) {
29
+ throw new Error('[easSyncProcess] App DB not available');
30
+ }
31
+ const now = Date.now();
32
+ const [row] = await db
33
+ .insert(easSyncProcesses)
34
+ .values({
35
+ status: 'in_progress',
36
+ startedAt: now,
37
+ requestPayload: JSON.stringify(params.requestPayload),
38
+ persistedSnapshot: JSON.stringify(params.persistedSnapshot),
39
+ createdAt: now,
40
+ updatedAt: now,
41
+ })
42
+ .returning({ id: easSyncProcesses.id });
43
+ return row.id;
44
+ });
45
+ }
46
+ async function finalizeEasSyncProcessRow(id, params) {
47
+ return enqueueEasSyncDbWrite(async () => {
48
+ const db = BaseDb.getAppDb();
49
+ if (!db) {
50
+ return;
51
+ }
52
+ const now = Date.now();
53
+ await db
54
+ .update(easSyncProcesses)
55
+ .set({
56
+ status: params.status,
57
+ completedAt: now,
58
+ updatedAt: now,
59
+ errorMessage: params.errorMessage?.slice(0, MAX_ERROR_MESSAGE_LENGTH) ?? null,
60
+ errorDetails: params.errorDetails?.slice(0, MAX_ERROR_DETAILS_LENGTH) ?? null,
61
+ persistedSnapshot: JSON.stringify(params.persistedSnapshot),
62
+ })
63
+ .where(eq(easSyncProcesses.id, id));
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Merge multiple EAS sync intents: full sync (no concrete address list) wins;
69
+ * otherwise union addresses (first-seen casing).
70
+ */
71
+ function mergeEasSyncRequestIntents(parts) {
72
+ const norm = parts.filter((p) => p != null);
73
+ if (norm.length === 0) {
74
+ return {};
75
+ }
76
+ let full = false;
77
+ const addressMap = new Map();
78
+ for (const p of norm) {
79
+ if (!('addresses' in p) || p.addresses === undefined) {
80
+ full = true;
81
+ break;
82
+ }
83
+ for (const addr of p.addresses) {
84
+ const k = addr.toLowerCase();
85
+ if (!addressMap.has(k)) {
86
+ addressMap.set(k, addr);
87
+ }
88
+ }
89
+ }
90
+ if (full) {
91
+ return {};
92
+ }
93
+ if (addressMap.size === 0) {
94
+ return { addresses: [] };
95
+ }
96
+ return { addresses: [...addressMap.values()] };
97
+ }
98
+
99
+ const log = debug('seedSdk:events:easSyncManager');
100
+ function toRunSyncArg(options) {
101
+ if (options &&
102
+ 'addresses' in options &&
103
+ options.addresses !== undefined) {
104
+ return options;
105
+ }
106
+ return undefined;
107
+ }
108
+ const runEasSync = fromPromise(async ({ input, }) => {
109
+ const rowId = await insertEasSyncProcessRow({
110
+ requestPayload: input.requestPayload,
111
+ persistedSnapshot: input.runningSnapshot,
112
+ });
113
+ const { runSyncFromEas } = await import('./syncDbWithEas-BPap9Sru.js');
114
+ try {
115
+ // Sync checks for rows and then inserts the rest (no unique index on uids), so syncs in
116
+ // different tabs take turns. A sync can run for minutes, so wait without a timeout.
117
+ await withDbTabLock('eas-sync', () => runSyncFromEas(toRunSyncArg(input.options)), {
118
+ timeoutMs: Infinity,
119
+ });
120
+ await finalizeEasSyncProcessRow(rowId, {
121
+ status: 'completed',
122
+ persistedSnapshot: {
123
+ phase: 'completed',
124
+ correlationIds: input.requestPayload.correlationIds,
125
+ },
126
+ });
127
+ }
128
+ catch (err) {
129
+ const message = err instanceof Error ? err.message : String(err);
130
+ const details = err instanceof Error && err.stack
131
+ ? err.stack.slice(0, 2000)
132
+ : undefined;
133
+ await finalizeEasSyncProcessRow(rowId, {
134
+ status: 'failed',
135
+ errorMessage: message,
136
+ errorDetails: details,
137
+ persistedSnapshot: {
138
+ phase: 'failed',
139
+ correlationIds: input.requestPayload.correlationIds,
140
+ },
141
+ });
142
+ throw err;
143
+ }
144
+ });
145
+ const initialContext = {
146
+ runOptions: {},
147
+ activeCorrelationIds: [],
148
+ activePrimarySource: 'event_bus',
149
+ pendingMergedOptions: undefined,
150
+ pendingCorrelationIds: [],
151
+ pendingPrimarySource: null,
152
+ lastFinishedCorrelationIds: null,
153
+ lastFinishedStatus: null,
154
+ lastFinishedError: null,
155
+ };
156
+ const easSyncMachine = setup({
157
+ types: {
158
+ context: {},
159
+ events: {},
160
+ },
161
+ actors: {
162
+ runEasSync,
163
+ },
164
+ }).createMachine({
165
+ id: MachineIds.EAS_SYNC_MANAGER,
166
+ initial: 'idle',
167
+ context: initialContext,
168
+ states: {
169
+ idle: {
170
+ on: {
171
+ REQUEST: {
172
+ target: 'running',
173
+ actions: assign({
174
+ runOptions: ({ event }) => event.options === undefined
175
+ ? {}
176
+ : mergeEasSyncRequestIntents([event.options]),
177
+ activeCorrelationIds: ({ event }) => [event.correlationId],
178
+ activePrimarySource: ({ event }) => event.source,
179
+ }),
180
+ },
181
+ },
182
+ },
183
+ running: {
184
+ on: {
185
+ REQUEST: {
186
+ actions: assign({
187
+ pendingMergedOptions: ({ context, event }) => mergeEasSyncRequestIntents([
188
+ context.runOptions,
189
+ context.pendingMergedOptions,
190
+ event.options === undefined ? {} : event.options,
191
+ ].filter((x) => x != null)),
192
+ pendingCorrelationIds: ({ context, event }) => [
193
+ ...context.pendingCorrelationIds,
194
+ event.correlationId,
195
+ ],
196
+ pendingPrimarySource: ({ context, event }) => context.pendingPrimarySource ?? event.source,
197
+ }),
198
+ },
199
+ },
200
+ invoke: {
201
+ src: 'runEasSync',
202
+ input: ({ context }) => ({
203
+ options: context.runOptions,
204
+ requestPayload: {
205
+ source: context.activePrimarySource,
206
+ correlationIds: context.activeCorrelationIds,
207
+ options: context.runOptions,
208
+ },
209
+ runningSnapshot: {
210
+ phase: 'running',
211
+ correlationIds: context.activeCorrelationIds,
212
+ },
213
+ }),
214
+ onDone: {
215
+ target: 'afterRun',
216
+ actions: assign({
217
+ lastFinishedCorrelationIds: ({ context }) => [
218
+ ...context.activeCorrelationIds,
219
+ ],
220
+ lastFinishedStatus: () => 'success',
221
+ lastFinishedError: () => null,
222
+ activeCorrelationIds: () => [],
223
+ runOptions: () => ({}),
224
+ }),
225
+ },
226
+ onError: {
227
+ target: 'afterRun',
228
+ actions: [
229
+ assign({
230
+ lastFinishedCorrelationIds: ({ context }) => [
231
+ ...context.activeCorrelationIds,
232
+ ],
233
+ lastFinishedStatus: () => 'failed',
234
+ lastFinishedError: ({ event }) => {
235
+ const e = event.error;
236
+ if (e instanceof Error) {
237
+ return e.message;
238
+ }
239
+ return e != null ? String(e) : 'EAS sync failed';
240
+ },
241
+ activeCorrelationIds: () => [],
242
+ runOptions: () => ({}),
243
+ }),
244
+ ({ event }) => {
245
+ log('runEasSync error', event.error);
246
+ },
247
+ ],
248
+ },
249
+ },
250
+ },
251
+ afterRun: {
252
+ always: [
253
+ {
254
+ guard: ({ context }) => context.pendingCorrelationIds.length > 0,
255
+ target: 'running',
256
+ actions: assign({
257
+ runOptions: ({ context }) => mergeEasSyncRequestIntents([context.pendingMergedOptions]),
258
+ activeCorrelationIds: ({ context }) => [
259
+ ...context.pendingCorrelationIds,
260
+ ],
261
+ activePrimarySource: ({ context }) => context.pendingPrimarySource ?? 'event_bus',
262
+ pendingMergedOptions: () => undefined,
263
+ pendingCorrelationIds: () => [],
264
+ pendingPrimarySource: () => null,
265
+ }),
266
+ },
267
+ { target: 'idle' },
268
+ ],
269
+ },
270
+ },
271
+ });
272
+ const easSyncActor = createActor(easSyncMachine);
273
+ let easSyncActorStarted = false;
274
+ function startEasSyncActor() {
275
+ if (easSyncActorStarted) {
276
+ return;
277
+ }
278
+ easSyncActorStarted = true;
279
+ easSyncActor.start();
280
+ }
281
+ function newCorrelationId() {
282
+ const c = globalThis.crypto;
283
+ if (c?.randomUUID) {
284
+ return c.randomUUID();
285
+ }
286
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
287
+ }
288
+ /** Requests deferred in this tab until it leads, by source; each runs once on takeover. */
289
+ const deferredUntilLeader = new Set();
290
+ /**
291
+ * Automatic syncs run in the leader tab only (docs/MULTI_TAB.md). A tab that skips one runs it if
292
+ * it later becomes leader, so closing the leader doesn't lose the catch-up sync.
293
+ */
294
+ function runInLeaderTab(source, request) {
295
+ if (isLeaderTab()) {
296
+ request();
297
+ return;
298
+ }
299
+ log(`${source} sync skipped: another tab leads`);
300
+ if (deferredUntilLeader.has(source))
301
+ return;
302
+ deferredUntilLeader.add(source);
303
+ void whenLeaderTab().then(() => {
304
+ deferredUntilLeader.delete(source);
305
+ request();
306
+ });
307
+ }
308
+ let listeningToOtherTabs = false;
309
+ /** The leader runs address-change syncs requested by other tabs. */
310
+ function listenForEasSyncRequestsFromOtherTabs() {
311
+ if (listeningToOtherTabs)
312
+ return;
313
+ listeningToOtherTabs = true;
314
+ onTabMessage((message) => {
315
+ if (message.type === 'eas-sync-address-change' && isLeaderTab()) {
316
+ requestEasSyncFromAddressChange(message.addresses);
317
+ }
318
+ });
319
+ }
320
+ function requestEasSyncFromEventBus() {
321
+ runInLeaderTab('event_bus', sendEventBusRequest);
322
+ }
323
+ function sendEventBusRequest() {
324
+ startEasSyncActor();
325
+ easSyncActor.send({
326
+ type: 'REQUEST',
327
+ correlationId: newCorrelationId(),
328
+ source: 'event_bus',
329
+ });
330
+ }
331
+ function requestEasSyncFromAddressChange(addresses) {
332
+ if (!isLeaderTab()) {
333
+ // The leader syncs these addresses; its own address list may be stale.
334
+ postTabMessage({ type: 'eas-sync-address-change', addresses });
335
+ return;
336
+ }
337
+ startEasSyncActor();
338
+ easSyncActor.send({
339
+ type: 'REQUEST',
340
+ correlationId: newCorrelationId(),
341
+ options: { addresses },
342
+ source: 'address_change',
343
+ });
344
+ }
345
+ function requestEasSyncFromModelsInit() {
346
+ runInLeaderTab('models_init', sendModelsInitRequest);
347
+ }
348
+ function sendModelsInitRequest() {
349
+ startEasSyncActor();
350
+ easSyncActor.send({
351
+ type: 'REQUEST',
352
+ correlationId: newCorrelationId(),
353
+ source: 'models_init',
354
+ });
355
+ }
356
+ function sendEasSyncClientRequest(correlationId, options) {
357
+ startEasSyncActor();
358
+ easSyncActor.send({
359
+ type: 'REQUEST',
360
+ correlationId,
361
+ options,
362
+ source: 'client_api',
363
+ });
364
+ }
365
+
366
+ var easSyncManager = /*#__PURE__*/Object.freeze({
367
+ __proto__: null,
368
+ easSyncActor: easSyncActor,
369
+ easSyncMachine: easSyncMachine,
370
+ listenForEasSyncRequestsFromOtherTabs: listenForEasSyncRequestsFromOtherTabs,
371
+ requestEasSyncFromAddressChange: requestEasSyncFromAddressChange,
372
+ requestEasSyncFromEventBus: requestEasSyncFromEventBus,
373
+ requestEasSyncFromModelsInit: requestEasSyncFromModelsInit,
374
+ sendEasSyncClientRequest: sendEasSyncClientRequest,
375
+ startEasSyncActor: startEasSyncActor
376
+ });
377
+
378
+ const logger$b = debug('seedSdk:services:events');
379
+ const handleServiceSaveState = (event) => {
380
+ const { state, serviceId } = event;
381
+ logger$b(`[browser] [service.saveState.request] serviceId: ${serviceId}`);
382
+ // TODO: This is called from Node as well. Do we need it? If so, need to generalize this to all platforms.
383
+ // localStorage.setItem(`seed_sdk_service_${serviceId}`, JSON.stringify(state))
384
+ };
385
+ const setupServicesEventHandlers = () => {
386
+ eventEmitter.addListener('service.saveState.request', handleServiceSaveState);
387
+ };
388
+
389
+ const setupAllItemsEventHandlers = () => {
390
+ startEasSyncActor();
391
+ listenForEasSyncRequestsFromOtherTabs();
392
+ listenForOtherTabsEvents();
393
+ eventEmitter.addListener('syncDbWithEas', requestEasSyncFromEventBus);
394
+ };
395
+ // Note: getAreItemEventHandlersReady removed - was only used by useItemIsReady hook which has been removed
396
+
397
+ // AllItems services have been removed - this handler is no longer needed
398
+ // Service state persistence is now handled by entity instances directly
399
+ const saveServiceHandler = async (event) => {
400
+ // AllItems services removed - this functionality is no longer needed
401
+ // Entity instances (Schema, Model, Item) manage their own state
402
+ console.warn('[saveServiceHandler] AllItems services removed - handler is a no-op');
403
+ };
404
+
405
+ const setupServiceHandlers = () => {
406
+ eventEmitter.addListener('service.save', saveServiceHandler);
407
+ };
408
+
409
+ /**
410
+ * On Node, storage uses `filesDir`. `endpoints.filePaths` and `endpoints.files` are
411
+ * required by init but have no separate Node consumer, so fill them from `filesDir`.
412
+ * Browser configs are left unchanged.
413
+ */
414
+ function normalizeSeedConfigEndpoints(config) {
415
+ if (!config)
416
+ return config;
417
+ const endpoints = config.endpoints;
418
+ if (endpoints?.filePaths && endpoints?.files)
419
+ return config;
420
+ if (!(isNode() && !isBrowser()))
421
+ return config;
422
+ const filesDir = config.filesDir;
423
+ if (!filesDir)
424
+ return config;
425
+ const next = {
426
+ filePaths: endpoints?.filePaths || filesDir,
427
+ files: endpoints?.files || filesDir,
428
+ };
429
+ return { ...config, endpoints: next };
430
+ }
431
+
432
+ const logger$a = debug('seedSdk:ClientManager:initialize');
433
+ const platformClassesInit = fromCallback(({ sendBack, input: { context, event } }) => {
434
+ logger$a('initialize from ClientManager');
435
+ const { isInitialized } = context;
436
+ if (!event || !('options' in event)) {
437
+ throw new Error('Initialize event must include options');
438
+ }
439
+ const { options } = event;
440
+ if (isInitialized) {
441
+ sendBack({ type: 'initialized' });
442
+ return;
443
+ }
444
+ // Validate synchronously before starting async operations
445
+ // This ensures errors are handled immediately, preventing unhandled rejections
446
+ const { config } = options;
447
+ const { endpoints } = config || {};
448
+ // Track if the operation is cancelled
449
+ let cancelled = false;
450
+ // Create a promise that will handle validation errors immediately
451
+ // We use Promise.resolve().then() to ensure the catch handler is attached synchronously
452
+ // before the async function starts executing, preventing unhandled rejections
453
+ const initPromise = Promise.resolve().then(async () => {
454
+ // Check if cancelled before starting
455
+ if (cancelled) {
456
+ return;
457
+ }
458
+ const { addresses } = options;
459
+ const config = normalizeSeedConfigEndpoints(options.config);
460
+ // Validate required endpoints - this should happen early in the initialization process.
461
+ // Node fills these from filesDir; browser callers still pass them.
462
+ if (!config?.endpoints || !config.endpoints.filePaths || !config.endpoints.files) {
463
+ throw new Error('Config must include endpoints with filePaths and files');
464
+ }
465
+ // Prefer the condition-resolved factory when available; fall back to explicit
466
+ // platform imports so source/vitest resolution stays reliable.
467
+ const { configurePlatform } = await import('./configurePlatform-CZpjyuCo.js');
468
+ if (isBrowser()) {
469
+ const { createPlatformServices } = await import('./platform.browser.js');
470
+ configurePlatform(createPlatformServices());
471
+ }
472
+ else if (isNode()) {
473
+ const { createPlatformServices } = await import('./platform.node.js');
474
+ configurePlatform(createPlatformServices());
475
+ }
476
+ else {
477
+ throw new Error(`Unable to determine environment. isBrowser()=${isBrowser()}, isNode()=${isNode()}. Platform-specific implementations could not be loaded.`);
478
+ }
479
+ // Check if cancelled after async imports
480
+ if (cancelled) {
481
+ return;
482
+ }
483
+ const { models, endpoints, arweaveDomain, dbConfig, filesDir, schemaFile, schema, gateway, uploadApiBaseUrl, eas } = config;
484
+ if (eas) {
485
+ configureEasReadChain('sdk', { chainId: eas.chainId, indexerUrl: eas.indexerUrl });
486
+ }
487
+ const gatewayConfig = seedGatewayConfigFromSeedConfig({
488
+ ...config,
489
+ arweaveDomain,
490
+ uploadApiBaseUrl,
491
+ gateway,
492
+ });
493
+ // Only when configured, so a host app's own setPreferredArweaveReadBaseUrls() call survives init.
494
+ if (gatewayConfig.preferredReadBaseUrls) {
495
+ setPreferredArweaveReadBaseUrls(gatewayConfig.preferredReadBaseUrls);
496
+ }
497
+ const resolvedGateway = await resolveSeedGatewayEndpoints(gatewayConfig);
498
+ setResolvedSeedGatewayEndpoints(resolvedGateway);
499
+ BaseArweaveClient.setHost(`${resolvedGateway.arweaveProtocol}://${resolvedGateway.arweaveHost}`);
500
+ // Note: Validation already happened above, but we have endpoints here for path normalization
501
+ // Normalize filesDir for Node.js environment
502
+ // In Node.js, filesDir should be resolved relative to .seed directory at project root
503
+ let normalizedFilesDir = filesDir || endpoints?.files;
504
+ // jsdom (and any env with both Node + window) is "browser" for SDK: PathResolver is OPFS/virtual.
505
+ // Do not run real fs against those paths (e.g. mkdir '/.seed').
506
+ if (isNode() && !isBrowser() && normalizedFilesDir) {
507
+ const path = (await import('node:path')).default;
508
+ const fs = (await import('node:fs')).default;
509
+ const dotSeedDir = BasePathResolver.getDotSeedDir();
510
+ // Ensure .seed directory exists
511
+ if (!fs.existsSync(dotSeedDir)) {
512
+ fs.mkdirSync(dotSeedDir, { recursive: true });
513
+ }
514
+ // Check if filesDir is a browser OPFS-style path (absolute path starting with '/'
515
+ // that is a simple single-level path like '/app-files', not a real filesystem path)
516
+ // Browser OPFS paths are typically simple like '/app-files', '/files', etc.
517
+ const pathWithoutLeadingSlash = normalizedFilesDir.slice(1);
518
+ const isBrowserOpfsPath = path.isAbsolute(normalizedFilesDir) &&
519
+ normalizedFilesDir.startsWith('/') &&
520
+ pathWithoutLeadingSlash.length > 0 &&
521
+ !pathWithoutLeadingSlash.includes('/') && // Single directory name, no subdirectories
522
+ !fs.existsSync(normalizedFilesDir); // Doesn't exist on filesystem
523
+ if (isBrowserOpfsPath) {
524
+ // Convert browser OPFS path to .seed subdirectory
525
+ // e.g., '/app-files' -> '.seed/app-files'
526
+ const dirName = normalizedFilesDir.slice(1) || 'app-files';
527
+ normalizedFilesDir = path.join(dotSeedDir, dirName);
528
+ }
529
+ else if (!path.isAbsolute(normalizedFilesDir)) {
530
+ // If it's a relative path, check if it already starts with .seed
531
+ if (normalizedFilesDir.startsWith('.seed') || normalizedFilesDir.startsWith('.seed/')) {
532
+ // Already relative to project root, resolve it
533
+ normalizedFilesDir = path.resolve(normalizedFilesDir);
534
+ }
535
+ else {
536
+ // Resolve relative to .seed directory
537
+ normalizedFilesDir = path.join(dotSeedDir, normalizedFilesDir);
538
+ }
539
+ }
540
+ // If it's an absolute path that exists or is a valid filesystem path, use it as-is
541
+ }
542
+ const normalizedAddresses = normalizeAddressConfig(addresses);
543
+ sendBack({ type: 'updateContext', context: {
544
+ models: models || {},
545
+ endpoints,
546
+ arweaveDomain: resolvedGateway.arweaveHost,
547
+ uploadApiBaseUrl: resolvedGateway.uploadApiBaseUrl,
548
+ gatewayTransport: resolvedGateway.mode,
549
+ gatewayHyperKey: resolvedGateway.gatewayHyperKey,
550
+ gatewaySidecarHost: gatewayConfig.hyper?.localSidecarHost,
551
+ gatewaySidecarPort: gatewayConfig.hyper?.localSidecarPort,
552
+ gatewayProxyBaseUrl: resolvedGateway.proxyBaseUrl ?? gatewayConfig.proxyBaseUrl,
553
+ addresses: normalizedAddresses.owned,
554
+ ownedAddresses: normalizedAddresses.owned,
555
+ watchedAddresses: normalizedAddresses.watched,
556
+ filesDir: normalizedFilesDir,
557
+ dbConfig,
558
+ schemaFile,
559
+ schema,
560
+ syncFromEasOnAddressChange: options.syncFromEasOnAddressChange ?? true,
561
+ } });
562
+ // Elect a leader tab for background work before anything requests it (docs/MULTI_TAB.md).
563
+ if (normalizedFilesDir) {
564
+ startTabCoordination({ filesDir: normalizedFilesDir, mode: options.multiTab });
565
+ }
566
+ if (resolvedGateway.activePath !== 'hyper-sidecar' && resolvedGateway.activePath !== 'http-proxy') {
567
+ void ensureReadGatewaySelected().catch(() => {
568
+ /* non-blocking warm-up for read gateway probe */
569
+ });
570
+ }
571
+ // Models are now Model instances, no registration needed
572
+ // They should be created via Model.create() and are accessible via Model static methods
573
+ setupAllItemsEventHandlers();
574
+ setupServicesEventHandlers();
575
+ setupServiceHandlers();
576
+ });
577
+ // Handle the promise immediately - this ensures rejections are caught synchronously
578
+ // The catch handler is attached synchronously before the async function executes
579
+ initPromise
580
+ .then(() => {
581
+ if (!cancelled) {
582
+ sendBack({ type: 'platformClassesReady' });
583
+ }
584
+ })
585
+ .catch((error) => {
586
+ // Only send error if not cancelled (prevents unhandled rejections after cleanup)
587
+ if (!cancelled) {
588
+ // Send error event using sendBack - this is the recommended XState pattern
589
+ // The parent state machine should handle the 'error' event
590
+ sendBack({
591
+ type: 'error',
592
+ error: error instanceof Error ? error : new Error(String(error))
593
+ });
594
+ }
595
+ // Explicitly handle the rejection to prevent unhandled promise rejection warnings
596
+ // The error has been sent via sendBack, so we've handled it
597
+ });
598
+ // Return cleanup function to cancel pending operations
599
+ return () => {
600
+ cancelled = true;
601
+ // The promise will still resolve/reject, but we won't send events if cancelled
602
+ // This prevents unhandled rejections after the callback is cleaned up
603
+ };
604
+ });
605
+
606
+ const logger$9 = debug('seedSdk:client:writeToDb');
607
+ const saveAppState = fromCallback(({ sendBack, input: { key, value } }) => {
608
+ const _saveAppState = async () => {
609
+ try {
610
+ const appDb = BaseDb.getAppDb();
611
+ if (!appDb) {
612
+ // In test environments, continue anyway
613
+ if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
614
+ logger$9('[client/actors] [saveAppState] App DB not found, but continuing in test environment');
615
+ return;
616
+ }
617
+ throw new Error('App DB not found');
618
+ }
619
+ const result = await appDb.insert(appState)
620
+ .values({
621
+ key: key,
622
+ value: JSON.stringify(value),
623
+ })
624
+ .onConflictDoUpdate({
625
+ target: appState.key,
626
+ set: {
627
+ value: JSON.stringify(value),
628
+ },
629
+ });
630
+ logger$9('result', result);
631
+ }
632
+ catch (error) {
633
+ logger$9('[client/actors] [saveAppState] Error saving app state:', error);
634
+ // In test environments, continue anyway
635
+ if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
636
+ logger$9('[client/actors] [saveAppState] Continuing despite error in test environment');
637
+ return;
638
+ }
639
+ throw error;
640
+ }
641
+ };
642
+ _saveAppState()
643
+ .then(() => {
644
+ sendBack({
645
+ type: 'saveAppStateSuccess',
646
+ key,
647
+ value,
648
+ });
649
+ })
650
+ .catch((error) => {
651
+ logger$9('[client/actors] [saveAppState] Error in promise chain:', error);
652
+ // In test environments, still send success to allow state machine to progress
653
+ if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
654
+ logger$9('[client/actors] [saveAppState] Sending success despite error in test environment');
655
+ sendBack({
656
+ type: 'saveAppStateSuccess',
657
+ key,
658
+ value,
659
+ });
660
+ }
661
+ else {
662
+ sendBack({
663
+ type: 'saveAppStateError',
664
+ error: error instanceof Error ? error : new Error(String(error)),
665
+ key,
666
+ value,
667
+ });
668
+ }
669
+ });
670
+ });
671
+
672
+ let isInitialized = false;
673
+ const fsInitHandler = async () => {
674
+ if (isInitialized) {
675
+ return;
676
+ }
677
+ isInitialized = true;
678
+ };
679
+ let areReady = false;
680
+ const setupFsListeners = () => {
681
+ eventEmitter.addListener('fs.downloadAll.request', downloadAllFilesRequestHandler);
682
+ eventEmitter.addListener('fs.downloadAllBinary.request', downloadAllFilesBinaryRequestHandler);
683
+ eventEmitter.addListener('fs.init', fsInitHandler);
684
+ areReady = true;
685
+ };
686
+ const areFsListenersReady = () => {
687
+ return areReady;
688
+ };
689
+
690
+ debug('seedSdk:client:actors:fileSystemInit');
691
+ const fileSystemInit = fromCallback(({ sendBack, input: { context }, }) => {
692
+ setupFsListeners();
693
+ if (!areFsListenersReady()) {
694
+ throw new Error('fs listeners not ready during init');
695
+ }
696
+ // Check for filesDir synchronously before starting async operation
697
+ // This ensures the error is thrown synchronously and caught by XState
698
+ const { filesDir } = context;
699
+ if (!filesDir) {
700
+ throw new Error('filesDir is required');
701
+ }
702
+ const _fileSystemInit = async () => {
703
+ await BaseFileManager.initializeFileSystem(filesDir);
704
+ eventEmitter.emit('fs.init');
705
+ };
706
+ _fileSystemInit()
707
+ .then(() => {
708
+ sendBack({ type: 'fileSystemReady' });
709
+ })
710
+ .catch((error) => {
711
+ // Send error event using sendBack - this is the recommended XState pattern
712
+ // The parent state machine should handle the 'ERROR' event
713
+ sendBack({
714
+ type: 'error',
715
+ error: error instanceof Error ? error : new Error(String(error))
716
+ });
717
+ });
718
+ // Return cleanup function
719
+ return () => {
720
+ // Cleanup if needed
721
+ };
722
+ });
723
+
724
+ const logger$8 = debug('seedSdk:client:actors:dbInit');
725
+ const dbInit = fromCallback(({ sendBack, input: { context } }) => {
726
+ const _dbInit = async () => {
727
+ const { filesDir } = context;
728
+ if (!filesDir) {
729
+ throw new Error('filesDir is required');
730
+ }
731
+ // Prepare databases - this handles all initialization, migration, and file setup
732
+ await BaseDb.prepareDb(filesDir);
733
+ // Verify database is ready
734
+ const appDb = BaseDb.getAppDb();
735
+ if (!appDb) {
736
+ throw new Error('Database not available after preparation');
737
+ }
738
+ logger$8('[client/actors] [dbInit] Database prepared and ready');
739
+ };
740
+ _dbInit()
741
+ .then(() => {
742
+ sendBack({ type: ClientManagerEvents.DB_READY });
743
+ })
744
+ .catch((error) => {
745
+ logger$8('Error in dbInit:', error);
746
+ sendBack({
747
+ type: 'error',
748
+ error: error instanceof Error ? error : new Error(String(error))
749
+ });
750
+ });
751
+ });
752
+
753
+ const logger$7 = debug('seedSdk:client:actors:addModelsToStore');
754
+ const addModelsToStore = fromCallback(({ sendBack, input: { context } }) => {
755
+ const _addModelsToStore = async () => {
756
+ let { models } = context;
757
+ if (!models) {
758
+ models = {};
759
+ }
760
+ // Internal models (Seed, Version, Metadata, Image) are now loaded via SEEDPROTOCOL_Seed_Protocol_v1.json schema
761
+ // They should already be in context.models from processSchemaFiles
762
+ const allModels = { ...models };
763
+ sendBack({ type: ClientManagerEvents.UPDATE_CONTEXT, context: { models: allModels } });
764
+ // Models are now Model instances, no registration needed
765
+ // They should be created via Model.create() and are accessible via Model static methods
766
+ for (const [key] of Object.entries(allModels)) {
767
+ logger$7('model available:', key);
768
+ }
769
+ };
770
+ _addModelsToStore()
771
+ .then(() => {
772
+ sendBack({ type: ClientManagerEvents.ADD_MODELS_TO_STORE_SUCCESS });
773
+ })
774
+ .catch((error) => {
775
+ logger$7('[client/actors] [addModelsToStore] Error:', error);
776
+ sendBack({
777
+ type: 'error',
778
+ error: error instanceof Error ? error : new Error(String(error)),
779
+ });
780
+ });
781
+ });
782
+
783
+ const logger$6 = debug('seedSdk:client:actors:addModelsToDb');
784
+ /**
785
+ * Returns every `models` row named in `modelNames`, inserting a null-schemaFileId stub for each name
786
+ * with no row yet (the schema import adopts stubs via findOrCreateModelRecord). Check-then-insert:
787
+ * another tab initializing at the same time would insert the same stubs, so tabs take turns
788
+ * (docs/MULTI_TAB.md).
789
+ */
790
+ async function ensureModelStubs(appDb, modelNames, filesDir) {
791
+ return withSeedDbLock('init', filesDir, async () => {
792
+ // One query for all names. Model names are only unique per schema, so callers match each
793
+ // Model by its id (schemaFileId) first.
794
+ const rows = (await appDb
795
+ .select({ id: models$1.id, name: models$1.name, schemaFileId: models$1.schemaFileId })
796
+ .from(models$1)
797
+ .where(inArray(models$1.name, modelNames)));
798
+ const modelsToInsert = modelNames.filter((name) => !rows.some((m) => m.name === name));
799
+ if (modelsToInsert.length > 0) {
800
+ const newlyInserted = (await appDb
801
+ .insert(models$1)
802
+ .values(modelsToInsert.map((name) => ({ name })))
803
+ .returning({ id: models$1.id, name: models$1.name, schemaFileId: models$1.schemaFileId }));
804
+ rows.push(...newlyInserted);
805
+ for (const name of modelsToInsert) {
806
+ logger$6('[client/actors] [addModelsToDb] inserted model:', name);
807
+ }
808
+ }
809
+ return rows;
810
+ });
811
+ }
812
+ const addModelsToDb = fromCallback(({ sendBack, input: { context } }) => {
813
+ const { models } = context;
814
+ const _addModelsToDb = async () => {
815
+ const appDb = BaseDb.getAppDb();
816
+ if (!appDb) {
817
+ throw new Error('Database not ready');
818
+ }
819
+ if (!models) {
820
+ return;
821
+ }
822
+ // Internal models (Seed, Version, Metadata) are now loaded via SEEDPROTOCOL_Seed_Protocol_v1.json schema
823
+ // They should already be in context.models from processSchemaFiles
824
+ const allModels = { ...models };
825
+ const modelNames = Object.keys(allModels);
826
+ if (modelNames.length === 0) {
827
+ return;
828
+ }
829
+ const schemaDefsByModelName = new Map();
830
+ // Model names are only unique per schema, so match each Model by its id (schemaFileId) first.
831
+ const existingModels = await ensureModelStubs(appDb, modelNames, context.filesDir);
832
+ const rowFor = (modelName) => {
833
+ const modelFileId = allModels[modelName]?.id;
834
+ const sameName = existingModels.filter((m) => m.name === modelName);
835
+ return ((modelFileId ? sameName.find((m) => m.schemaFileId === modelFileId) : undefined) ??
836
+ (sameName.length === 1 ? sameName[0] : undefined) ??
837
+ sameName.find((m) => !m.schemaFileId));
838
+ };
839
+ let hasModelsInDb = true;
840
+ for (const modelName of modelNames) {
841
+ const foundModel = rowFor(modelName);
842
+ if (!foundModel) {
843
+ logger$6('[client/actors] [addModelsToDb] Warning: Could not find or create model:', modelName);
844
+ hasModelsInDb = false;
845
+ continue;
846
+ }
847
+ // The EAS schema string depends only on the model name, so every same-name row shares its uid.
848
+ schemaDefsByModelName.set(modelName, {
849
+ dbIds: existingModels.filter((m) => m.name === modelName).map((m) => m.id),
850
+ schemaDef: `bytes32 ${toSnakeCase(modelName)}`,
851
+ });
852
+ }
853
+ // If we have no models to process, still send success (empty models is valid)
854
+ if (schemaDefsByModelName.size === 0) {
855
+ logger$6('[client/actors] [addModelsToDb] No models to process, but continuing');
856
+ sendBack({ type: ClientManagerEvents.ADD_MODELS_TO_DB_SUCCESS });
857
+ return;
858
+ }
859
+ const schemaDefs = Array.from(schemaDefsByModelName.values()).map(({ schemaDef }) => schemaDef);
860
+ // Fetch schemas from EAS in background - do not block init. EAS can be slow (10s+);
861
+ // modelUids will be populated async and EAS sync orchestrator runs when done.
862
+ const fetchEasAndPopulateModelUids = async () => {
863
+ try {
864
+ const queryClient = BaseQueryClient.getQueryClient();
865
+ const easClient = BaseEasClient.getEasClient();
866
+ const queryPromise = queryClient.fetchQuery({
867
+ queryKey: [`getSchemasVersion`, [...schemaDefs].sort()],
868
+ queryFn: async () => easClient.request(GET_SCHEMAS, {
869
+ where: {
870
+ schema: {
871
+ in: schemaDefs,
872
+ },
873
+ },
874
+ }),
875
+ });
876
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('EAS query timeout after 10 seconds')), 10000));
877
+ const { schemas } = await Promise.race([queryPromise, timeoutPromise]);
878
+ if (schemas && schemas.length > 0) {
879
+ const db = BaseDb.getAppDb();
880
+ if (db) {
881
+ for (const schema of schemas) {
882
+ const modelIds = Array.from(schemaDefsByModelName.values()).find(({ schemaDef }) => schemaDef === schema.schema)?.dbIds ?? [];
883
+ for (const modelId of modelIds) {
884
+ await db
885
+ .insert(modelUids)
886
+ .values({
887
+ modelId,
888
+ uid: schema.id,
889
+ })
890
+ .onConflictDoNothing();
891
+ }
892
+ }
893
+ }
894
+ const { requestEasSyncFromModelsInit } = await Promise.resolve().then(function () { return easSyncManager; });
895
+ requestEasSyncFromModelsInit();
896
+ }
897
+ else {
898
+ logger$6('[client/actors] [addModelsToDb] No schemas found from EAS, but continuing');
899
+ }
900
+ }
901
+ catch (error) {
902
+ if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
903
+ logger$6('[client/actors] [addModelsToDb] Warning: Could not fetch schemas from EAS (background):', error.message);
904
+ }
905
+ else {
906
+ logger$6('[client/actors] [addModelsToDb] Error fetching schemas (background):', error.message);
907
+ }
908
+ }
909
+ };
910
+ fetchEasAndPopulateModelUids();
911
+ return hasModelsInDb;
912
+ };
913
+ _addModelsToDb()
914
+ .then(() => {
915
+ sendBack({ type: ClientManagerEvents.ADD_MODELS_TO_DB_SUCCESS });
916
+ })
917
+ .catch((error) => {
918
+ logger$6('[client/actors] [addModelsToDb] Error:', error);
919
+ // In test environments, still send success to allow initialization to complete
920
+ if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
921
+ logger$6('[client/actors] [addModelsToDb] Continuing despite error in test environment');
922
+ sendBack({ type: ClientManagerEvents.ADD_MODELS_TO_DB_SUCCESS });
923
+ }
924
+ else {
925
+ sendBack({
926
+ type: 'error',
927
+ error: error instanceof Error ? error : new Error(String(error)),
928
+ });
929
+ }
930
+ });
931
+ return () => { };
932
+ });
933
+
934
+ const logger$5 = debug('seedSdk:helpers:localDbChain');
935
+ /** appState key recording which EAS chain the local database holds attestations from. */
936
+ const EAS_CHAIN_ID_APP_STATE_KEY = 'easChainId';
937
+ /**
938
+ * Databases written before the chain was recorded only ever held Optimism Sepolia data
939
+ * (the only chain the SDK supported then).
940
+ */
941
+ const LEGACY_CHAIN_ID = DEFAULT_EAS_CHAIN_ID;
942
+ async function readRecordedChainId(appDb) {
943
+ const rows = await appDb
944
+ .select({ value: appState.value })
945
+ .from(appState)
946
+ .where(eq(appState.key, EAS_CHAIN_ID_APP_STATE_KEY))
947
+ .limit(1);
948
+ const parsed = Number(rows[0]?.value);
949
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
950
+ }
951
+ async function recordChainId(appDb, chainId) {
952
+ const value = String(chainId);
953
+ await appDb
954
+ .insert(appState)
955
+ .values({ key: EAS_CHAIN_ID_APP_STATE_KEY, value })
956
+ .onConflictDoUpdate({ target: appState.key, set: { value } });
957
+ }
958
+ async function hasOnChainData(appDb) {
959
+ const rows = await appDb
960
+ .select({ uid: seeds.uid })
961
+ .from(seeds)
962
+ .where(and(isNotNull(seeds.uid), ne(seeds.uid, '')))
963
+ .limit(1);
964
+ return rows.length > 0;
965
+ }
966
+ /**
967
+ * Chain the local DB holds data from: the recorded one, or Optimism Sepolia for an unrecorded
968
+ * DB that already has attestations. Records the legacy value so later checks are cheap.
969
+ */
970
+ async function resolveLocalDbChainId(appDb) {
971
+ const recorded = await readRecordedChainId(appDb);
972
+ if (recorded !== undefined)
973
+ return recorded;
974
+ if (await hasOnChainData(appDb)) {
975
+ await recordChainId(appDb, LEGACY_CHAIN_ID);
976
+ return LEGACY_CHAIN_ID;
977
+ }
978
+ return undefined;
979
+ }
980
+ /** How long EAS sync waits for `initPublish` to set the chain before using the current one. */
981
+ const EAS_CHAIN_SETTLE_TIMEOUT_MS = 30000;
982
+ /**
983
+ * Wait until the read chain is known before syncing. Only waits when `@seedprotocol/publish`
984
+ * is loaded but `initPublish` has not run, and no chain is configured or recorded locally.
985
+ */
986
+ async function waitForEasReadChain(timeoutMs = EAS_CHAIN_SETTLE_TIMEOUT_MS) {
987
+ if (await whenEasReadChainSettled(timeoutMs))
988
+ return;
989
+ console.warn(`[Seed Protocol] EAS sync waited ${Math.round(timeoutMs / 1000)}s for initPublish to set the chain and is continuing on chain ${getEasReadChainId()}. Set SeedConfig.eas.chainId to skip the wait.`);
990
+ }
991
+ /**
992
+ * Called at SDK init once the DB is ready. Registers the DB's chain with the EAS read side so
993
+ * reads start on that chain even before publish is initialized, and throws when SeedConfig.eas
994
+ * or PublishConfig.chain names a different chain. An empty DB is recorded only when a chain
995
+ * is configured explicitly; otherwise the first sync or publish records it.
996
+ */
997
+ async function loadLocalDbChain() {
998
+ const appDb = BaseDb.getAppDb();
999
+ if (!appDb)
1000
+ return;
1001
+ const localChainId = await resolveLocalDbChainId(appDb);
1002
+ if (localChainId !== undefined) {
1003
+ configureEasReadChain('localDb', { chainId: localChainId });
1004
+ logger$5('local DB holds chain %d', localChainId);
1005
+ return;
1006
+ }
1007
+ if (isEasReadChainConfigured()) {
1008
+ const chainId = getEasReadChainId();
1009
+ await recordChainId(appDb, chainId);
1010
+ configureEasReadChain('localDb', { chainId });
1011
+ }
1012
+ }
1013
+ /**
1014
+ * Call before writing chain data to the local DB (EAS sync, publish, revoke). Records the
1015
+ * current chain on an empty DB; throws when the DB holds data from a different chain.
1016
+ */
1017
+ async function assertLocalDbChain() {
1018
+ const appDb = BaseDb.getAppDb();
1019
+ if (!appDb)
1020
+ return;
1021
+ const chainId = getEasReadChainId();
1022
+ const localChainId = await resolveLocalDbChainId(appDb);
1023
+ if (localChainId === undefined) {
1024
+ await recordChainId(appDb, chainId);
1025
+ }
1026
+ // Throws with a remediation message when the local chain differs from the configured one.
1027
+ configureEasReadChain('localDb', { chainId: localChainId ?? chainId });
1028
+ }
1029
+
1030
+ const logger$4 = debug('seedSdk:client:actors:saveConfig');
1031
+ /**
1032
+ * Saves the addresses passed to init. Hosts pass `addresses: []` before a wallet connects. In the
1033
+ * first (leader) tab that clears stored addresses, as it always has, so drafts made before the
1034
+ * wallet reconnects stay editable. A tab opened while another is running (`keepStoredWhenEmpty`)
1035
+ * keeps them instead: they're the other tab's connected wallet, which EAS sync, downloads and
1036
+ * publishing read. See docs/MULTI_TAB.md.
1037
+ */
1038
+ async function persistInitAddresses(appDb, { addresses, ownedAddresses, watchedAddresses }, { keepStoredWhenEmpty }) {
1039
+ const owned = normalizeAddressList(ownedAddresses ?? addresses ?? []);
1040
+ const watched = normalizeAddressList(watchedAddresses ?? []);
1041
+ if (owned.length === 0 && watched.length === 0 && keepStoredWhenEmpty)
1042
+ return;
1043
+ const value = JSON.stringify({ owned, watched });
1044
+ await appDb
1045
+ .insert(appState)
1046
+ .values({ key: 'addresses', value })
1047
+ .onConflictDoUpdate({ target: appState.key, set: { value } });
1048
+ }
1049
+ const saveConfig = fromCallback(({ sendBack, input: { context } }) => {
1050
+ logger$4('saveConfig starting');
1051
+ const { endpoints, addresses, ownedAddresses, watchedAddresses, arweaveDomain, uploadApiBaseUrl, gatewayTransport, gatewayHyperKey, gatewaySidecarHost, gatewaySidecarPort, gatewayProxyBaseUrl, } = context;
1052
+ // Validate endpoints - required for proper initialization
1053
+ // If endpoints are missing or invalid, initialization should fail
1054
+ if (!endpoints || !endpoints.filePaths || !endpoints.files) {
1055
+ const error = new Error('saveConfig called with invalid endpoints: endpoints must include both filePaths and files');
1056
+ logger$4('[internal/actors] [saveConfig] Invalid endpoints:', { endpoints });
1057
+ throw error;
1058
+ }
1059
+ const _saveConfig = async () => {
1060
+ try {
1061
+ const appDb = BaseDb.getAppDb();
1062
+ if (!appDb) {
1063
+ // In test environments, continue anyway
1064
+ if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
1065
+ logger$4('[internal/actors] [saveConfig] App DB not found, but continuing in test environment');
1066
+ return;
1067
+ }
1068
+ throw new Error('App DB not found');
1069
+ }
1070
+ const endpointsValueString = JSON.stringify(endpoints);
1071
+ // TODO: Figure out how to define on conflict with multiple rows added
1072
+ await appDb
1073
+ .insert(appState)
1074
+ .values({
1075
+ key: 'endpoints',
1076
+ value: endpointsValueString,
1077
+ })
1078
+ .onConflictDoUpdate({
1079
+ target: appState.key,
1080
+ set: {
1081
+ value: endpointsValueString,
1082
+ },
1083
+ });
1084
+ await persistInitAddresses(appDb, { addresses, ownedAddresses, watchedAddresses }, { keepStoredWhenEmpty: !isLeaderTab() });
1085
+ await appDb
1086
+ .insert(appState)
1087
+ .values({
1088
+ key: 'arweaveDomain',
1089
+ value: arweaveDomain || DEFAULT_ARWEAVE_HOST,
1090
+ })
1091
+ .onConflictDoUpdate({
1092
+ target: appState.key,
1093
+ set: {
1094
+ value: arweaveDomain || DEFAULT_ARWEAVE_HOST,
1095
+ },
1096
+ });
1097
+ const persistKey = async (key, value) => {
1098
+ if (value == null || value === '')
1099
+ return;
1100
+ await appDb
1101
+ .insert(appState)
1102
+ .values({ key, value })
1103
+ .onConflictDoUpdate({
1104
+ target: appState.key,
1105
+ set: { value },
1106
+ });
1107
+ };
1108
+ await persistKey('uploadApiBaseUrl', uploadApiBaseUrl);
1109
+ await persistKey('gatewayTransport', gatewayTransport);
1110
+ await persistKey('gatewayHyperKey', gatewayHyperKey);
1111
+ await persistKey('gatewaySidecarHost', gatewaySidecarHost);
1112
+ if (gatewaySidecarPort != null) {
1113
+ await persistKey('gatewaySidecarPort', String(gatewaySidecarPort));
1114
+ }
1115
+ await persistKey('gatewayProxyBaseUrl', gatewayProxyBaseUrl);
1116
+ // Throws when the DB holds attestations from a different chain than the one configured.
1117
+ await loadLocalDbChain();
1118
+ }
1119
+ catch (error) {
1120
+ logger$4('[internal/actors] [saveConfig] Error saving config:', error);
1121
+ // In test environments, continue anyway
1122
+ if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
1123
+ logger$4('[internal/actors] [saveConfig] Continuing despite error in test environment');
1124
+ return;
1125
+ }
1126
+ throw error;
1127
+ }
1128
+ };
1129
+ // Init writes run one tab at a time (docs/MULTI_TAB.md).
1130
+ withSeedDbLock('init', context.filesDir, _saveConfig)
1131
+ .then(() => {
1132
+ logger$4('[internal/actors] [saveConfig] saveConfig success');
1133
+ return sendBack({ type: ClientManagerEvents.SAVE_CONFIG_SUCCESS });
1134
+ })
1135
+ .catch((error) => {
1136
+ logger$4('[internal/actors] [saveConfig] Error in saveConfig promise chain:', error);
1137
+ // In test environments, still send success to allow state machine to progress
1138
+ if (process.env.NODE_ENV === 'test' || process.env.IS_SEED_DEV) {
1139
+ logger$4('[internal/actors] [saveConfig] Sending success despite error in test environment');
1140
+ sendBack({ type: ClientManagerEvents.SAVE_CONFIG_SUCCESS });
1141
+ }
1142
+ else {
1143
+ sendBack({
1144
+ type: 'error',
1145
+ error: error instanceof Error ? error : new Error(String(error)),
1146
+ });
1147
+ }
1148
+ });
1149
+ return () => { };
1150
+ });
1151
+
1152
+ const logger$3 = debug('seedSdk:helpers:schema');
1153
+ /**
1154
+ * Sanitize a schema name to be filesystem-safe
1155
+ * Replaces all special characters (except alphanumeric, hyphens, underscores) with underscores
1156
+ * Converts spaces to underscores
1157
+ * Removes leading/trailing underscores
1158
+ *
1159
+ * @param name - Schema name to sanitize
1160
+ * @returns Sanitized name safe for use in filenames
1161
+ */
1162
+ const sanitizeSchemaName$1 = (name) => {
1163
+ return name
1164
+ .replace(/[^a-zA-Z0-9\s_-]/g, '_') // Replace special chars (except spaces, hyphens, underscores) with underscore
1165
+ .replace(/\s+/g, '_') // Convert spaces to underscores
1166
+ .replace(/^_+|_+$/g, '') // Remove leading/trailing underscores
1167
+ .replace(/_+/g, '_'); // Collapse multiple underscores to single
1168
+ };
1169
+ /**
1170
+ * Generate filename for a schema
1171
+ * Format: {schemaFileId}_{schemaName}_v{version}.json
1172
+ *
1173
+ * The ID-first format ensures all files for a schema group together when sorted alphabetically.
1174
+ *
1175
+ * @param name - Schema name
1176
+ * @param version - Schema version
1177
+ * @param schemaFileId - Schema file ID (required)
1178
+ */
1179
+ const getSchemaFilename = (name, version, schemaFileId) => {
1180
+ const sanitizedName = sanitizeSchemaName$1(name);
1181
+ return `${schemaFileId}_${sanitizedName}_v${version}.json`;
1182
+ };
1183
+ /**
1184
+ * Get the full file path for a schema
1185
+ * Format: {schemaFileId}_{schemaName}_v{version}.json
1186
+ *
1187
+ * The ID-first format ensures all files for a schema group together when sorted alphabetically.
1188
+ *
1189
+ * @param name - Schema name
1190
+ * @param version - Schema version
1191
+ * @param schemaFileId - Schema file ID (required)
1192
+ */
1193
+ const getSchemaFilePath$1 = (name, version, schemaFileId) => {
1194
+ const path = BaseFileManager.getPathModule();
1195
+ const filename = getSchemaFilename(name, version, schemaFileId);
1196
+ const workingDir = BaseFileManager.getWorkingDir();
1197
+ return path.join(workingDir, filename);
1198
+ };
1199
+ /**
1200
+ * Create a new schema file
1201
+ * @param schema - The schema object to save
1202
+ * @param schemaFileId - Schema file ID (required)
1203
+ * @throws Error if schema already exists or if workingDir is invalid
1204
+ */
1205
+ const createSchema = async (schema, schemaFileId) => {
1206
+ if (!schema.name || !schema.version) {
1207
+ throw new Error('Schema must have a name and version');
1208
+ }
1209
+ const filePath = getSchemaFilePath$1(schema.name, schema.version, schemaFileId);
1210
+ // Check if schema already exists
1211
+ const exists = await BaseFileManager.pathExists(filePath);
1212
+ if (exists) {
1213
+ throw new Error(`Schema ${schema.name} v${schema.version} already exists`);
1214
+ }
1215
+ // Ensure working directory exists
1216
+ const workingDir = BaseFileManager.getWorkingDir();
1217
+ await BaseFileManager.createDirIfNotExists(workingDir);
1218
+ // Write schema to file
1219
+ const content = JSON.stringify(schema, null, 2);
1220
+ await BaseFileManager.saveFile(filePath, content);
1221
+ };
1222
+ /**
1223
+ * Read a schema file by name and version
1224
+ * @param name - The name of the schema
1225
+ * @param version - The version of the schema
1226
+ * @param schemaFileId - Schema file ID (required)
1227
+ * @returns The schema object, or null if not found
1228
+ */
1229
+ const readSchema = async (name, version, schemaFileId) => {
1230
+ const filePath = getSchemaFilePath$1(name, version, schemaFileId);
1231
+ const exists = await BaseFileManager.pathExists(filePath);
1232
+ if (!exists) {
1233
+ return null;
1234
+ }
1235
+ try {
1236
+ const content = await BaseFileManager.readFileAsString(filePath);
1237
+ return JSON.parse(content);
1238
+ }
1239
+ catch (error) {
1240
+ throw new Error(`Failed to read schema ${name} v${version}: ${error instanceof Error ? error.message : String(error)}`);
1241
+ }
1242
+ };
1243
+ /**
1244
+ * Update an existing schema file
1245
+ * @param schema - The updated schema object (must have same name and version as existing)
1246
+ * @param schemaFileId - Schema file ID (required)
1247
+ * @throws Error if schema doesn't exist
1248
+ */
1249
+ async function updateSchema(schema, schemaFileId) {
1250
+ if (!schema.name || !schema.version) {
1251
+ throw new Error('Schema must have a name and version');
1252
+ }
1253
+ const filePath = getSchemaFilePath$1(schema.name, schema.version, schemaFileId);
1254
+ // Check if schema exists
1255
+ const exists = await BaseFileManager.pathExists(filePath);
1256
+ if (!exists) {
1257
+ throw new Error(`Schema ${schema.name} v${schema.version} does not exist`);
1258
+ }
1259
+ // Write updated schema to file
1260
+ const content = JSON.stringify(schema, null, 2);
1261
+ await BaseFileManager.saveFile(filePath, content);
1262
+ }
1263
+ /**
1264
+ * Delete a schema file
1265
+ * @param name - The name of the schema
1266
+ * @param version - The version of the schema
1267
+ * @param schemaFileId - Schema file ID (required)
1268
+ * @throws Error if schema doesn't exist
1269
+ */
1270
+ async function deleteSchema(name, version, schemaFileId) {
1271
+ const filePath = getSchemaFilePath$1(name, version, schemaFileId);
1272
+ // Check if schema exists
1273
+ const exists = await BaseFileManager.pathExists(filePath);
1274
+ if (!exists) {
1275
+ throw new Error(`Schema ${name} v${version} does not exist`);
1276
+ }
1277
+ // Delete the file
1278
+ const fs = await BaseFileManager.getFs();
1279
+ BaseFileManager.getPathModule();
1280
+ try {
1281
+ await fs.promises.unlink(filePath);
1282
+ }
1283
+ catch (error) {
1284
+ throw new Error(`Failed to delete schema ${name} v${version}: ${error instanceof Error ? error.message : String(error)}`);
1285
+ }
1286
+ }
1287
+ /**
1288
+ * List all schema files in the working directory
1289
+ * Only returns files that are NOT already complete schema files (i.e., files without $schema field)
1290
+ * This allows processing of minimal import files while skipping already-processed schema files
1291
+ * @returns Array of objects containing name, version, and file path for each schema
1292
+ */
1293
+ async function listSchemaFiles() {
1294
+ const fs = await BaseFileManager.getFs();
1295
+ const path = BaseFileManager.getPathModule();
1296
+ // Check if working directory exists
1297
+ const workingDir = BaseFileManager.getWorkingDir();
1298
+ try {
1299
+ const files = await fs.promises.readdir(workingDir);
1300
+ const schemas = [];
1301
+ for (const file of files) {
1302
+ // Match filename pattern: {name}-v{version}.json
1303
+ const match = file.match(/^(.+)-v(\d+)\.json$/);
1304
+ if (match) {
1305
+ const [, name, versionStr] = match;
1306
+ const version = parseInt(versionStr, 10);
1307
+ if (!isNaN(version)) {
1308
+ const filePath = path.join(workingDir, file);
1309
+ // Check if this file is already a complete schema file (has $schema field)
1310
+ // If it is, skip it since it's already been processed
1311
+ try {
1312
+ const content = await BaseFileManager.readFileAsString(filePath);
1313
+ const data = JSON.parse(content);
1314
+ // Skip files that already have $schema (complete schema format)
1315
+ // These are already processed and don't need to be imported again
1316
+ if (data.$schema) {
1317
+ continue;
1318
+ }
1319
+ }
1320
+ catch (error) {
1321
+ // If we can't read/parse the file, skip it
1322
+ continue;
1323
+ }
1324
+ schemas.push({
1325
+ name,
1326
+ version,
1327
+ filePath,
1328
+ });
1329
+ }
1330
+ }
1331
+ }
1332
+ return schemas;
1333
+ }
1334
+ catch (error) {
1335
+ throw new Error(`Failed to list schemas: ${error instanceof Error ? error.message : String(error)}`);
1336
+ }
1337
+ }
1338
+ async function listCompleteSchemaFiles() {
1339
+ const fs = await BaseFileManager.getFs();
1340
+ const path = BaseFileManager.getPathModule();
1341
+ // Check if working directory exists
1342
+ const workingDir = BaseFileManager.getWorkingDir();
1343
+ try {
1344
+ // Check if directory exists before trying to read it
1345
+ const dirExists = await BaseFileManager.pathExists(workingDir);
1346
+ if (!dirExists) {
1347
+ // Directory doesn't exist yet - return empty array (not an error condition)
1348
+ logger$3(`Working directory does not exist yet: ${workingDir}, returning empty schema list`);
1349
+ return [];
1350
+ }
1351
+ const files = await fs.promises.readdir(workingDir);
1352
+ const schemas = [];
1353
+ for (const file of files) {
1354
+ // Match filename pattern: {schemaFileId}_{schemaName}_v{version}.json
1355
+ const match = file.match(/^(.+)_(.+)_v(\d+)\.json$/);
1356
+ if (match) {
1357
+ const [, schemaFileId, schemaName, versionStr] = match;
1358
+ const version = parseInt(versionStr, 10);
1359
+ if (!isNaN(version)) {
1360
+ const filePath = path.join(workingDir, file);
1361
+ // Check if this file is a complete schema file (has $schema field)
1362
+ try {
1363
+ const content = await BaseFileManager.readFileAsString(filePath);
1364
+ const data = JSON.parse(content);
1365
+ // Only include files that have $schema (complete schema format)
1366
+ if (data.$schema && data.metadata?.name) {
1367
+ // CRITICAL: Use schemaFileId from filename (source of truth), not from JSON content
1368
+ // The filename pattern is {schemaFileId}_{schemaName}_v{version}.json
1369
+ // The JSON content's id might be out of sync, but the filename tells us what file actually exists
1370
+ schemas.push({
1371
+ name: data.metadata.name,
1372
+ version: data.version || version,
1373
+ filePath,
1374
+ schemaFileId: schemaFileId, // Use from filename, not data.id
1375
+ });
1376
+ }
1377
+ }
1378
+ catch (error) {
1379
+ // If we can't read/parse the file, skip it
1380
+ continue;
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+ return schemas;
1386
+ }
1387
+ catch (error) {
1388
+ throw new Error(`Failed to list complete schemas: ${error instanceof Error ? error.message : String(error)}`);
1389
+ }
1390
+ }
1391
+ /**
1392
+ * Find a schema by name (returns the latest version if multiple versions exist)
1393
+ * @param name - The name of the schema
1394
+ * @returns The schema with the highest version, or null if not found
1395
+ */
1396
+ async function findSchemaByName(name) {
1397
+ const schemas = await listCompleteSchemaFiles();
1398
+ const matchingSchemas = schemas.filter((s) => s.name === name);
1399
+ if (matchingSchemas.length === 0) {
1400
+ return null;
1401
+ }
1402
+ // Find the schema with the highest version
1403
+ const latest = matchingSchemas.reduce((prev, current) => current.version > prev.version ? current : prev);
1404
+ if (!latest.schemaFileId) {
1405
+ throw new Error(`Schema ${latest.name} v${latest.version} is missing schemaFileId`);
1406
+ }
1407
+ return readSchema(latest.name, latest.version, latest.schemaFileId);
1408
+ }
1409
+ /**
1410
+ * Get the latest version number for a schema by name
1411
+ * Only considers complete schema files (with $schema field)
1412
+ * @param name - The name of the schema
1413
+ * @returns The latest version number, or 0 if no schema found
1414
+ */
1415
+ async function getLatestSchemaVersion(name) {
1416
+ const completeSchemas = await listCompleteSchemaFiles();
1417
+ const matchingSchemas = completeSchemas.filter((s) => s.name === name);
1418
+ if (matchingSchemas.length === 0) {
1419
+ return 0;
1420
+ }
1421
+ // Find the schema with the highest version
1422
+ const latest = matchingSchemas.reduce((prev, current) => current.version > prev.version ? current : prev);
1423
+ return latest.version;
1424
+ }
1425
+ /**
1426
+ * Get only the latest version of each schema
1427
+ * @returns Array of objects containing name, version, and file path for the latest version of each schema
1428
+ */
1429
+ async function listLatestSchemaFiles() {
1430
+ const completeSchemas = await listCompleteSchemaFiles();
1431
+ // Group by schema name and keep only the latest version of each
1432
+ const schemaMap = new Map();
1433
+ for (const schema of completeSchemas) {
1434
+ const existing = schemaMap.get(schema.name);
1435
+ if (!existing || schema.version > existing.version) {
1436
+ schemaMap.set(schema.name, schema);
1437
+ }
1438
+ }
1439
+ return Array.from(schemaMap.values());
1440
+ }
1441
+ /**
1442
+ * Filter an array of schemas to only include the latest version for each schema name
1443
+ * Works with any object that has a name (via metadata.name or name property) and a version property
1444
+ * @param schemas - Array of schema objects
1445
+ * @returns Array containing only the latest version of each schema
1446
+ */
1447
+ function filterLatestSchemas(schemas) {
1448
+ // Group by schema name and keep only the latest version of each
1449
+ const schemaMap = new Map();
1450
+ for (const schema of schemas) {
1451
+ const schemaName = schema.metadata?.name || schema.name;
1452
+ if (!schemaName) {
1453
+ continue; // Skip schemas without a name
1454
+ }
1455
+ const existing = schemaMap.get(schemaName);
1456
+ if (!existing || schema.version > existing.version) {
1457
+ schemaMap.set(schemaName, schema);
1458
+ }
1459
+ }
1460
+ return Array.from(schemaMap.values());
1461
+ }
1462
+ async function loadAllSchemasFromDb() {
1463
+ const db = BaseDb.getAppDb();
1464
+ if (!db) {
1465
+ throw new Error('Database not found');
1466
+ }
1467
+ const result = [];
1468
+ // STEP 1: Query all schemas from database
1469
+ const dbSchemas = await db
1470
+ .select()
1471
+ .from(schemas)
1472
+ .orderBy(schemas.name, desc(schemas.version));
1473
+ const processedSchemaNames = new Set();
1474
+ // List the schema files once. listCompleteSchemaFiles reads and parses every file in the working
1475
+ // dir, so listing per schema made this O(files x schemas).
1476
+ let completeSchemaFiles;
1477
+ const getCompleteSchemaFiles = async () => {
1478
+ if (!completeSchemaFiles) {
1479
+ try {
1480
+ completeSchemaFiles = await listCompleteSchemaFiles();
1481
+ }
1482
+ catch (error) {
1483
+ // If we can't list schema files (e.g., directory doesn't exist yet), continue without file-based loading
1484
+ logger$3(`Error listing complete schema files (continuing with DB data): ${error instanceof Error ? error.message : String(error)}`);
1485
+ completeSchemaFiles = [];
1486
+ }
1487
+ }
1488
+ return completeSchemaFiles;
1489
+ };
1490
+ // STEP 2: Process each database schema
1491
+ for (const dbSchema of dbSchemas) {
1492
+ const schemaName = dbSchema.name;
1493
+ // Skip if we've already processed a newer version of this schema
1494
+ if (processedSchemaNames.has(schemaName)) {
1495
+ continue;
1496
+ }
1497
+ // If it's a draft, load from schemaData
1498
+ if (dbSchema.isDraft === true && dbSchema.schemaData) {
1499
+ try {
1500
+ let schemaFile = JSON.parse(dbSchema.schemaData);
1501
+ // Merge models from DB only when schemaData is incomplete. When schemaData has models (e.g. init),
1502
+ // skip the expensive loadModelsFromDbForSchema - it can return 1000s of orphaned model_schemas rows
1503
+ // and cause init to hang. The merge is for drafts where schemaData may be missing models.
1504
+ const schemaModelCount = Object.keys(schemaFile.models || {}).length;
1505
+ let mergedModels = { ...(schemaFile.models || {}) };
1506
+ if (dbSchema.id && schemaModelCount === 0) {
1507
+ const dbModels = await loadModelsFromDbForSchema(dbSchema.id);
1508
+ if (Object.keys(dbModels).length > 0) {
1509
+ mergedModels = { ...mergedModels, ...dbModels };
1510
+ for (const [modelName, dbModel] of Object.entries(dbModels)) {
1511
+ if (mergedModels[modelName]) {
1512
+ mergedModels[modelName] = {
1513
+ ...mergedModels[modelName],
1514
+ properties: {
1515
+ ...mergedModels[modelName].properties,
1516
+ ...dbModel.properties,
1517
+ },
1518
+ };
1519
+ }
1520
+ }
1521
+ }
1522
+ }
1523
+ schemaFile = {
1524
+ ...schemaFile,
1525
+ models: mergedModels,
1526
+ };
1527
+ result.push({
1528
+ schema: schemaFile,
1529
+ isDraft: true,
1530
+ source: 'db',
1531
+ schemaRecordId: dbSchema.id || undefined,
1532
+ });
1533
+ processedSchemaNames.add(schemaName);
1534
+ continue;
1535
+ }
1536
+ catch (error) {
1537
+ logger$3(`Error parsing schemaData for ${schemaName}:`, error);
1538
+ // Fall through to file-based loading
1539
+ }
1540
+ }
1541
+ // If it's not a draft and has schemaFileId, try to load from file
1542
+ if (dbSchema.isDraft === false && dbSchema.schemaFileId) {
1543
+ const completeSchemas = await getCompleteSchemaFiles();
1544
+ const matchingSchemas = completeSchemas.filter((s) => s.name === schemaName);
1545
+ if (matchingSchemas.length > 0) {
1546
+ // Find the schema with the highest version
1547
+ const latest = matchingSchemas.reduce((prev, current) => current.version > prev.version ? current : prev);
1548
+ try {
1549
+ // Read the file directly to get SchemaFileFormat
1550
+ const content = await BaseFileManager.readFileAsString(latest.filePath);
1551
+ const schemaFile = JSON.parse(content);
1552
+ // Check if file is newer than DB record
1553
+ const fileUpdatedAt = new Date(schemaFile.metadata.updatedAt).getTime();
1554
+ const dbUpdatedAt = dbSchema.updatedAt || 0;
1555
+ const dbMatchesFile = schemaFile.id === dbSchema.schemaFileId &&
1556
+ schemaFile.version === dbSchema.version &&
1557
+ fileUpdatedAt <= dbUpdatedAt;
1558
+ // If file is newer, update DB with file content
1559
+ if (fileUpdatedAt > dbUpdatedAt && schemaFile.id === dbSchema.schemaFileId) {
1560
+ await addSchemaToDb({
1561
+ name: schemaName,
1562
+ version: schemaFile.version,
1563
+ createdAt: new Date(schemaFile.metadata.createdAt).getTime(),
1564
+ updatedAt: fileUpdatedAt,
1565
+ }, schemaFile.id, JSON.stringify(schemaFile, null, 2), false);
1566
+ }
1567
+ result.push({
1568
+ schema: schemaFile,
1569
+ isDraft: false,
1570
+ source: 'db+file',
1571
+ schemaRecordId: dbSchema.id || undefined,
1572
+ filePath: latest.filePath,
1573
+ dbMatchesFile,
1574
+ });
1575
+ processedSchemaNames.add(schemaName);
1576
+ continue;
1577
+ }
1578
+ catch (error) {
1579
+ logger$3(`Error loading schema file for ${schemaName}:`, error);
1580
+ // Fall through to try loading from schemaData if available
1581
+ }
1582
+ }
1583
+ // If file doesn't exist but we have schemaData, use that
1584
+ if (dbSchema.schemaData) {
1585
+ try {
1586
+ let schemaFile = JSON.parse(dbSchema.schemaData);
1587
+ // CRITICAL: Merge models from database (model_schemas join table) with models from schemaData
1588
+ // This ensures models added to the database are included even if they're not in schemaData
1589
+ // Build merged models without mutating schemaFile (read-only approach)
1590
+ const schemaModelCount = Object.keys(schemaFile.models || {}).length;
1591
+ let mergedModels = { ...(schemaFile.models || {}) };
1592
+ if (dbSchema.id && schemaModelCount === 0) {
1593
+ const dbModels = await loadModelsFromDbForSchema(dbSchema.id);
1594
+ if (Object.keys(dbModels).length > 0) {
1595
+ mergedModels = { ...mergedModels, ...dbModels };
1596
+ for (const [modelName, dbModel] of Object.entries(dbModels)) {
1597
+ if (mergedModels[modelName]) {
1598
+ mergedModels[modelName] = {
1599
+ ...mergedModels[modelName],
1600
+ properties: {
1601
+ ...mergedModels[modelName].properties,
1602
+ ...dbModel.properties,
1603
+ },
1604
+ };
1605
+ }
1606
+ }
1607
+ }
1608
+ }
1609
+ schemaFile = {
1610
+ ...schemaFile,
1611
+ models: mergedModels,
1612
+ };
1613
+ result.push({
1614
+ schema: schemaFile,
1615
+ isDraft: false,
1616
+ source: 'db',
1617
+ schemaRecordId: dbSchema.id || undefined,
1618
+ });
1619
+ processedSchemaNames.add(schemaName);
1620
+ continue;
1621
+ }
1622
+ catch (error) {
1623
+ logger$3(`Error parsing schemaData for ${schemaName}:`, error);
1624
+ }
1625
+ }
1626
+ }
1627
+ }
1628
+ // STEP 3: Find schema files not yet in database (for migration/backward compatibility)
1629
+ const completeSchemas = await getCompleteSchemaFiles();
1630
+ for (const schemaFileInfo of completeSchemas) {
1631
+ // Only process if not already in result
1632
+ if (!processedSchemaNames.has(schemaFileInfo.name)) {
1633
+ if (isInternalSchema(schemaFileInfo.name, schemaFileInfo.schemaFileId)) {
1634
+ logger$3(`STEP 3: skipping internal schema file: ${schemaFileInfo.filePath}`);
1635
+ continue;
1636
+ }
1637
+ try {
1638
+ logger$3(`STEP 3: processing schema file: ${schemaFileInfo.name} from ${schemaFileInfo.filePath}`);
1639
+ const content = await BaseFileManager.readFileAsString(schemaFileInfo.filePath);
1640
+ const schemaFile = JSON.parse(content);
1641
+ // Import this schema to database (mark as non-draft since it's in a file)
1642
+ const schemaRecord = await addSchemaToDb({
1643
+ name: schemaFile.metadata.name,
1644
+ version: schemaFile.version,
1645
+ createdAt: new Date(schemaFile.metadata.createdAt).getTime(),
1646
+ updatedAt: new Date(schemaFile.metadata.updatedAt).getTime(),
1647
+ }, schemaFile.id, JSON.stringify(schemaFile, null, 2), false);
1648
+ result.push({
1649
+ schema: schemaFile,
1650
+ isDraft: false,
1651
+ source: 'file',
1652
+ schemaRecordId: schemaRecord.id || undefined,
1653
+ filePath: schemaFileInfo.filePath,
1654
+ });
1655
+ processedSchemaNames.add(schemaFileInfo.name);
1656
+ }
1657
+ catch (error) {
1658
+ logger$3(`Error loading schema file ${schemaFileInfo.filePath}:`, error);
1659
+ }
1660
+ }
1661
+ }
1662
+ return result;
1663
+ }
1664
+ /**
1665
+ * Migration helper: Migrate existing file-based schemas to the database
1666
+ * This function should be called once to migrate existing schema files to the new database-first approach
1667
+ * All existing schemas will be marked as published (isDraft = false) since they already exist in files
1668
+ * @returns Number of schemas migrated
1669
+ */
1670
+ async function migrateFileSchemasToDb() {
1671
+ const db = BaseDb.getAppDb();
1672
+ if (!db) {
1673
+ throw new Error('Database not found');
1674
+ }
1675
+ logger$3('Starting migration of file-based schemas to database');
1676
+ // Get all complete schema files
1677
+ const completeSchemas = await listCompleteSchemaFiles();
1678
+ let migratedCount = 0;
1679
+ for (const schemaFileInfo of completeSchemas) {
1680
+ try {
1681
+ // Read the schema file
1682
+ const content = await BaseFileManager.readFileAsString(schemaFileInfo.filePath);
1683
+ const schemaFile = JSON.parse(content);
1684
+ if (!schemaFile.$schema || !schemaFile.metadata?.name) {
1685
+ logger$3(`Skipping invalid schema file: ${schemaFileInfo.filePath}`);
1686
+ continue;
1687
+ }
1688
+ const schemaName = schemaFile.metadata.name;
1689
+ // Check if schema already exists in database
1690
+ const existingSchemas = await db
1691
+ .select()
1692
+ .from(schemas)
1693
+ .where(eq(schemas.name, schemaName))
1694
+ .limit(1);
1695
+ // Only migrate if it doesn't exist or if existing record doesn't have schemaFileId
1696
+ if (existingSchemas.length === 0 || !existingSchemas[0].schemaFileId) {
1697
+ // Add schema to database as published (isDraft = false)
1698
+ await addSchemaToDb({
1699
+ name: schemaName,
1700
+ version: schemaFile.version,
1701
+ createdAt: new Date(schemaFile.metadata.createdAt).getTime(),
1702
+ updatedAt: new Date(schemaFile.metadata.updatedAt).getTime(),
1703
+ }, schemaFile.id, // schemaFileId
1704
+ JSON.stringify(schemaFile, null, 2), // schemaData
1705
+ false);
1706
+ migratedCount++;
1707
+ logger$3(`Migrated schema: ${schemaName} v${schemaFile.version}`);
1708
+ }
1709
+ else {
1710
+ logger$3(`Schema ${schemaName} already exists in database, skipping`);
1711
+ }
1712
+ }
1713
+ catch (error) {
1714
+ logger$3(`Error migrating schema file ${schemaFileInfo.filePath}:`, error);
1715
+ }
1716
+ }
1717
+ logger$3(`Migration complete: ${migratedCount} schemas migrated`);
1718
+ return migratedCount;
1719
+ }
1720
+ /**
1721
+ * Extract the schema name from a schemaId string
1722
+ * @param schemaId - Schema ID in the format `${schemaName}-${schemaVersion}`
1723
+ * @returns The schema name, or null if the format is invalid
1724
+ */
1725
+ function getSchemaNameFromId(schemaId) {
1726
+ if (!schemaId) {
1727
+ return null;
1728
+ }
1729
+ const lastDashIndex = schemaId.lastIndexOf('-');
1730
+ if (lastDashIndex === -1 || lastDashIndex === 0 || lastDashIndex === schemaId.length - 1) {
1731
+ return null;
1732
+ }
1733
+ return schemaId.substring(0, lastDashIndex);
1734
+ }
1735
+
1736
+ var schema = /*#__PURE__*/Object.freeze({
1737
+ __proto__: null,
1738
+ createSchema: createSchema,
1739
+ deleteSchema: deleteSchema,
1740
+ filterLatestSchemas: filterLatestSchemas,
1741
+ findSchemaByName: findSchemaByName,
1742
+ getLatestSchemaVersion: getLatestSchemaVersion,
1743
+ getSchemaNameFromId: getSchemaNameFromId,
1744
+ listCompleteSchemaFiles: listCompleteSchemaFiles,
1745
+ listLatestSchemaFiles: listLatestSchemaFiles,
1746
+ listSchemaFiles: listSchemaFiles,
1747
+ loadAllSchemasFromDb: loadAllSchemasFromDb,
1748
+ migrateFileSchemasToDb: migrateFileSchemasToDb,
1749
+ readSchema: readSchema,
1750
+ updateSchema: updateSchema
1751
+ });
1752
+
1753
+ function toBytes(data) {
1754
+ if (typeof data === 'string')
1755
+ return utf8ToBytes(data);
1756
+ if (data instanceof Uint8Array)
1757
+ return data;
1758
+ return new Uint8Array(data);
1759
+ }
1760
+ const getContentHash = async (data) => {
1761
+ return bytesToHex(sha3_256(toBytes(data)));
1762
+ };
1763
+ /**
1764
+ * Returns a function that maps `suffix` to `getDeterministicId(prefix + suffix)`, hashing `prefix` only
1765
+ * once. Use it when deriving many ids from one long shared prefix (e.g. a whole serialized schema).
1766
+ */
1767
+ const getDeterministicIdsWithPrefix = (prefix) => {
1768
+ const prefixHash = sha3_256.create().update(utf8ToBytes(prefix));
1769
+ return (suffix) => bytesToHex(prefixHash.clone().update(utf8ToBytes(suffix)).digest()).slice(0, 10);
1770
+ };
1771
+
1772
+ const logger$2 = debug('seedSdk:imports:json');
1773
+ /**
1774
+ * Get refValueType from a property object with case-insensitive key lookup.
1775
+ * Handles refValueType, refvaluetype, ref_value_type, elementType, and legacy items.type / items.Type.
1776
+ * Also handles items as string (e.g. items: "Relation").
1777
+ */
1778
+ /**
1779
+ * Normalize schema file format: convert models/properties arrays to objects if needed.
1780
+ * Some tools output models/properties as arrays; we need object format keyed by name or index.
1781
+ */
1782
+ function normalizeSchemaModels(models) {
1783
+ if (!models || typeof models !== 'object')
1784
+ return {};
1785
+ if (!Array.isArray(models))
1786
+ return models;
1787
+ const result = {};
1788
+ for (let i = 0; i < models.length; i++) {
1789
+ const m = models[i];
1790
+ const key = m?.name ?? m?.id ?? String(i);
1791
+ const props = m?.properties;
1792
+ let properties = {};
1793
+ if (props && typeof props === 'object') {
1794
+ if (Array.isArray(props)) {
1795
+ for (let j = 0; j < props.length; j++) {
1796
+ const p = props[j];
1797
+ const pKey = p?.name ?? p?.id ?? String(j);
1798
+ properties[pKey] = p;
1799
+ }
1800
+ }
1801
+ else {
1802
+ properties = props;
1803
+ }
1804
+ }
1805
+ result[key] = { ...m, properties };
1806
+ }
1807
+ return result;
1808
+ }
1809
+ /**
1810
+ * Get refValueType from a property object with case-insensitive key lookup.
1811
+ */
1812
+ function getRefValueType(prop) {
1813
+ const refValueType = prop.refValueType ??
1814
+ prop.refvaluetype ??
1815
+ prop.RefValueType ??
1816
+ prop.REFVALUETYPE ??
1817
+ prop.ref_value_type;
1818
+ if (refValueType != null && String(refValueType).trim() !== '') {
1819
+ return String(refValueType);
1820
+ }
1821
+ const elementType = prop.elementType ?? prop.element_type;
1822
+ if (elementType != null && String(elementType).trim() !== '') {
1823
+ return String(elementType);
1824
+ }
1825
+ const items = prop.items;
1826
+ if (items != null) {
1827
+ if (typeof items === 'string' && items.trim() !== '') {
1828
+ return items.trim();
1829
+ }
1830
+ if (items && typeof items === 'object') {
1831
+ const itemsType = items.type ?? items.Type ?? items.TYPE;
1832
+ if (itemsType != null && String(itemsType).trim() !== '') {
1833
+ return String(itemsType);
1834
+ }
1835
+ }
1836
+ }
1837
+ return undefined;
1838
+ }
1839
+ /**
1840
+ * Verify that properties are persisted to the database for a given model
1841
+ * This is important in browser environments where database writes may not be immediately visible
1842
+ * @param db - Database instance
1843
+ * @param modelId - Database ID of the model
1844
+ * @param modelName - Name of the model (for logging)
1845
+ * @param maxRetries - Maximum number of retry attempts
1846
+ * @param retryDelay - Delay between retries in milliseconds
1847
+ * @returns Promise that resolves when properties are found, or rejects if not found after max retries
1848
+ */
1849
+ const verifyPropertiesPersisted = async (db, modelId, modelName, maxRetries = 10, retryDelay = 100) => {
1850
+ const _mod_0 = await import('./BasePathResolver-DRT09oVv.js');
1851
+ const _ns_0 = _mod_0.N;
1852
+ const modelSchemaMod = _ns_0;
1853
+ const { properties: propertiesTable } = modelSchemaMod;
1854
+ const drizzleMod = await import('drizzle-orm');
1855
+ const { eq } = drizzleMod;
1856
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1857
+ const props = await db
1858
+ .select()
1859
+ .from(propertiesTable)
1860
+ .where(eq(propertiesTable.modelId, modelId))
1861
+ .limit(1);
1862
+ if (props.length > 0) {
1863
+ logger$2(`Verified properties exist for model "${modelName}" (modelId: ${modelId}) after ${attempt + 1} attempt(s)`);
1864
+ return;
1865
+ }
1866
+ if (attempt < maxRetries - 1) {
1867
+ logger$2(`Properties not yet visible for model "${modelName}" (modelId: ${modelId}), retrying in ${retryDelay}ms (attempt ${attempt + 1}/${maxRetries})`);
1868
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
1869
+ }
1870
+ }
1871
+ throw new Error(`Properties not found for model "${modelName}" (modelId: ${modelId}) after ${maxRetries} attempts. This may indicate a database write issue in the browser environment.`);
1872
+ };
1873
+ /**
1874
+ * Compare two schema files structurally, ignoring metadata timestamps
1875
+ * This is used to determine if an existing schema file matches the schema being imported
1876
+ * @param schema1 - First schema to compare
1877
+ * @param schema2 - Second schema to compare
1878
+ * @returns true if schemas are structurally equivalent, false otherwise
1879
+ */
1880
+ function compareSchemasStructurally(schema1, schema2) {
1881
+ // Compare basic fields
1882
+ if (schema1.$schema !== schema2.$schema)
1883
+ return false;
1884
+ if (schema1.version !== schema2.version)
1885
+ return false;
1886
+ if (schema1.id !== schema2.id)
1887
+ return false;
1888
+ // Compare metadata (ignoring timestamps)
1889
+ if (schema1.metadata?.name !== schema2.metadata?.name)
1890
+ return false;
1891
+ // Compare models structure
1892
+ const models1 = schema1.models || {};
1893
+ const models2 = schema2.models || {};
1894
+ const modelNames1 = Object.keys(models1).sort();
1895
+ const modelNames2 = Object.keys(models2).sort();
1896
+ if (modelNames1.length !== modelNames2.length)
1897
+ return false;
1898
+ if (!modelNames1.every((name, i) => name === modelNames2[i]))
1899
+ return false;
1900
+ // Compare each model's structure
1901
+ for (const modelName of modelNames1) {
1902
+ const model1 = models1[modelName];
1903
+ const model2 = models2[modelName];
1904
+ // Compare model IDs if both have them
1905
+ if (model1.id !== model2.id)
1906
+ return false;
1907
+ // Compare properties
1908
+ const props1 = model1.properties || {};
1909
+ const props2 = model2.properties || {};
1910
+ const propNames1 = Object.keys(props1).sort();
1911
+ const propNames2 = Object.keys(props2).sort();
1912
+ if (propNames1.length !== propNames2.length)
1913
+ return false;
1914
+ if (!propNames1.every((name, i) => name === propNames2[i]))
1915
+ return false;
1916
+ // Compare each property's structure
1917
+ for (const propName of propNames1) {
1918
+ const prop1 = props1[propName];
1919
+ const prop2 = props2[propName];
1920
+ // Compare property IDs if both have them
1921
+ if (prop1.id !== prop2.id)
1922
+ return false;
1923
+ // Compare property types
1924
+ if (prop1.type !== prop2.type)
1925
+ return false;
1926
+ // Compare other relevant fields (ref, refValueType for List, etc.)
1927
+ if (prop1.ref !== prop2.ref)
1928
+ return false;
1929
+ const refVal1 = prop1.refValueType ?? prop1.items?.type;
1930
+ const refVal2 = prop2.refValueType ?? prop2.items?.type;
1931
+ if (refVal1 !== refVal2)
1932
+ return false;
1933
+ const model1 = prop1.ref ?? prop1.items?.model;
1934
+ const model2 = prop2.ref ?? prop2.items?.model;
1935
+ if (model1 !== model2)
1936
+ return false;
1937
+ }
1938
+ }
1939
+ // Compare enums
1940
+ const enums1 = schema1.enums || {};
1941
+ const enums2 = schema2.enums || {};
1942
+ const enumNames1 = Object.keys(enums1).sort();
1943
+ const enumNames2 = Object.keys(enums2).sort();
1944
+ if (enumNames1.length !== enumNames2.length)
1945
+ return false;
1946
+ if (!enumNames1.every((name, i) => name === enumNames2[i]))
1947
+ return false;
1948
+ // Note: We don't compare migrations as they're historical records
1949
+ // and may differ even for the same schema structure
1950
+ return true;
1951
+ }
1952
+ /**
1953
+ * Create a canonical JSON string for schema comparison (ignores IDs and timestamps).
1954
+ * Used to detect if schema content has changed via string comparison of schemaData.
1955
+ */
1956
+ function getSchemaContentForComparison(schema) {
1957
+ const normalized = {
1958
+ name: schema.metadata?.name,
1959
+ version: schema.version,
1960
+ models: {},
1961
+ enums: schema.enums || {},
1962
+ };
1963
+ for (const modelName of Object.keys(schema.models || {}).sort()) {
1964
+ const model = schema.models[modelName];
1965
+ const props = {};
1966
+ for (const propName of Object.keys(model.properties || {}).sort()) {
1967
+ const prop = model.properties[propName];
1968
+ const items = prop.items;
1969
+ props[propName] = {
1970
+ type: prop.type ?? prop.dataType,
1971
+ ref: prop.ref ?? prop.refModelName,
1972
+ refValueType: prop.refValueType ?? items?.type,
1973
+ model: prop.model ?? items?.model,
1974
+ storage: prop.storage,
1975
+ };
1976
+ }
1977
+ normalized.models[modelName] = {
1978
+ description: model.description,
1979
+ properties: props,
1980
+ };
1981
+ }
1982
+ return JSON.stringify(normalized);
1983
+ }
1984
+ /**
1985
+ * Compare two schemas by structure (ignoring IDs and timestamps).
1986
+ * Returns true if schema content is equivalent.
1987
+ */
1988
+ function compareSchemaContent(schema1, schema2) {
1989
+ return getSchemaContentForComparison(schema1) === getSchemaContentForComparison(schema2);
1990
+ }
1991
+ /**
1992
+ * Generate a deterministic schemaFileId from schema content.
1993
+ * Same schema content always produces the same id, preventing duplicate schema files
1994
+ * when processSchemaFiles runs multiple times (e.g. React StrictMode, app restarts).
1995
+ */
1996
+ async function getDeterministicSchemaFileId(schemaFile) {
1997
+ const content = getSchemaContentForComparison(schemaFile);
1998
+ const hash = await getContentHash(content);
1999
+ return hash.slice(0, 10);
2000
+ }
2001
+ /**
2002
+ * Transform import JSON format to full schema file format
2003
+ * @param importData - The JSON import data
2004
+ * @param version - The version number for the schema (defaults to 1)
2005
+ * @returns The transformed schema file format
2006
+ */
2007
+ const transformImportToSchemaFile = (importData, version = 1) => {
2008
+ const now = new Date().toISOString();
2009
+ const schemaId = generateId();
2010
+ // Generate IDs for models and properties
2011
+ const modelsWithIds = {};
2012
+ for (const [modelName, modelDef] of Object.entries(importData.models)) {
2013
+ const modelId = generateId();
2014
+ const propertiesWithIds = {};
2015
+ for (const [propertyName, propertyDef] of Object.entries(modelDef.properties)) {
2016
+ const propertyId = generateId();
2017
+ const prop = propertyDef;
2018
+ const isList = (prop.type ?? prop.dataType) === 'List';
2019
+ let normalized = { ...prop, id: propertyId };
2020
+ // Normalize List: convert legacy items to refValueType/ref (case-insensitive)
2021
+ const existingRefValueType = getRefValueType(prop);
2022
+ if (isList && existingRefValueType) {
2023
+ normalized = { ...normalized, refValueType: normalizeDataType(existingRefValueType) };
2024
+ const items = prop.items;
2025
+ if (items?.model && !normalized.ref)
2026
+ normalized.ref = items.model;
2027
+ }
2028
+ propertiesWithIds[propertyName] = normalized;
2029
+ }
2030
+ modelsWithIds[modelName] = {
2031
+ ...modelDef,
2032
+ id: modelId,
2033
+ properties: propertiesWithIds,
2034
+ };
2035
+ }
2036
+ return {
2037
+ $schema: 'https://seedprotocol.org/schemas/data-model/v1',
2038
+ version,
2039
+ id: schemaId,
2040
+ metadata: {
2041
+ name: importData.name,
2042
+ createdAt: now,
2043
+ updatedAt: now,
2044
+ },
2045
+ models: modelsWithIds,
2046
+ enums: {},
2047
+ migrations: [
2048
+ {
2049
+ version,
2050
+ timestamp: now,
2051
+ description: 'Initial schema',
2052
+ changes: [],
2053
+ },
2054
+ ],
2055
+ };
2056
+ };
2057
+ /**
2058
+ * Parse JSON import content (string) into JsonImportSchema format
2059
+ * Supports two formats:
2060
+ * 1. Minimal format: { name: string, models: {...} }
2061
+ * 2. Complete schema format: { $schema: string, metadata: { name: string }, models: {...}, ... }
2062
+ * @param content - The JSON content as a string
2063
+ * @returns The parsed JSON import data normalized to JsonImportSchema format
2064
+ * @throws Error if content cannot be parsed
2065
+ */
2066
+ const parseJsonImportContent = (content) => {
2067
+ try {
2068
+ const data = JSON.parse(content);
2069
+ // Determine the format and extract name
2070
+ let schemaName;
2071
+ // Check if it's a complete schema format (has $schema field)
2072
+ if (data.$schema) {
2073
+ // Complete schema format: name is in metadata.name
2074
+ schemaName = data.metadata?.name;
2075
+ }
2076
+ else {
2077
+ // Minimal format: name is at top level
2078
+ schemaName = data.name;
2079
+ }
2080
+ if (!schemaName) {
2081
+ throw new Error('Schema name is required (either at top level or in metadata.name)');
2082
+ }
2083
+ // Return normalized JsonImportSchema format
2084
+ const models = normalizeSchemaModels(data.models);
2085
+ return {
2086
+ name: schemaName,
2087
+ models,
2088
+ };
2089
+ }
2090
+ catch (error) {
2091
+ if (error instanceof SyntaxError) {
2092
+ throw new Error(`Invalid JSON content: ${error.message}`);
2093
+ }
2094
+ throw new Error(`Failed to parse JSON import content: ${error instanceof Error ? error.message : String(error)}`);
2095
+ }
2096
+ };
2097
+ /**
2098
+ * Read and parse a JSON import file
2099
+ * Supports two formats:
2100
+ * 1. Minimal format: { name: string, models: {...} }
2101
+ * 2. Complete schema format: { $schema: string, metadata: { name: string }, models: {...}, ... }
2102
+ * @param filePath - Path to the JSON import file
2103
+ * @returns The parsed JSON import data normalized to JsonImportSchema format
2104
+ * @throws Error if file cannot be read or parsed
2105
+ */
2106
+ const readJsonImportFile = async (filePath) => {
2107
+ try {
2108
+ const workingDir = BaseFileManager.getWorkingDir();
2109
+ let internalFilePath = filePath;
2110
+ if (!filePath.startsWith(workingDir)) {
2111
+ internalFilePath = `${workingDir}/${filePath}`;
2112
+ }
2113
+ const content = await BaseFileManager.readFileAsString(internalFilePath);
2114
+ return parseJsonImportContent(content);
2115
+ }
2116
+ catch (error) {
2117
+ if (error instanceof SyntaxError) {
2118
+ throw new Error(`Invalid JSON in file ${filePath}: ${error.message}`);
2119
+ }
2120
+ throw new Error(`Failed to read JSON import file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2121
+ }
2122
+ };
2123
+ /**
2124
+ * Sanitize a schema name to be filesystem-safe
2125
+ * Replaces all special characters (except alphanumeric, hyphens, underscores) with underscores
2126
+ * Converts spaces to underscores
2127
+ * Removes leading/trailing underscores
2128
+ *
2129
+ * @param name - Schema name to sanitize
2130
+ * @returns Sanitized name safe for use in filenames
2131
+ */
2132
+ const sanitizeSchemaName = (name) => {
2133
+ return name
2134
+ .replace(/[^a-zA-Z0-9\s_-]/g, '_') // Replace special chars (except spaces, hyphens, underscores) with underscore
2135
+ .replace(/\s+/g, '_') // Convert spaces to underscores
2136
+ .replace(/^_+|_+$/g, '') // Remove leading/trailing underscores
2137
+ .replace(/_+/g, '_'); // Collapse multiple underscores to single
2138
+ };
2139
+ /**
2140
+ * Get the full file path for a schema file
2141
+ * Format: {schemaFileId}_{schemaName}_v{version}.json
2142
+ *
2143
+ * The ID-first format ensures all files for a schema group together when sorted alphabetically.
2144
+ *
2145
+ * @param workingDir - Working directory path
2146
+ * @param name - Schema name
2147
+ * @param version - Schema version
2148
+ * @param schemaFileId - Schema file ID (required)
2149
+ */
2150
+ const getSchemaFilePath = (workingDir, name, version, schemaFileId) => {
2151
+ const path = BaseFileManager.getPathModule();
2152
+ const sanitizedName = sanitizeSchemaName(name);
2153
+ const filename = `${schemaFileId}_${sanitizedName}_v${version}.json`;
2154
+ return path.join(workingDir, filename);
2155
+ };
2156
+ async function importJsonSchema(importFilePathOrContents, version = 1) {
2157
+ // Determine if we have a file path or file contents
2158
+ let importData;
2159
+ let importFilePath;
2160
+ let originalSchemaFile;
2161
+ if (typeof importFilePathOrContents === 'string') {
2162
+ // It's a file path
2163
+ importFilePath = importFilePathOrContents;
2164
+ importData = await readJsonImportFile(importFilePath);
2165
+ }
2166
+ else {
2167
+ // It's file contents - check if it's already a complete schema format
2168
+ try {
2169
+ const parsed = JSON.parse(importFilePathOrContents.contents);
2170
+ if (parsed.$schema && parsed.metadata) {
2171
+ // It's already a complete schema format - preserve it (id may be missing, will be generated)
2172
+ originalSchemaFile = parsed;
2173
+ logger$2(`Input is complete schema format (${originalSchemaFile.id ? `preserving ID: ${originalSchemaFile.id}` : 'id will be generated'})`);
2174
+ }
2175
+ }
2176
+ catch {
2177
+ // Not valid JSON or not a complete schema, parse as import format
2178
+ }
2179
+ if (!originalSchemaFile) {
2180
+ importData = parseJsonImportContent(importFilePathOrContents.contents);
2181
+ }
2182
+ }
2183
+ // Use original schema file if available, otherwise transform from import format
2184
+ let schemaFile;
2185
+ if (originalSchemaFile) {
2186
+ schemaFile = originalSchemaFile;
2187
+ // Normalize models if provided as array (some tools output array format)
2188
+ if (schemaFile.models) {
2189
+ schemaFile.models = normalizeSchemaModels(schemaFile.models);
2190
+ }
2191
+ // Use provided version if different, otherwise use version from schema
2192
+ if (version !== undefined && version !== schemaFile.version) {
2193
+ schemaFile = { ...schemaFile, version };
2194
+ }
2195
+ // Check DB for existing schema by name+version (same as minimal path)
2196
+ const db = BaseDb.getAppDb();
2197
+ if (db) {
2198
+ const _mod_1 = await import('./BasePathResolver-DRT09oVv.js');
2199
+ const _ns_1 = _mod_1.Q;
2200
+ const schemaSchemaMod = _ns_1;
2201
+ const { schemas: schemasTable } = schemaSchemaMod;
2202
+ const drizzleMod = await import('drizzle-orm');
2203
+ const { eq, and, desc } = drizzleMod;
2204
+ const existingByName = await db
2205
+ .select()
2206
+ .from(schemasTable)
2207
+ .where(and(eq(schemasTable.name, schemaFile.metadata.name), eq(schemasTable.version, schemaFile.version ?? version)))
2208
+ .orderBy(desc(schemasTable.isDraft))
2209
+ .limit(1);
2210
+ if (existingByName.length > 0 && existingByName[0].schemaData) {
2211
+ const existing = existingByName[0];
2212
+ logger$2(`Idempotency: found existing schema by name+version: ${schemaFile.metadata.name} v${schemaFile.version ?? version}`);
2213
+ const existingSchema = JSON.parse(existing.schemaData);
2214
+ const contentMatches = compareSchemaContent(schemaFile, existingSchema);
2215
+ logger$2(`Idempotency: compareSchemaContent=${contentMatches} for ${schemaFile.metadata.name}`);
2216
+ if (contentMatches) {
2217
+ if (existing.schemaFileId) {
2218
+ const workingDir = BaseFileManager.getWorkingDir();
2219
+ const existingFilePath = getSchemaFilePath(workingDir, schemaFile.metadata.name, schemaFile.version ?? version, existing.schemaFileId);
2220
+ if (await BaseFileManager.pathExists(existingFilePath)) {
2221
+ logger$2(`Schema ${schemaFile.metadata.name} v${schemaFile.version} already exists with same content, loading existing file`);
2222
+ return await loadSchemaFromFile(existingFilePath);
2223
+ }
2224
+ await BaseFileManager.createDirIfNotExists(workingDir);
2225
+ await BaseFileManager.saveFile(existingFilePath, existing.schemaData);
2226
+ await BaseFileManager.waitForFileWithContent(existingFilePath, 100, 5000);
2227
+ return await loadSchemaFromFile(existingFilePath);
2228
+ }
2229
+ logger$2(`Schema ${schemaFile.metadata.name} v${schemaFile.version} already exists with same content (no schemaFileId), using DB`);
2230
+ return '';
2231
+ }
2232
+ }
2233
+ }
2234
+ // Add metadata defaults when missing (prevents NaN in addSchemaToDb)
2235
+ const now = new Date().toISOString();
2236
+ if (!schemaFile.metadata.createdAt)
2237
+ schemaFile.metadata.createdAt = now;
2238
+ if (!schemaFile.metadata.updatedAt)
2239
+ schemaFile.metadata.updatedAt = now;
2240
+ // Generate missing IDs for complete schema files.
2241
+ // Use deterministic id from content hash to prevent duplicate schema files when
2242
+ // processSchemaFiles runs multiple times (React StrictMode, app restarts).
2243
+ if (!schemaFile.id) {
2244
+ schemaFile.id = await getDeterministicSchemaFileId(schemaFile);
2245
+ logger$2(`Generated deterministic schema ID for ${schemaFile.metadata.name}:`, schemaFile.id);
2246
+ }
2247
+ // Before generating IDs, check if a file already exists with this schemaFileId
2248
+ // This handles the case where the schema was already loaded via loadSchemaFromFile
2249
+ const workingDir = BaseFileManager.getWorkingDir();
2250
+ BaseFileManager.getPathModule();
2251
+ const existingFilePath = getSchemaFilePath(workingDir, schemaFile.metadata.name, schemaFile.version, schemaFile.id);
2252
+ const existingFileExists = await BaseFileManager.pathExists(existingFilePath);
2253
+ if (existingFileExists) {
2254
+ // File already exists with this schemaFileId - just load it instead of creating a new one
2255
+ logger$2(`Schema file already exists with schemaFileId ${schemaFile.id}, loading it instead of creating new file`);
2256
+ return await loadSchemaFromFile(existingFilePath);
2257
+ }
2258
+ // Generate missing model and property IDs.
2259
+ // Use deterministic IDs (from schema content hash) so addModelsToDb finds existing
2260
+ // records when processSchemaFiles runs multiple times with different DB/file contexts.
2261
+ // Hash the schema content once: rehashing it per id is quadratic in schema size.
2262
+ const getDeterministicId = getDeterministicIdsWithPrefix(getSchemaContentForComparison(schemaFile));
2263
+ for (const [modelName, model] of Object.entries(schemaFile.models || {})) {
2264
+ if (!model.id) {
2265
+ model.id = getDeterministicId(modelName);
2266
+ logger$2(`Generated deterministic model ID for ${modelName}:`, model.id);
2267
+ }
2268
+ for (const [propName, prop] of Object.entries(model.properties || {})) {
2269
+ if (!prop.id) {
2270
+ prop.id = getDeterministicId(modelName + propName);
2271
+ logger$2(`Generated deterministic property ID for ${modelName}.${propName}:`, prop.id);
2272
+ }
2273
+ }
2274
+ }
2275
+ }
2276
+ else {
2277
+ if (!importData) {
2278
+ throw new Error('Failed to parse import data: neither complete schema nor import format could be determined');
2279
+ }
2280
+ // For minimal imports: check DB for existing schema before creating new file
2281
+ const db = BaseDb.getAppDb();
2282
+ if (db) {
2283
+ const _mod_2 = await import('./BasePathResolver-DRT09oVv.js');
2284
+ const _ns_2 = _mod_2.Q;
2285
+ const schemaSchemaMod = _ns_2;
2286
+ const { schemas: schemasTable } = schemaSchemaMod;
2287
+ const drizzleMod = await import('drizzle-orm');
2288
+ const { eq, and, desc } = drizzleMod;
2289
+ const existingByName = await db
2290
+ .select()
2291
+ .from(schemasTable)
2292
+ .where(and(eq(schemasTable.name, importData.name), eq(schemasTable.version, version)))
2293
+ .orderBy(desc(schemasTable.isDraft))
2294
+ .limit(1);
2295
+ if (existingByName.length > 0) {
2296
+ const existing = existingByName[0];
2297
+ const schemaFileForCompare = transformImportToSchemaFile(importData, version);
2298
+ if (existing.schemaData) {
2299
+ try {
2300
+ const existingSchema = JSON.parse(existing.schemaData);
2301
+ if (compareSchemaContent(schemaFileForCompare, existingSchema)) {
2302
+ // Same content - use existing schema
2303
+ if (existing.schemaFileId) {
2304
+ const workingDir = BaseFileManager.getWorkingDir();
2305
+ const existingFilePath = getSchemaFilePath(workingDir, importData.name, version, existing.schemaFileId);
2306
+ if (await BaseFileManager.pathExists(existingFilePath)) {
2307
+ logger$2(`Schema ${importData.name} v${version} already exists with same content, loading existing file`);
2308
+ return await loadSchemaFromFile(existingFilePath);
2309
+ }
2310
+ // File missing but schemaData exists - restore file from schemaData
2311
+ await BaseFileManager.createDirIfNotExists(workingDir);
2312
+ await BaseFileManager.saveFile(existingFilePath, existing.schemaData);
2313
+ await BaseFileManager.waitForFileWithContent(existingFilePath, 100, 5000);
2314
+ logger$2(`Restored schema file from schemaData for ${importData.name} v${version}`);
2315
+ return await loadSchemaFromFile(existingFilePath);
2316
+ }
2317
+ // No schemaFileId - restore file from schemaData using id from parsed schema
2318
+ const schemaIdForPath = existingSchema.id || existing.schemaFileId;
2319
+ if (schemaIdForPath) {
2320
+ const workingDir = BaseFileManager.getWorkingDir();
2321
+ const existingFilePath = getSchemaFilePath(workingDir, importData.name, version, schemaIdForPath);
2322
+ await BaseFileManager.createDirIfNotExists(workingDir);
2323
+ await BaseFileManager.saveFile(existingFilePath, existing.schemaData);
2324
+ await BaseFileManager.waitForFileWithContent(existingFilePath, 100, 5000);
2325
+ logger$2(`Restored schema file from schemaData for ${importData.name} v${version}`);
2326
+ return await loadSchemaFromFile(existingFilePath);
2327
+ }
2328
+ logger$2(`Schema ${importData.name} v${version} already exists with same content (no id), using DB`);
2329
+ return '';
2330
+ }
2331
+ // Content differs - create new version
2332
+ const latestVersionRows = await db
2333
+ .select({ version: schemasTable.version })
2334
+ .from(schemasTable)
2335
+ .where(eq(schemasTable.name, importData.name))
2336
+ .orderBy(desc(schemasTable.version))
2337
+ .limit(1);
2338
+ const newVersion = latestVersionRows.length > 0 ? latestVersionRows[0].version + 1 : version;
2339
+ logger$2(`Schema ${importData.name} content changed, creating new version v${newVersion}`);
2340
+ schemaFile = transformImportToSchemaFile(importData, newVersion);
2341
+ version = newVersion;
2342
+ }
2343
+ catch {
2344
+ // Parse error - proceed with normal import
2345
+ schemaFile = schemaFileForCompare;
2346
+ }
2347
+ }
2348
+ else {
2349
+ schemaFile = schemaFileForCompare;
2350
+ }
2351
+ }
2352
+ else {
2353
+ schemaFile = transformImportToSchemaFile(importData, version);
2354
+ }
2355
+ }
2356
+ else {
2357
+ schemaFile = transformImportToSchemaFile(importData, version);
2358
+ }
2359
+ }
2360
+ // Check if this is an internal SDK schema (should not create files in app directory)
2361
+ const isInternal = isInternalSchema(schemaFile.metadata.name, schemaFile.id);
2362
+ if (isInternal) {
2363
+ logger$2(`Skipping file creation for internal schema: ${schemaFile.metadata.name}`);
2364
+ // For internal schemas, just load to database and store, don't create file
2365
+ // Extract schema name and version
2366
+ const schemaName = schemaFile.metadata?.name;
2367
+ const version = schemaFile.version;
2368
+ if (!schemaName) {
2369
+ throw new Error('Schema name is required in metadata.name');
2370
+ }
2371
+ // Convert to JsonImportSchema format for processing
2372
+ const importDataForInternal = {
2373
+ name: schemaName,
2374
+ models: Object.fromEntries(Object.entries(schemaFile.models || {}).map(([modelName, model]) => [
2375
+ modelName,
2376
+ {
2377
+ ...model,
2378
+ // Remove id field for import format
2379
+ id: undefined,
2380
+ properties: Object.fromEntries(Object.entries(model.properties || {}).map(([propName, prop]) => {
2381
+ const schemaProp = prop;
2382
+ const jsonProp = {
2383
+ type: schemaProp.dataType || schemaProp.type,
2384
+ // Remove id field for import format
2385
+ id: undefined,
2386
+ };
2387
+ // Handle Relation type - convert ref to model (not for List)
2388
+ const rawDataType = (schemaProp.dataType ?? schemaProp.type ?? '').toString().toLowerCase();
2389
+ if (rawDataType !== 'list' && (schemaProp.ref || schemaProp.refModelName)) {
2390
+ jsonProp.model = schemaProp.refModelName || schemaProp.ref;
2391
+ }
2392
+ // Handle List type (support both refValueType and legacy items, case-insensitive dataType)
2393
+ if (rawDataType === 'list') {
2394
+ let refValueType = getRefValueType(schemaProp);
2395
+ // When ref is present but refValueType is not, derive refValueType as "Relation" (list of Relations)
2396
+ if (!refValueType && (schemaProp.ref ?? schemaProp.refModelName ?? schemaProp.items?.model)) {
2397
+ refValueType = 'Relation';
2398
+ }
2399
+ if (refValueType) {
2400
+ jsonProp.refValueType = refValueType;
2401
+ const ref = schemaProp.ref ?? schemaProp.refModelName ?? schemaProp.items?.model;
2402
+ if (ref)
2403
+ jsonProp.ref = ref;
2404
+ }
2405
+ }
2406
+ // Copy other properties (storage, etc.)
2407
+ Object.keys(schemaProp).forEach(key => {
2408
+ if (key !== 'id' && key !== 'dataType' && key !== 'type' && key !== 'ref' && key !== 'refModelName' && key !== 'refValueType') {
2409
+ jsonProp[key] = schemaProp[key];
2410
+ }
2411
+ });
2412
+ return [propName, jsonProp];
2413
+ })),
2414
+ },
2415
+ ])),
2416
+ };
2417
+ // Use fixed internal schema ID if missing (never generate for internal)
2418
+ if (!schemaFile.id) {
2419
+ schemaFile.id = INTERNAL_SCHEMA_IDS[0];
2420
+ logger$2('Using fixed internal schema ID:', schemaFile.id);
2421
+ }
2422
+ // Extract schemaFileIds from JSON file and generate missing ones BEFORE creating models
2423
+ // This ensures Model instances are created with correct IDs
2424
+ const modelFileIds = new Map();
2425
+ const propertyFileIds = new Map();
2426
+ for (const [modelName, model] of Object.entries(schemaFile.models || {})) {
2427
+ // Generate model ID if missing
2428
+ if (!model.id) {
2429
+ model.id = generateId();
2430
+ logger$2(`Generated model ID for ${modelName}:`, model.id);
2431
+ // Update the schemaFile object so it's included in schemaData
2432
+ schemaFile.models[modelName].id = model.id;
2433
+ }
2434
+ modelFileIds.set(modelName, model.id);
2435
+ const propIds = new Map();
2436
+ for (const [propName, prop] of Object.entries(model.properties || {})) {
2437
+ // Generate property ID if missing
2438
+ if (!prop.id) {
2439
+ prop.id = generateId();
2440
+ logger$2(`Generated property ID for ${modelName}.${propName}:`, prop.id);
2441
+ // Update the schemaFile object so it's included in schemaData
2442
+ schemaFile.models[modelName].properties[propName].id = prop.id;
2443
+ }
2444
+ propIds.set(propName, prop.id);
2445
+ }
2446
+ if (propIds.size > 0) {
2447
+ propertyFileIds.set(modelName, propIds);
2448
+ }
2449
+ }
2450
+ // Convert JSON models to Model classes, passing modelFileIds and propertyFileIds so Model instances use correct IDs
2451
+ const modelDefinitions = await createModelsFromJson(importDataForInternal, modelFileIds, propertyFileIds);
2452
+ logger$2('loadSchemaFromFile (internal) - modelDefinitions length:', Object.keys(modelDefinitions).length);
2453
+ // Convert schema file metadata to schema input for database
2454
+ const schemaInput = {
2455
+ name: schemaName,
2456
+ version,
2457
+ schemaFileId: schemaFile.id || null,
2458
+ createdAt: new Date(schemaFile.metadata.createdAt).getTime(),
2459
+ updatedAt: new Date(schemaFile.metadata.updatedAt).getTime(),
2460
+ };
2461
+ // Use dynamic import to break circular dependency
2462
+ const _mod_3 = await import('./db-Lw0KVZmc.js');
2463
+ const _ns_3 = _mod_3.Z;
2464
+ const dbMod = _ns_3;
2465
+ const { addSchemaToDb, addModelsToDb } = dbMod;
2466
+ // Try to add schema and models to database if database is available
2467
+ try {
2468
+ const db = BaseDb.getAppDb();
2469
+ if (db) {
2470
+ // Store full schema data in database as fallback when file is not available
2471
+ // schemaFile already has all IDs (from JSON file or generated), so we can use it directly
2472
+ const schemaData = JSON.stringify(schemaFile, null, 2);
2473
+ // Add schema to database (creates or returns existing) with schemaFileId and schemaData
2474
+ const schemaRecord = await addSchemaToDb(schemaInput, schemaFile.id, schemaData, false);
2475
+ // Add models to database and link them to the schema with schemaFileIds (only if there are models)
2476
+ if (Object.keys(modelDefinitions).length > 0) {
2477
+ await addModelsToDb(modelDefinitions, schemaRecord, undefined, {
2478
+ schemaFileId: schemaFile.id,
2479
+ modelFileIds,
2480
+ propertyFileIds,
2481
+ });
2482
+ // Verify expected models are linked (single attempt - addModelsToDb already confirmed)
2483
+ const expectedModelNames = Object.keys(schemaFile.models || {});
2484
+ const _mod_4 = await import('./BasePathResolver-DRT09oVv.js');
2485
+ const _ns_4 = _mod_4.R;
2486
+ const modelSchemaSchemaMod = _ns_4;
2487
+ const { modelSchemas } = modelSchemaSchemaMod;
2488
+ const _mod_5 = await import('./BasePathResolver-DRT09oVv.js');
2489
+ const _ns_5 = _mod_5.N;
2490
+ const modelSchemaMod = _ns_5;
2491
+ const { models: modelsTable } = modelSchemaMod;
2492
+ const drizzleMod = await import('drizzle-orm');
2493
+ const { eq, and, isNull } = drizzleMod;
2494
+ const modelLinks = await db
2495
+ .select({
2496
+ modelId: modelSchemas.modelId,
2497
+ modelName: modelsTable.name,
2498
+ })
2499
+ .from(modelSchemas)
2500
+ .innerJoin(modelsTable, eq(modelSchemas.modelId, modelsTable.id))
2501
+ .where(eq(modelSchemas.schemaId, schemaRecord.id));
2502
+ const linkedModelNames = modelLinks
2503
+ .map((link) => link.modelName)
2504
+ .filter((n) => n !== null);
2505
+ const missingModels = expectedModelNames.filter(name => !linkedModelNames.includes(name));
2506
+ const allModelsLinked = missingModels.length === 0;
2507
+ if (!allModelsLinked) {
2508
+ console.warn(`[importJsonSchema] WARNING: Not all expected models are linked. Expected: ${expectedModelNames.join(', ')}, Linked: ${linkedModelNames.join(', ')}, Missing: ${missingModels.join(', ') || 'none'}`);
2509
+ // Try to link missing models directly by finding them in the database
2510
+ if (missingModels.length > 0) {
2511
+ for (const missingModelName of missingModels) {
2512
+ try {
2513
+ const modelFileId = modelFileIds.get(missingModelName);
2514
+ if (modelFileId) {
2515
+ // Find model by schemaFileId
2516
+ const missingModel = await db
2517
+ .select()
2518
+ .from(modelsTable)
2519
+ .where(eq(modelsTable.schemaFileId, modelFileId))
2520
+ .limit(1);
2521
+ if (missingModel.length > 0 && missingModel[0].id) {
2522
+ // Check if join entry already exists
2523
+ const existingJoin = await db
2524
+ .select()
2525
+ .from(modelSchemas)
2526
+ .where(and(eq(modelSchemas.modelId, missingModel[0].id), eq(modelSchemas.schemaId, schemaRecord.id)))
2527
+ .limit(1);
2528
+ if (existingJoin.length === 0) {
2529
+ await linkModelToSchema(db, missingModel[0].id, schemaRecord.id);
2530
+ }
2531
+ else {
2532
+ }
2533
+ }
2534
+ else {
2535
+ console.warn(`[importJsonSchema] Could not find model "${missingModelName}" with schemaFileId "${modelFileId}" in database`);
2536
+ }
2537
+ }
2538
+ else {
2539
+ // Fallback: find by name, but only a stub no schema claims (model names are only
2540
+ // unique per schema; an unlinked row with a schemaFileId is a deleted schema's leftover)
2541
+ const missingModel = await db
2542
+ .select({ id: modelsTable.id })
2543
+ .from(modelsTable)
2544
+ .leftJoin(modelSchemas, eq(modelsTable.id, modelSchemas.modelId))
2545
+ .where(and(eq(modelsTable.name, missingModelName), isNull(modelSchemas.id), isNull(modelsTable.schemaFileId)))
2546
+ .limit(1);
2547
+ if (missingModel.length > 0 && missingModel[0].id) {
2548
+ const existingJoin = await db
2549
+ .select()
2550
+ .from(modelSchemas)
2551
+ .where(and(eq(modelSchemas.modelId, missingModel[0].id), eq(modelSchemas.schemaId, schemaRecord.id)))
2552
+ .limit(1);
2553
+ if (existingJoin.length === 0) {
2554
+ await linkModelToSchema(db, missingModel[0].id, schemaRecord.id);
2555
+ }
2556
+ }
2557
+ else {
2558
+ console.warn(`[importJsonSchema] Could not find model "${missingModelName}" by name in database`);
2559
+ }
2560
+ }
2561
+ }
2562
+ catch (error) {
2563
+ console.error(`[importJsonSchema] Error linking missing model "${missingModelName}":`, error?.message || String(error));
2564
+ }
2565
+ }
2566
+ }
2567
+ }
2568
+ // Skip expensive verifyPropertiesPersisted - addModelsToDb already persisted; verification was for browser quirks
2569
+ // Skip "After properties" block for internal schema: addSchemaToDb/addModelsToDb already stored
2570
+ // correct schemaData. The per-model ID sync (6 model + 6 property selects + schema update) is
2571
+ // redundant during init and causes slow OPFS writes. Ad-hoc loadSchemaFromFile uses the same
2572
+ // applySchemaToDb path but runs when DB is warm.
2573
+ }
2574
+ }
2575
+ }
2576
+ catch (dbError) {
2577
+ // Database not available - log warning but continue
2578
+ logger$2('Database not available, skipping database operations:', dbError instanceof Error ? dbError.message : String(dbError));
2579
+ }
2580
+ // Models are now Model instances, no registration needed
2581
+ // They should be created via Model.create() and are accessible via Model static methods
2582
+ for (const [modelName] of Object.entries(modelDefinitions)) {
2583
+ logger$2('loadSchemaFromFile (internal) - model available:', modelName);
2584
+ }
2585
+ // Schema is now saved to database
2586
+ // useSchemas hook will pick it up via database queries
2587
+ return ''; // Return empty string to indicate no file was created
2588
+ }
2589
+ const workingDir = BaseFileManager.getWorkingDir();
2590
+ const path = BaseFileManager.getPathModule();
2591
+ // Ensure schema ID is defined (should always be set by this point, but check for safety)
2592
+ if (!schemaFile.id) {
2593
+ schemaFile.id = generateId();
2594
+ logger$2('Generated schema ID before file path creation:', schemaFile.id);
2595
+ }
2596
+ // Get the target file path using ID-based naming (preferred)
2597
+ const filePath = getSchemaFilePath(workingDir, schemaFile.metadata.name, version, schemaFile.id);
2598
+ // Check if schema already exists
2599
+ const exists = await BaseFileManager.pathExists(filePath);
2600
+ if (exists) {
2601
+ // If we have a file path and the import file is the same as the target file,
2602
+ // and it's already a complete schema, just load it without writing
2603
+ if (importFilePath) {
2604
+ const normalizedImportPath = path.resolve(importFilePath);
2605
+ const normalizedTargetPath = path.resolve(filePath);
2606
+ if (normalizedImportPath === normalizedTargetPath) {
2607
+ // Check if the file is already a complete schema
2608
+ try {
2609
+ const content = await BaseFileManager.readFileAsString(importFilePath);
2610
+ const data = JSON.parse(content);
2611
+ if (data.$schema) {
2612
+ // File is already a complete schema, just load it
2613
+ logger$2(`Schema file ${filePath} already exists and is complete, loading it`);
2614
+ return await loadSchemaFromFile(filePath);
2615
+ }
2616
+ }
2617
+ catch (error) {
2618
+ // If we can't read the file, proceed with the error
2619
+ }
2620
+ }
2621
+ }
2622
+ // File exists - check if it matches the schema we're trying to import
2623
+ try {
2624
+ const existingContent = await BaseFileManager.readFileAsString(filePath);
2625
+ const existingSchema = JSON.parse(existingContent);
2626
+ // Verify it's a complete schema file
2627
+ if (existingSchema.$schema) {
2628
+ // Compare schemas structurally
2629
+ if (compareSchemasStructurally(schemaFile, existingSchema)) {
2630
+ // Schemas match - just load the existing file
2631
+ logger$2(`Schema ${schemaFile.metadata.name} v${version} already exists with matching content, loading it`);
2632
+ return await loadSchemaFromFile(filePath);
2633
+ }
2634
+ else {
2635
+ // Schemas don't match - throw error to prevent data loss
2636
+ throw new Error(`Schema ${schemaFile.metadata.name} v${version} already exists with different content. ` +
2637
+ `To update the schema, delete the existing file first or use a different version.`);
2638
+ }
2639
+ }
2640
+ else {
2641
+ // File exists but is not a complete schema - this shouldn't happen with ID-based naming
2642
+ // but we'll treat it as a conflict
2643
+ throw new Error(`Schema ${schemaFile.metadata.name} v${version} already exists but file is not a complete schema`);
2644
+ }
2645
+ }
2646
+ catch (error) {
2647
+ // If error is already our custom error, re-throw it
2648
+ if (error instanceof Error && error.message.includes('already exists')) {
2649
+ throw error;
2650
+ }
2651
+ // If we can't read/parse the existing file, throw a generic error
2652
+ throw new Error(`Schema ${schemaFile.metadata.name} v${version} already exists but could not be read: ${error instanceof Error ? error.message : String(error)}`);
2653
+ }
2654
+ }
2655
+ // Ensure working directory exists
2656
+ await BaseFileManager.createDirIfNotExists(workingDir);
2657
+ // Write schema to file
2658
+ const content = JSON.stringify(schemaFile, null, 2);
2659
+ await BaseFileManager.saveFile(filePath, content);
2660
+ // Wait for the file to be available and readable before trying to read it
2661
+ // This is necessary in browser environments where file writes may not be immediately readable
2662
+ await BaseFileManager.waitForFileWithContent(filePath, 100, 5000);
2663
+ // Load the schema file to process models and add them to the store
2664
+ // This handles all model creation, database operations, and store updates
2665
+ await loadSchemaFromFile(filePath);
2666
+ // Schema is now saved to database
2667
+ // useSchemas hook will pick it up via database queries
2668
+ return filePath;
2669
+ }
2670
+ /**
2671
+ * Apply schema and models to the database. Shared by loadSchemaFromFile and syncSchemaFromSource.
2672
+ * @internal
2673
+ */
2674
+ async function applySchemaToDb(schemaFile, modelDefinitions, modelFileIds, propertyFileIds) {
2675
+ const schemaName = schemaFile.metadata?.name;
2676
+ const version = schemaFile.version;
2677
+ if (!schemaName) {
2678
+ throw new Error('Schema name is required in metadata.name');
2679
+ }
2680
+ const schemaInput = {
2681
+ name: schemaName,
2682
+ version,
2683
+ schemaFileId: schemaFile.id || null,
2684
+ createdAt: new Date(schemaFile.metadata.createdAt).getTime(),
2685
+ updatedAt: new Date(schemaFile.metadata.updatedAt).getTime(),
2686
+ };
2687
+ const _mod_6 = await import('./db-Lw0KVZmc.js');
2688
+ const _ns_6 = _mod_6.Z;
2689
+ const dbMod = _ns_6;
2690
+ const { addSchemaToDb, addModelsToDb } = dbMod;
2691
+ const db = BaseDb.getAppDb();
2692
+ if (!db) {
2693
+ throw new Error('Database not found');
2694
+ }
2695
+ const schemaData = JSON.stringify(schemaFile, null, 2);
2696
+ const schemaRecord = await addSchemaToDb(schemaInput, schemaFile.id, schemaData, false);
2697
+ if (Object.keys(modelDefinitions).length > 0) {
2698
+ await addModelsToDb(modelDefinitions, schemaRecord, undefined, {
2699
+ schemaFileId: schemaFile.id,
2700
+ modelFileIds,
2701
+ propertyFileIds,
2702
+ });
2703
+ await new Promise(resolve => setTimeout(resolve, 200));
2704
+ }
2705
+ return { schemaRecord };
2706
+ }
2707
+ /**
2708
+ * Sync a canonical schema from a file path or inlined object to the database.
2709
+ * Uses add/update semantics: no "already exists with different content" errors, no ID-based file copy.
2710
+ * Called automatically at init when config.schema is provided.
2711
+ *
2712
+ * @param source - File path (string) or complete SchemaFileFormat object
2713
+ */
2714
+ async function syncSchemaFromSource(source) {
2715
+ if (typeof source === 'string') {
2716
+ const path = BaseFileManager.getPathModule();
2717
+ const resolvedPath = path.isAbsolute(source)
2718
+ ? source
2719
+ : isNode()
2720
+ ? path.resolve(process.cwd(), source)
2721
+ : path.join(BaseFileManager.getWorkingDir(), source);
2722
+ const content = await BaseFileManager.readFileAsString(resolvedPath);
2723
+ const parsed = JSON.parse(content);
2724
+ const version = parsed?.version ?? 1;
2725
+ await importJsonSchema({ contents: content }, version);
2726
+ }
2727
+ else {
2728
+ const version = source.version ?? 1;
2729
+ await importJsonSchema({ contents: JSON.stringify(source) }, version);
2730
+ }
2731
+ }
2732
+ /**
2733
+ * Load an existing complete schema file and process its models
2734
+ * This is used to load models from already-processed schema files into the store
2735
+ * @param schemaFilePath - Path to the complete schema file (must have $schema field)
2736
+ * @returns The file path of the schema
2737
+ * @throws Error if file cannot be read, parsed, or is not a complete schema
2738
+ */
2739
+ const loadSchemaFromFile = async (schemaFilePath) => {
2740
+ try {
2741
+ // Read and parse the schema file
2742
+ const content = await BaseFileManager.readFileAsString(schemaFilePath);
2743
+ const schemaFile = JSON.parse(content);
2744
+ // Normalize models if provided as array (some tools output array format)
2745
+ if (schemaFile.models) {
2746
+ schemaFile.models = normalizeSchemaModels(schemaFile.models);
2747
+ }
2748
+ // Verify it's a complete schema file
2749
+ if (!schemaFile.$schema) {
2750
+ throw new Error(`File ${schemaFilePath} is not a complete schema file (missing $schema field). Use importJsonSchema() for minimal format files.`);
2751
+ }
2752
+ // Extract schema name and version
2753
+ const schemaName = schemaFile.metadata?.name;
2754
+ const version = schemaFile.version;
2755
+ if (!schemaName) {
2756
+ throw new Error('Schema name is required in metadata.name');
2757
+ }
2758
+ // Convert to JsonImportSchema format for processing
2759
+ // Remove id fields for JsonImportSchema format (they're not part of the import format)
2760
+ const importData = {
2761
+ name: schemaName,
2762
+ models: Object.fromEntries(Object.entries(schemaFile.models || {}).map(([modelName, model]) => [
2763
+ modelName,
2764
+ {
2765
+ ...model,
2766
+ // Remove id field for import format
2767
+ id: undefined,
2768
+ properties: Object.fromEntries(Object.entries(model.properties).map(([propName, prop]) => {
2769
+ const schemaProp = prop;
2770
+ const jsonProp = {
2771
+ type: schemaProp.dataType || schemaProp.type,
2772
+ // Remove id field for import format
2773
+ id: undefined,
2774
+ };
2775
+ // Handle Relation type - convert ref to model (not for List)
2776
+ const rawDataType = (schemaProp.dataType ?? schemaProp.type ?? '').toString().toLowerCase();
2777
+ if (rawDataType !== 'list' && (schemaProp.ref || schemaProp.refModelName)) {
2778
+ jsonProp.model = schemaProp.refModelName || schemaProp.ref;
2779
+ }
2780
+ // Handle List type (support both refValueType and legacy items, case-insensitive dataType)
2781
+ if (rawDataType === 'list') {
2782
+ let refValueType = getRefValueType(schemaProp);
2783
+ // When ref is present but refValueType is not, derive refValueType as "Relation" (list of Relations)
2784
+ if (!refValueType && (schemaProp.ref ?? schemaProp.refModelName ?? schemaProp.items?.model)) {
2785
+ refValueType = 'Relation';
2786
+ }
2787
+ if (refValueType) {
2788
+ jsonProp.refValueType = refValueType;
2789
+ const ref = schemaProp.ref ?? schemaProp.refModelName ?? schemaProp.items?.model;
2790
+ if (ref)
2791
+ jsonProp.ref = ref;
2792
+ }
2793
+ }
2794
+ // Copy other properties (storage, etc.)
2795
+ Object.keys(schemaProp).forEach(key => {
2796
+ if (key !== 'id' && key !== 'dataType' && key !== 'type' && key !== 'ref' && key !== 'refModelName' && key !== 'refValueType') {
2797
+ jsonProp[key] = schemaProp[key];
2798
+ }
2799
+ });
2800
+ return [propName, jsonProp];
2801
+ })),
2802
+ },
2803
+ ])),
2804
+ };
2805
+ // Generate schema ID if missing (before creating schemaInput)
2806
+ if (!schemaFile.id) {
2807
+ schemaFile.id = generateId();
2808
+ logger$2('Generated schema ID for schema file:', schemaFile.id);
2809
+ }
2810
+ // Extract schemaFileIds from JSON file and generate missing ones BEFORE creating models
2811
+ // This ensures Model instances are created with correct IDs
2812
+ const modelFileIds = new Map();
2813
+ const propertyFileIds = new Map();
2814
+ for (const [modelName, model] of Object.entries(schemaFile.models || {})) {
2815
+ // Generate model ID if missing
2816
+ if (!model.id) {
2817
+ model.id = generateId();
2818
+ logger$2(`Generated model ID for ${modelName}:`, model.id);
2819
+ // Update the schemaFile object so it's included in schemaData
2820
+ schemaFile.models[modelName].id = model.id;
2821
+ }
2822
+ modelFileIds.set(modelName, model.id);
2823
+ const propIds = new Map();
2824
+ for (const [propName, prop] of Object.entries(model.properties)) {
2825
+ // Generate property ID if missing
2826
+ if (!prop.id) {
2827
+ prop.id = generateId();
2828
+ logger$2(`Generated property ID for ${modelName}.${propName}:`, prop.id);
2829
+ // Update the schemaFile object so it's included in schemaData
2830
+ schemaFile.models[modelName].properties[propName].id = prop.id;
2831
+ }
2832
+ propIds.set(propName, prop.id);
2833
+ }
2834
+ if (propIds.size > 0) {
2835
+ propertyFileIds.set(modelName, propIds);
2836
+ }
2837
+ }
2838
+ // Convert JSON models to Model classes, passing modelFileIds and propertyFileIds so Model instances use correct IDs
2839
+ const modelDefinitions = await createModelsFromJson(importData, modelFileIds, propertyFileIds);
2840
+ logger$2('loadSchemaFromFile - modelDefinitions length:', Object.keys(modelDefinitions).length);
2841
+ // Try to add schema and models to database if database is available
2842
+ try {
2843
+ const db = BaseDb.getAppDb();
2844
+ if (db) {
2845
+ const { schemaRecord } = await applySchemaToDb(schemaFile, modelDefinitions, modelFileIds, propertyFileIds);
2846
+ // Query the database to get model IDs that were just created
2847
+ const _mod_7 = await import('./BasePathResolver-DRT09oVv.js');
2848
+ const _ns_7 = _mod_7.R;
2849
+ const modelSchemaSchemaMod = _ns_7;
2850
+ const { modelSchemas } = modelSchemaSchemaMod;
2851
+ const _mod_8 = await import('./BasePathResolver-DRT09oVv.js');
2852
+ const _ns_8 = _mod_8.N;
2853
+ const modelSchemaMod = _ns_8;
2854
+ const { models: modelsTable } = modelSchemaMod;
2855
+ const drizzleMod = await import('drizzle-orm');
2856
+ const { eq } = drizzleMod;
2857
+ // Try to find models directly by schemaFileId first (more reliable than join table)
2858
+ const seedModelId = modelFileIds.get('Seed');
2859
+ let seedModel = [];
2860
+ if (seedModelId) {
2861
+ // Retry querying for the model until it's visible
2862
+ for (let attempt = 0; attempt < 10; attempt++) {
2863
+ seedModel = await db
2864
+ .select()
2865
+ .from(modelsTable)
2866
+ .where(eq(modelsTable.schemaFileId, seedModelId))
2867
+ .limit(1);
2868
+ if (seedModel.length > 0 && seedModel[0].id) {
2869
+ break;
2870
+ }
2871
+ if (attempt < 9) {
2872
+ await new Promise(resolve => setTimeout(resolve, 100));
2873
+ }
2874
+ }
2875
+ if (seedModel.length > 0 && seedModel[0].id) {
2876
+ await verifyPropertiesPersisted(db, seedModel[0].id, 'Seed', 10, 100);
2877
+ }
2878
+ }
2879
+ // Also verify via join table if schemaRecord.id is available (for completeness)
2880
+ if (schemaRecord.id) {
2881
+ // Retry querying join table until entries are visible
2882
+ let modelLinks = [];
2883
+ for (let attempt = 0; attempt < 10; attempt++) {
2884
+ modelLinks = await db
2885
+ .select({
2886
+ modelId: modelSchemas.modelId,
2887
+ modelName: modelsTable.name,
2888
+ })
2889
+ .from(modelSchemas)
2890
+ .innerJoin(modelsTable, eq(modelSchemas.modelId, modelsTable.id))
2891
+ .where(eq(modelSchemas.schemaId, schemaRecord.id));
2892
+ if (modelLinks.length > 0) {
2893
+ break;
2894
+ }
2895
+ if (attempt < 9) {
2896
+ await new Promise(resolve => setTimeout(resolve, 100));
2897
+ }
2898
+ }
2899
+ logger$2(`Verifying properties: found ${modelLinks.length} model links for schema ${schemaName} (id: ${schemaRecord.id})`);
2900
+ // If we didn't verify via direct lookup, verify via join table
2901
+ if (!seedModelId || (seedModel.length === 0 || !seedModel[0].id)) {
2902
+ const seedModelLink = modelLinks.find((link) => link.modelName === 'Seed');
2903
+ if (seedModelLink && seedModelLink.modelId) {
2904
+ logger$2(`Verifying properties for Seed model via join table (modelId: ${seedModelLink.modelId})`);
2905
+ await verifyPropertiesPersisted(db, seedModelLink.modelId, 'Seed', 10, 100);
2906
+ }
2907
+ else if (modelLinks.length > 0 && modelLinks[0].modelId) {
2908
+ // Fallback to first model if Seed not found
2909
+ logger$2(`Verifying properties for ${modelLinks[0].modelName} model via join table (modelId: ${modelLinks[0].modelId})`);
2910
+ await verifyPropertiesPersisted(db, modelLinks[0].modelId, modelLinks[0].modelName || 'unknown', 10, 100);
2911
+ }
2912
+ }
2913
+ }
2914
+ // After properties are created (file path), ensure schemaFile has the correct IDs from database
2915
+ // Query the database to get the actual schemaFileId values that were used
2916
+ // This ensures schemaData matches what's actually in the database
2917
+ const _mod_9 = await import('./BasePathResolver-DRT09oVv.js');
2918
+ const _ns_9 = _mod_9.N;
2919
+ const modelSchemaMod2 = _ns_9;
2920
+ const { properties: propertiesTable } = modelSchemaMod2;
2921
+ let schemaFileUpdated = false;
2922
+ for (const [modelName, modelFileId] of modelFileIds.entries()) {
2923
+ // Get model record to find modelId
2924
+ const modelRecords = await db
2925
+ .select()
2926
+ .from(modelsTable)
2927
+ .where(eq(modelsTable.schemaFileId, modelFileId))
2928
+ .limit(1);
2929
+ if (modelRecords.length > 0) {
2930
+ const modelId = modelRecords[0].id;
2931
+ // Get all properties for this model from database
2932
+ const propertyRecords = await db
2933
+ .select()
2934
+ .from(propertiesTable)
2935
+ .where(eq(propertiesTable.modelId, modelId));
2936
+ // Update schemaFile with actual database IDs (only if they differ)
2937
+ for (const propRecord of propertyRecords) {
2938
+ if (propRecord.schemaFileId && schemaFile.models[modelName]?.properties[propRecord.name]) {
2939
+ const currentId = schemaFile.models[modelName].properties[propRecord.name].id;
2940
+ if (currentId !== propRecord.schemaFileId) {
2941
+ schemaFile.models[modelName].properties[propRecord.name].id = propRecord.schemaFileId;
2942
+ logger$2(`Updated schemaFile property ID for ${modelName}.${propRecord.name} from ${currentId} to ${propRecord.schemaFileId}`);
2943
+ schemaFileUpdated = true;
2944
+ }
2945
+ }
2946
+ }
2947
+ }
2948
+ }
2949
+ // Always update schemaData to ensure it matches the current schemaFile state
2950
+ // This is important even if nothing changed, to ensure consistency
2951
+ const updatedSchemaData = JSON.stringify(schemaFile, null, 2);
2952
+ // Update the schema record in the database with current schemaData
2953
+ const _mod_10 = await import('./BasePathResolver-DRT09oVv.js');
2954
+ const _ns_10 = _mod_10.Q;
2955
+ const schemaSchemaMod = _ns_10;
2956
+ const { schemas: schemasTable } = schemaSchemaMod;
2957
+ await db
2958
+ .update(schemasTable)
2959
+ .set({ schemaData: updatedSchemaData })
2960
+ .where(eq(schemasTable.id, schemaRecord.id));
2961
+ if (schemaFileUpdated) {
2962
+ logger$2(`Updated schemaData in database with actual property IDs for schema "${schemaName}"`);
2963
+ }
2964
+ else {
2965
+ logger$2(`Verified schemaData in database matches schemaFile for schema "${schemaName}"`);
2966
+ }
2967
+ }
2968
+ }
2969
+ catch (dbError) {
2970
+ // Database not available - log warning but continue
2971
+ logger$2('Database not available, skipping database operations:', dbError instanceof Error ? dbError.message : String(dbError));
2972
+ }
2973
+ // Models are now Model instances, no registration needed
2974
+ // They should be created via Model.create() and are accessible via Model static methods
2975
+ for (const [modelName] of Object.entries(modelDefinitions)) {
2976
+ logger$2('loadSchemaFromFile - model available:', modelName);
2977
+ }
2978
+ return schemaFilePath;
2979
+ }
2980
+ catch (error) {
2981
+ if (error instanceof SyntaxError) {
2982
+ throw new Error(`Invalid JSON in file ${schemaFilePath}: ${error.message}`);
2983
+ }
2984
+ throw new Error(`Failed to load schema from file ${schemaFilePath}: ${error instanceof Error ? error.message : String(error)}`);
2985
+ }
2986
+ };
2987
+ /**
2988
+ * Create a Model class from a JSON model definition
2989
+ * @param modelName - The name of the model
2990
+ * @param modelDef - The JSON model definition
2991
+ * @param schemaName - The name of the schema this model belongs to
2992
+ * @returns A Model class that can be used with the SDK
2993
+ */
2994
+ const createModelFromJson = async (modelName, modelDef, schemaName, modelFileId, // Optional modelFileId from JSON file
2995
+ propertyFileIds) => {
2996
+ const _mod_11 = await import('./index-Y-yYyRBf.js');
2997
+ const _ns_11 = _mod_11.f;
2998
+ const modelMod = _ns_11;
2999
+ const { Model } = modelMod;
3000
+ // Convert JSON properties to schema format
3001
+ const convertedProperties = {};
3002
+ if (modelDef.properties) {
3003
+ for (const [propName, propDef] of Object.entries(modelDef.properties)) {
3004
+ // JSON import format: { type, model, items, storage: { type, path, extension } }
3005
+ // Schema format: { dataType, ref, refValueType, storageType, localStorageDir, filenameSuffix }
3006
+ const jsonProp = propDef;
3007
+ const rawType = jsonProp.type ?? jsonProp.dataType;
3008
+ const schemaProp = {
3009
+ dataType: normalizeDataType(rawType),
3010
+ };
3011
+ // Include property ID if available from propertyFileIds map
3012
+ // This ensures the property uses the correct ID from the schema file
3013
+ const propertyFileId = propertyFileIds?.get(propName);
3014
+ if (propertyFileId) {
3015
+ schemaProp.id = propertyFileId;
3016
+ }
3017
+ // Handle Relation type
3018
+ if (jsonProp.model && (rawType || '').toLowerCase() !== 'list') {
3019
+ schemaProp.ref = jsonProp.model;
3020
+ schemaProp.refModelName = jsonProp.model;
3021
+ }
3022
+ // Handle List type (case-insensitive): requires refValueType (or legacy items.type); ref when Relation
3023
+ if ((rawType || '').toLowerCase() === 'list') {
3024
+ let elementType = getRefValueType(jsonProp);
3025
+ // When ref is present but refValueType is not, derive refValueType as "Relation" (list of Relations)
3026
+ if ((!elementType || String(elementType).trim() === '') && (jsonProp.ref ?? jsonProp.items?.model ?? jsonProp.model)) {
3027
+ elementType = 'Relation';
3028
+ }
3029
+ if (!elementType || String(elementType).trim() === '') {
3030
+ const propKeys = Object.keys(jsonProp).join(', ');
3031
+ const propPreview = JSON.stringify(jsonProp).slice(0, 200);
3032
+ throw new Error(`List property "${propName}" in model "${modelName}" requires "refValueType" (e.g. refValueType: "Text" or refValueType: "Relation" with ref: "Tag"). ` +
3033
+ `Property has keys: [${propKeys}]. Raw property: ${propPreview}${propPreview.length >= 200 ? '...' : ''}`);
3034
+ }
3035
+ schemaProp.refValueType = normalizeDataType(elementType);
3036
+ const modelRef = jsonProp.ref ?? jsonProp.items?.model ?? jsonProp.model;
3037
+ if (modelRef) {
3038
+ schemaProp.ref = modelRef;
3039
+ schemaProp.refModelName = modelRef;
3040
+ }
3041
+ }
3042
+ // Handle storage configuration
3043
+ if (jsonProp.storage) {
3044
+ schemaProp.storageType = jsonProp.storage.type === 'ItemStorage' ? 'ItemStorage' : 'PropertyStorage';
3045
+ schemaProp.localStorageDir = jsonProp.storage.path;
3046
+ schemaProp.filenameSuffix = jsonProp.storage.extension;
3047
+ }
3048
+ // Handle required (for Relation/Image properties)
3049
+ if (jsonProp.required !== undefined) {
3050
+ schemaProp.required = !!jsonProp.required;
3051
+ }
3052
+ // Preserve validation rules (enum, pattern, minLength, maxLength)
3053
+ if (jsonProp.validation && typeof jsonProp.validation === 'object') {
3054
+ schemaProp.validation = jsonProp.validation;
3055
+ }
3056
+ convertedProperties[propName] = schemaProp;
3057
+ }
3058
+ }
3059
+ // Create Model instance with the definition
3060
+ // Pass id (schemaFileId) if provided to ensure Model instance uses correct ID
3061
+ // Note: indexes and description are ignored - they remain in JSON for schema file format compliance but are not used by Model instances
3062
+ if (modelFileId) {
3063
+ logger$2(`Creating schema model "${modelName}" with modelFileId: ${modelFileId}`);
3064
+ }
3065
+ else {
3066
+ logger$2(`Warning: Creating schema model "${modelName}" without modelFileId - it will be renamed if duplicates exist`);
3067
+ }
3068
+ const modelInstance = Model.create(modelName, schemaName, {
3069
+ modelFileId: modelFileId, // Pass modelFileId (preferred) as id (schemaFileId) from JSON file
3070
+ id: modelFileId, // Also pass as id for backward compatibility
3071
+ properties: convertedProperties,
3072
+ waitForReady: false,
3073
+ });
3074
+ return modelInstance;
3075
+ };
3076
+ /**
3077
+ * Convert JSON import schema to ModelDefinitions
3078
+ * @param importData - The JSON import data
3079
+ * @param modelFileIds - Optional map of model names to their file IDs from the JSON file
3080
+ * @param propertyFileIds - Optional map of model names to maps of property names to their file IDs from the JSON file
3081
+ * @returns ModelDefinitions object with Model classes
3082
+ */
3083
+ const createModelsFromJson = async (importData, modelFileIds, propertyFileIds) => {
3084
+ const modelDefinitions = {};
3085
+ const schemaName = importData.name;
3086
+ for (const [modelName, modelDef] of Object.entries(importData.models)) {
3087
+ // Get modelFileId from map if available
3088
+ const modelFileId = modelFileIds?.get(modelName);
3089
+ // Use modelFileId from map (no fallback to modelDef.id as JsonImportSchema doesn't have id property)
3090
+ const finalModelFileId = modelFileId;
3091
+ if (!finalModelFileId) {
3092
+ logger$2(`Warning: No modelFileId found for model "${modelName}" - model will be created without ID`);
3093
+ }
3094
+ // Get propertyFileIds for this model if available
3095
+ const modelPropertyFileIds = propertyFileIds?.get(modelName);
3096
+ const ModelClass = await createModelFromJson(modelName, modelDef, schemaName, finalModelFileId, modelPropertyFileIds);
3097
+ modelDefinitions[modelName] = ModelClass;
3098
+ }
3099
+ return modelDefinitions;
3100
+ };
3101
+ /**
3102
+ * Read a JSON import file and create Model classes from it.
3103
+ * For complete schema files ($schema), extracts model/property IDs so Model instances
3104
+ * use correct schemaFileIds - preventing duplicate models (Publication 1, etc.) when
3105
+ * processSchemaFiles runs multiple times.
3106
+ * @param filePath - Path to the JSON import file
3107
+ * @returns ModelDefinitions with Model classes created from the JSON schema
3108
+ */
3109
+ const createModelsFromJsonFile = async (filePath) => {
3110
+ const workingDir = BaseFileManager.getWorkingDir();
3111
+ const internalFilePath = filePath.startsWith(workingDir) ? filePath : `${workingDir}/${filePath}`;
3112
+ const content = await BaseFileManager.readFileAsString(internalFilePath);
3113
+ const parsed = JSON.parse(content);
3114
+ const importData = parseJsonImportContent(content);
3115
+ // Extract model and property IDs from complete schema format so createModelsFromJson
3116
+ // can pass them - without these, Model instances get new random IDs and addModelsToDb
3117
+ // creates duplicates (Publication 1, Identity 1, etc.)
3118
+ let modelFileIds;
3119
+ let propertyFileIds;
3120
+ if (parsed?.$schema && parsed?.models) {
3121
+ const models = normalizeSchemaModels(parsed.models);
3122
+ modelFileIds = new Map();
3123
+ propertyFileIds = new Map();
3124
+ for (const [modelName, model] of Object.entries(models)) {
3125
+ if (model?.id)
3126
+ modelFileIds.set(modelName, model.id);
3127
+ if (model?.properties) {
3128
+ const propIds = new Map();
3129
+ for (const [propName, prop] of Object.entries(model.properties)) {
3130
+ if (prop?.id)
3131
+ propIds.set(propName, prop.id);
3132
+ }
3133
+ if (propIds.size > 0)
3134
+ propertyFileIds.set(modelName, propIds);
3135
+ }
3136
+ }
3137
+ }
3138
+ return createModelsFromJson(importData, modelFileIds, propertyFileIds);
3139
+ };
3140
+
3141
+ var $schema = "https://seedprotocol.org/schemas/data-model/v1";
3142
+ var version = 1;
3143
+ var id = "SEEDPROTOCOL";
3144
+ var metadata = {
3145
+ name: "Seed Protocol",
3146
+ description: "Internal Seed Protocol schema defining core models (Seed, Version, Metadata, Image, File, Html)",
3147
+ createdAt: "2025-01-01T00:00:00Z",
3148
+ updatedAt: "2025-01-01T00:00:00Z"
3149
+ };
3150
+ var models = {
3151
+ Seed: {
3152
+ id: "SEEDPROTOCOL_SEED",
3153
+ description: "Core seed model representing the base entity in Seed Protocol",
3154
+ properties: {
3155
+ uid: {
3156
+ type: "Text",
3157
+ id: "SEEDPROTOCOL_SEED_uid"
3158
+ },
3159
+ type: {
3160
+ type: "Text",
3161
+ id: "SEEDPROTOCOL_SEED_type"
3162
+ }
3163
+ }
3164
+ },
3165
+ Version: {
3166
+ id: "SEEDPROTOCOL_VERSION",
3167
+ description: "Version model representing versions of seeds",
3168
+ properties: {
3169
+ seedUid: {
3170
+ type: "Text",
3171
+ id: "SEEDPROTOCOL_VERSION_seedUid"
3172
+ },
3173
+ note: {
3174
+ type: "Text",
3175
+ id: "SEEDPROTOCOL_VERSION_note"
3176
+ }
3177
+ }
3178
+ },
3179
+ Metadata: {
3180
+ id: "SEEDPROTOCOL_METADATA",
3181
+ description: "Metadata model for storing key-value pairs",
3182
+ properties: {
3183
+ key: {
3184
+ type: "Text",
3185
+ id: "SEEDPROTOCOL_METADATA_key"
3186
+ },
3187
+ value: {
3188
+ type: "Text",
3189
+ id: "SEEDPROTOCOL_METADATA_value"
3190
+ }
3191
+ }
3192
+ },
3193
+ Image: {
3194
+ id: "SEEDPROTOCOL_IMAGE",
3195
+ description: "Image model for storing image data and metadata",
3196
+ properties: {
3197
+ storageTransactionId: {
3198
+ type: "Text",
3199
+ id: "SEEDPROTOCOL_IMAGE_storageTransactionId"
3200
+ },
3201
+ uri: {
3202
+ type: "Text",
3203
+ id: "SEEDPROTOCOL_IMAGE_uri"
3204
+ },
3205
+ alt: {
3206
+ type: "Text",
3207
+ id: "SEEDPROTOCOL_IMAGE_alt"
3208
+ }
3209
+ }
3210
+ },
3211
+ File: {
3212
+ id: "SEEDPROTOCOL_FILE",
3213
+ description: "File model for storing file data and metadata",
3214
+ properties: {
3215
+ storageTransactionId: {
3216
+ type: "Text",
3217
+ id: "SEEDPROTOCOL_FILE_storageTransactionId"
3218
+ },
3219
+ uri: {
3220
+ type: "Text",
3221
+ id: "SEEDPROTOCOL_FILE_uri"
3222
+ }
3223
+ }
3224
+ },
3225
+ Html: {
3226
+ id: "SEEDPROTOCOL_HTML",
3227
+ description: "Html model for storing HTML content and metadata",
3228
+ properties: {
3229
+ storageTransactionId: {
3230
+ type: "Text",
3231
+ id: "SEEDPROTOCOL_HTML_storageTransactionId"
3232
+ },
3233
+ uri: {
3234
+ type: "Text",
3235
+ id: "SEEDPROTOCOL_HTML_uri"
3236
+ }
3237
+ }
3238
+ }
3239
+ };
3240
+ var enums = {
3241
+ };
3242
+ var migrations = [
3243
+ {
3244
+ version: 1,
3245
+ timestamp: "2025-01-01T00:00:00Z",
3246
+ description: "Initial seed-protocol schema with Seed, Version, Metadata, and Image models",
3247
+ changes: [
3248
+ ]
3249
+ },
3250
+ {
3251
+ version: 2,
3252
+ timestamp: "2025-02-14T00:00:00Z",
3253
+ description: "Add File and Html models for storage seed types",
3254
+ changes: [
3255
+ "Add File model with storageTransactionId and uri",
3256
+ "Add Html model with storageTransactionId and uri"
3257
+ ]
3258
+ }
3259
+ ];
3260
+ var internalSchema = {
3261
+ $schema: $schema,
3262
+ version: version,
3263
+ id: id,
3264
+ metadata: metadata,
3265
+ models: models,
3266
+ enums: enums,
3267
+ migrations: migrations
3268
+ };
3269
+
3270
+ var SEEDPROTOCOL_Seed_Protocol_v1 = /*#__PURE__*/Object.freeze({
3271
+ __proto__: null,
3272
+ $schema: $schema,
3273
+ default: internalSchema,
3274
+ enums: enums,
3275
+ id: id,
3276
+ metadata: metadata,
3277
+ migrations: migrations,
3278
+ models: models,
3279
+ version: version
3280
+ });
3281
+
3282
+ const logger$1 = debug('seedSdk:client:actors:processSchemaFiles');
3283
+ let processSchemaFilesChain = Promise.resolve();
3284
+ function resolveSchemaFilePath(schemaFile) {
3285
+ const path = BaseFileManager.getPathModule();
3286
+ if (path.isAbsolute(schemaFile)) {
3287
+ return schemaFile;
3288
+ }
3289
+ if (isNode()) {
3290
+ return path.resolve(process.cwd(), schemaFile);
3291
+ }
3292
+ const workingDir = BaseFileManager.getWorkingDir();
3293
+ return path.join(workingDir, schemaFile);
3294
+ }
3295
+ /** Resolve schema display name from an inlined object or on-disk JSON path. */
3296
+ async function resolveSchemaNameFromSource(source) {
3297
+ if (typeof source === 'object' && source?.metadata?.name) {
3298
+ return String(source.metadata.name);
3299
+ }
3300
+ if (typeof source === 'string') {
3301
+ try {
3302
+ const resolvedPath = resolveSchemaFilePath(source);
3303
+ const content = await BaseFileManager.readFileAsString(resolvedPath);
3304
+ const parsed = JSON.parse(content);
3305
+ if (parsed?.metadata?.name) {
3306
+ return String(parsed.metadata.name);
3307
+ }
3308
+ }
3309
+ catch (error) {
3310
+ logger$1('Could not resolve schema name from path:', source, error);
3311
+ }
3312
+ }
3313
+ return null;
3314
+ }
3315
+ // Timeout for the entire schema processing operation (120 seconds)
3316
+ // Schema processing can be slow with syncSchemaFromSource, loadAllSchemasFromDb, and OPFS file I/O
3317
+ const PROCESS_SCHEMA_FILES_TIMEOUT_MS = 120000;
3318
+ const processSchemaFiles = fromCallback(({ sendBack, input: { context } }) => {
3319
+ logger$1('processSchemaFiles started');
3320
+ let hasResponded = false;
3321
+ let timeoutId = null;
3322
+ const reportError = (error) => {
3323
+ if (!hasResponded) {
3324
+ hasResponded = true;
3325
+ if (timeoutId) {
3326
+ clearTimeout(timeoutId);
3327
+ timeoutId = null;
3328
+ }
3329
+ logger$1('processSchemaFiles error:', error);
3330
+ sendBack({
3331
+ type: 'error',
3332
+ error: error instanceof Error ? error : new Error(String(error))
3333
+ });
3334
+ }
3335
+ };
3336
+ const reportSuccess = () => {
3337
+ if (!hasResponded) {
3338
+ hasResponded = true;
3339
+ if (timeoutId) {
3340
+ clearTimeout(timeoutId);
3341
+ timeoutId = null;
3342
+ }
3343
+ logger$1('processSchemaFiles completed');
3344
+ sendBack({ type: ClientManagerEvents.PROCESS_SCHEMA_FILES_SUCCESS });
3345
+ }
3346
+ };
3347
+ // Set up a single timeout for the entire operation
3348
+ timeoutId = setTimeout(() => {
3349
+ reportError(new Error(`processSchemaFiles timed out after ${PROCESS_SCHEMA_FILES_TIMEOUT_MS}ms`));
3350
+ }, PROCESS_SCHEMA_FILES_TIMEOUT_MS);
3351
+ const _processSchemaFiles = async () => {
3352
+ // Schemas written (or confirmed present) this run — skip redundant loadSchemaFromFile later
3353
+ const schemasWrittenThisRun = new Set();
3354
+ // First, load the internal seed-protocol schema
3355
+ logger$1('Loading internal seed-protocol schema');
3356
+ const internalSchemaData = internalSchema;
3357
+ const db = BaseDb.getAppDb();
3358
+ const internalSchemaExists = db
3359
+ ? (await db.select().from(schemas).where(eq(schemas.schemaFileId, INTERNAL_SCHEMA_IDS[0])).limit(1)).length > 0
3360
+ : false;
3361
+ // Always mark internal schema as written so the DB/file loop does not re-apply it
3362
+ schemasWrittenThisRun.add(SEED_PROTOCOL_SCHEMA_NAME);
3363
+ if (internalSchemaExists) {
3364
+ logger$1('Internal seed-protocol schema already in DB, skipping import');
3365
+ }
3366
+ else {
3367
+ try {
3368
+ // Import the internal schema to database (will create models and add to store)
3369
+ await importJsonSchema({ contents: JSON.stringify(internalSchemaData) }, internalSchemaData.version);
3370
+ logger$1(`Loaded internal seed-protocol schema v${internalSchemaData.version}`);
3371
+ }
3372
+ catch (error) {
3373
+ // If schema already exists, that's fine - it means it was already loaded
3374
+ if (error?.message?.includes('already exists')) {
3375
+ logger$1('Internal seed-protocol schema already loaded');
3376
+ }
3377
+ else {
3378
+ logger$1('Error loading internal seed-protocol schema:', error);
3379
+ // Don't fail if internal schema can't be loaded - it might already be in DB
3380
+ }
3381
+ }
3382
+ }
3383
+ // If schema (canonical) is provided, sync it - takes precedence over schemaFile
3384
+ if (context.schema) {
3385
+ // Skip sync when schema is internal (already loaded above)
3386
+ const schemaName = typeof context.schema === 'object' && context.schema?.metadata?.name
3387
+ ? String(context.schema.metadata.name)
3388
+ : '';
3389
+ const schemaId = typeof context.schema === 'object' && context.schema?.id ? String(context.schema.id) : undefined;
3390
+ if (isInternalSchema(schemaName, schemaId)) {
3391
+ logger$1('Schema is internal (Seed Protocol), already loaded - skipping syncSchemaFromSource');
3392
+ }
3393
+ else {
3394
+ try {
3395
+ const syncedName = await resolveSchemaNameFromSource(context.schema);
3396
+ await syncSchemaFromSource(context.schema);
3397
+ if (syncedName) {
3398
+ schemasWrittenThisRun.add(syncedName);
3399
+ }
3400
+ logger$1(`Synced canonical schema from ${typeof context.schema === 'string' ? context.schema : 'object'}`);
3401
+ }
3402
+ catch (error) {
3403
+ logger$1(`Error syncing canonical schema:`, error);
3404
+ // Don't fail init - schema might already be in DB
3405
+ }
3406
+ }
3407
+ }
3408
+ else if (context.schemaFile) {
3409
+ // Legacy: schemaFile - load via importJsonSchema
3410
+ try {
3411
+ const resolvedPath = resolveSchemaFilePath(context.schemaFile);
3412
+ const content = await BaseFileManager.readFileAsString(resolvedPath);
3413
+ const parsed = JSON.parse(content);
3414
+ await importJsonSchema({ contents: content });
3415
+ if (parsed?.metadata?.name) {
3416
+ schemasWrittenThisRun.add(String(parsed.metadata.name));
3417
+ }
3418
+ logger$1(`Loaded schema from ${resolvedPath}`);
3419
+ }
3420
+ catch (error) {
3421
+ logger$1(`Error loading schema file ${context.schemaFile}:`, error);
3422
+ // Don't fail init - schema might already be in DB
3423
+ }
3424
+ }
3425
+ // Process minimal import files (files without $schema) - skip when canonical schema is set
3426
+ if (!context.schema) {
3427
+ logger$1('Listing schema files');
3428
+ let schemaFiles = [];
3429
+ try {
3430
+ schemaFiles = await listSchemaFiles();
3431
+ logger$1(`Found ${schemaFiles.length} schema files to process`);
3432
+ }
3433
+ catch (error) {
3434
+ logger$1('Error listing schema files (continuing anyway):', error);
3435
+ schemaFiles = [];
3436
+ }
3437
+ for (const schemaFile of schemaFiles) {
3438
+ try {
3439
+ await importJsonSchema(schemaFile.filePath);
3440
+ if (schemaFile.name) {
3441
+ schemasWrittenThisRun.add(schemaFile.name);
3442
+ }
3443
+ }
3444
+ catch (error) {
3445
+ logger$1(`Error importing schema file ${schemaFile.filePath}:`, error);
3446
+ }
3447
+ }
3448
+ }
3449
+ // Then, load all schemas using the unified database-first approach
3450
+ logger$1('Loading schemas from database and files');
3451
+ let allSchemasData = [];
3452
+ try {
3453
+ allSchemasData = await loadAllSchemasFromDb();
3454
+ logger$1(`Loaded ${allSchemasData.length} schemas (${allSchemasData.filter(s => s.isDraft).length} drafts, ${allSchemasData.filter(s => !s.isDraft).length} published)`);
3455
+ }
3456
+ catch (error) {
3457
+ logger$1('Error loading schemas from database (continuing anyway):', error);
3458
+ // Continue with empty array - this is not critical for initialization
3459
+ // Schemas can be loaded on-demand later
3460
+ allSchemasData = [];
3461
+ }
3462
+ // Collect models to add to context (schemas are now loaded on-demand via database queries)
3463
+ const allModels = { ...(context.models || {}) };
3464
+ // Linked model count per schema row, to tell whether an earlier load already applied a file's models
3465
+ const linkedModelCounts = new Map();
3466
+ if (db) {
3467
+ try {
3468
+ const rows = await db
3469
+ .select({ schemaId: modelSchemas.schemaId, count: countDistinct(modelSchemas.modelId) })
3470
+ .from(modelSchemas)
3471
+ .groupBy(modelSchemas.schemaId);
3472
+ for (const row of rows) {
3473
+ if (row.schemaId != null)
3474
+ linkedModelCounts.set(row.schemaId, Number(row.count));
3475
+ }
3476
+ }
3477
+ catch (error) {
3478
+ logger$1('Error counting linked models (re-applying all schema files):', error);
3479
+ }
3480
+ }
3481
+ for (const schemaData of allSchemasData) {
3482
+ const schema = schemaData.schema;
3483
+ const schemaName = schema.metadata?.name || 'unknown';
3484
+ logger$1(`Processing schema: ${schemaName} v${schema.version} (${schemaData.isDraft ? 'draft' : 'published'}, source: ${schemaData.source})`);
3485
+ // For published schemas (not drafts), load models from file if available
3486
+ if (!schemaData.isDraft && schema.id) {
3487
+ // loadAllSchemasFromDb already picked the latest file for this schema (and read it)
3488
+ const latestFilePath = schemaData.filePath;
3489
+ if (latestFilePath) {
3490
+ try {
3491
+ // Skip loadSchemaFromFile when this schema was already written this run (or is
3492
+ // internal) — re-applying creates UNIQUE constraint races on properties.
3493
+ // Use createModelsFromJsonFile only to get model definitions for context.
3494
+ const alreadyWritten = schemasWrittenThisRun.has(schemaName) ||
3495
+ isInternalSchema(schemaName, schema.id);
3496
+ // Also skip when an earlier init already applied this file: the DB row matches it and
3497
+ // all its models are linked. Re-applying every published schema on every init made
3498
+ // init time grow with the number of schema files.
3499
+ const expectedModelCount = Object.keys(schema.models || {}).length;
3500
+ const alreadyApplied = schemaData.dbMatchesFile === true &&
3501
+ schemaData.schemaRecordId != null &&
3502
+ (linkedModelCounts.get(schemaData.schemaRecordId) ?? 0) >= expectedModelCount;
3503
+ if (alreadyWritten || alreadyApplied) {
3504
+ const modelDefinitions = await createModelsFromJsonFile(latestFilePath);
3505
+ Object.assign(allModels, modelDefinitions);
3506
+ }
3507
+ else {
3508
+ await loadSchemaFromFile(latestFilePath);
3509
+ const modelDefinitions = await createModelsFromJsonFile(latestFilePath);
3510
+ Object.assign(allModels, modelDefinitions);
3511
+ }
3512
+ }
3513
+ catch (error) {
3514
+ logger$1(`Error loading schema file for ${schemaName}:`, error);
3515
+ }
3516
+ }
3517
+ else {
3518
+ // File doesn't exist (e.g. schema passed via config.schema) - use schema data from loadAllSchemasFromDb
3519
+ try {
3520
+ const schemaModels = schema.models;
3521
+ if (schemaModels && Object.keys(schemaModels).length > 0) {
3522
+ const modelFileIds = new Map();
3523
+ const propertyFileIds = new Map();
3524
+ for (const [modelName, model] of Object.entries(schemaModels)) {
3525
+ const modelId = model.id;
3526
+ if (modelId)
3527
+ modelFileIds.set(modelName, modelId);
3528
+ if (model.properties) {
3529
+ const propIds = new Map();
3530
+ for (const [propName, prop] of Object.entries(model.properties)) {
3531
+ const propId = prop.id;
3532
+ if (propId)
3533
+ propIds.set(propName, propId);
3534
+ }
3535
+ if (propIds.size > 0)
3536
+ propertyFileIds.set(modelName, propIds);
3537
+ }
3538
+ }
3539
+ const importData = {
3540
+ name: schemaName,
3541
+ models: Object.fromEntries(Object.entries(schemaModels).map(([modelName, model]) => [
3542
+ modelName,
3543
+ {
3544
+ description: model.description,
3545
+ properties: Object.fromEntries(Object.entries(model.properties || {}).map(([propName, prop]) => {
3546
+ const p = prop;
3547
+ return [
3548
+ propName,
3549
+ {
3550
+ type: p.dataType || p.type,
3551
+ model: p.refModelName || p.ref,
3552
+ refValueType: p.refValueType,
3553
+ ref: p.ref,
3554
+ ...p,
3555
+ },
3556
+ ];
3557
+ })),
3558
+ },
3559
+ ])),
3560
+ };
3561
+ const modelDefinitions = await createModelsFromJson(importData, modelFileIds, propertyFileIds);
3562
+ Object.assign(allModels, modelDefinitions);
3563
+ logger$1(`Loaded ${Object.keys(modelDefinitions).length} models from schema data for ${schemaName}`);
3564
+ }
3565
+ }
3566
+ catch (error) {
3567
+ logger$1(`Error creating models from schema data for ${schemaName}:`, error);
3568
+ }
3569
+ }
3570
+ }
3571
+ else if (schemaData.isDraft) {
3572
+ // For drafts, create models from the schema data
3573
+ try {
3574
+ const schemaModels = schema.models;
3575
+ if (schemaModels && Object.keys(schemaModels).length > 0) {
3576
+ const modelFileIds = new Map();
3577
+ const propertyFileIds = new Map();
3578
+ for (const [modelName, model] of Object.entries(schemaModels)) {
3579
+ const modelId = model.id;
3580
+ if (modelId)
3581
+ modelFileIds.set(modelName, modelId);
3582
+ if (model.properties) {
3583
+ const propIds = new Map();
3584
+ for (const [propName, prop] of Object.entries(model.properties)) {
3585
+ const propId = prop.id;
3586
+ if (propId)
3587
+ propIds.set(propName, propId);
3588
+ }
3589
+ if (propIds.size > 0)
3590
+ propertyFileIds.set(modelName, propIds);
3591
+ }
3592
+ }
3593
+ const importData = {
3594
+ name: schemaName,
3595
+ models: Object.fromEntries(Object.entries(schemaModels).map(([modelName, model]) => [
3596
+ modelName,
3597
+ {
3598
+ description: model.description,
3599
+ properties: Object.fromEntries(Object.entries(model.properties || {}).map(([propName, prop]) => {
3600
+ const p = prop;
3601
+ return [
3602
+ propName,
3603
+ {
3604
+ type: p.dataType || p.type,
3605
+ model: p.refModelName || p.ref,
3606
+ refValueType: p.refValueType,
3607
+ ref: p.ref,
3608
+ ...p,
3609
+ },
3610
+ ];
3611
+ })),
3612
+ },
3613
+ ])),
3614
+ };
3615
+ const modelDefinitions = await createModelsFromJson(importData, modelFileIds, propertyFileIds);
3616
+ Object.assign(allModels, modelDefinitions);
3617
+ logger$1(`Loaded ${Object.keys(modelDefinitions).length} models from draft schema ${schemaName}`);
3618
+ }
3619
+ }
3620
+ catch (error) {
3621
+ logger$1(`Error creating models from draft schema ${schemaName}:`, error);
3622
+ }
3623
+ }
3624
+ // Schemas are now loaded on-demand via database queries (useSchemas hook uses loadAllSchemasFromDb)
3625
+ // No need to populate context.schemas
3626
+ }
3627
+ // Update context with models only (schemas are loaded on-demand via database queries)
3628
+ sendBack({
3629
+ type: ClientManagerEvents.UPDATE_CONTEXT,
3630
+ context: {
3631
+ models: allModels,
3632
+ }
3633
+ });
3634
+ };
3635
+ processSchemaFilesChain = processSchemaFilesChain
3636
+ // Its schema and model inserts check for rows first, so tabs take turns (docs/MULTI_TAB.md).
3637
+ .then(() => withSeedDbLock('init', context.filesDir, _processSchemaFiles))
3638
+ .catch((err) => {
3639
+ logger$1('processSchemaFiles chain error:', err);
3640
+ throw err;
3641
+ });
3642
+ processSchemaFilesChain.then(reportSuccess).catch((error) => {
3643
+ reportError(error instanceof Error ? error : new Error(String(error)));
3644
+ });
3645
+ // Cleanup function to clear timeout if actor is stopped
3646
+ return () => {
3647
+ if (timeoutId) {
3648
+ clearTimeout(timeoutId);
3649
+ timeoutId = null;
3650
+ }
3651
+ };
3652
+ });
3653
+
3654
+ const { UNINITIALIZED, PLATFORM_CLASSES_INIT, FILE_SYSTEM_INIT, DB_INIT, SAVE_CONFIG, PROCESS_SCHEMA_FILES, ADD_MODELS_TO_STORE, ADD_MODELS_TO_DB, IDLE, INIT_FAILED, } = ClientManagerState;
3655
+ const { UPDATE_CONTEXT, PLATFORM_CLASSES_READY, FILE_SYSTEM_READY, DB_READY, SAVE_CONFIG_SUCCESS, SAVE_APP_STATE_SUCCESS, SAVE_APP_STATE_ERROR, SET_ADDRESSES, ADD_MODELS_TO_STORE_SUCCESS, ADD_MODELS_TO_DB_SUCCESS, PROCESS_SCHEMA_FILES_SUCCESS, } = ClientManagerEvents;
3656
+ const logSyncAfterAddressSave = debug("seedSdk:client:clientManagerMachine:syncAfterAddressSave");
3657
+ function addressesFromPersistedAddressValue(value) {
3658
+ if (!value || typeof value !== "object")
3659
+ return [];
3660
+ const v = value;
3661
+ const owned = Array.isArray(v.owned)
3662
+ ? v.owned.filter((a) => typeof a === "string")
3663
+ : [];
3664
+ const watched = Array.isArray(v.watched)
3665
+ ? v.watched.filter((a) => typeof a === "string")
3666
+ : [];
3667
+ const seen = new Set();
3668
+ const out = [];
3669
+ for (const a of [...owned, ...watched]) {
3670
+ const k = a.toLowerCase();
3671
+ if (!seen.has(k)) {
3672
+ seen.add(k);
3673
+ out.push(a);
3674
+ }
3675
+ }
3676
+ return out;
3677
+ }
3678
+ function maybeRunSyncFromEasAfterAddressSave({ context, event, }) {
3679
+ const merged = addressesFromPersistedAddressValue(event.value);
3680
+ if (!context.syncFromEasOnAddressChange)
3681
+ return;
3682
+ if (event.key !== "addresses")
3683
+ return;
3684
+ if (merged.length === 0)
3685
+ return;
3686
+ try {
3687
+ requestEasSyncFromAddressChange(merged);
3688
+ }
3689
+ catch (err) {
3690
+ logSyncAfterAddressSave("requestEasSyncFromAddressChange after address save failed", err);
3691
+ }
3692
+ }
3693
+ function emitAddressesPersistedIfAddressesKey(event) {
3694
+ if (event.key !== "addresses")
3695
+ return;
3696
+ const payload = parseAddressesPersistedPayload(event.value);
3697
+ emitAcrossTabs(ADDRESSES_PERSISTED_EVENT, payload);
3698
+ }
3699
+ function errorFromEvent(event, fallbackMessage) {
3700
+ const error = event?.error;
3701
+ if (error instanceof Error) {
3702
+ return error;
3703
+ }
3704
+ return new Error(String(error ?? fallbackMessage));
3705
+ }
3706
+ const failInit = (fallbackMessage) => ({
3707
+ target: INIT_FAILED,
3708
+ actions: {
3709
+ type: "assignInitError",
3710
+ params: { fallbackMessage },
3711
+ },
3712
+ });
3713
+ const clientManagerMachine = setup({
3714
+ types: {
3715
+ context: {},
3716
+ input: {},
3717
+ },
3718
+ actors: {
3719
+ platformClassesInit,
3720
+ fileSystemInit,
3721
+ dbInit,
3722
+ saveConfig,
3723
+ saveAppState,
3724
+ addModelsToStore,
3725
+ addModelsToDb,
3726
+ processSchemaFiles,
3727
+ },
3728
+ actions: {
3729
+ assignInitError: assign(({ event }, params) => ({
3730
+ initError: errorFromEvent(event, params.fallbackMessage),
3731
+ isInitialized: false,
3732
+ })),
3733
+ },
3734
+ }).createMachine({
3735
+ id: MachineIds.CLIENT_MANAGER,
3736
+ initial: UNINITIALIZED,
3737
+ context: ({ input }) => input,
3738
+ on: {
3739
+ [UPDATE_CONTEXT]: {
3740
+ actions: assign(({ event, context }) => {
3741
+ return {
3742
+ ...context,
3743
+ ...event.context,
3744
+ };
3745
+ }),
3746
+ },
3747
+ init: {
3748
+ target: `.${PLATFORM_CLASSES_INIT}`,
3749
+ actions: assign({
3750
+ isInitialized: false,
3751
+ initError: undefined,
3752
+ saveError: undefined,
3753
+ }),
3754
+ },
3755
+ },
3756
+ states: {
3757
+ [UNINITIALIZED]: {
3758
+ on: {
3759
+ init: {
3760
+ target: PLATFORM_CLASSES_INIT,
3761
+ },
3762
+ },
3763
+ },
3764
+ [PLATFORM_CLASSES_INIT]: {
3765
+ on: {
3766
+ [PLATFORM_CLASSES_READY]: {
3767
+ target: FILE_SYSTEM_INIT,
3768
+ },
3769
+ error: failInit('Platform classes initialization failed'),
3770
+ },
3771
+ invoke: {
3772
+ src: 'platformClassesInit',
3773
+ input: ({ event, context }) => ({
3774
+ event: event,
3775
+ context
3776
+ }),
3777
+ onError: failInit('Platform classes initialization failed'),
3778
+ },
3779
+ },
3780
+ [FILE_SYSTEM_INIT]: {
3781
+ on: {
3782
+ [FILE_SYSTEM_READY]: {
3783
+ target: DB_INIT,
3784
+ },
3785
+ error: failInit('File system initialization failed'),
3786
+ },
3787
+ invoke: {
3788
+ src: 'fileSystemInit',
3789
+ input: ({ context }) => ({ context }),
3790
+ onError: failInit('File system initialization failed'),
3791
+ },
3792
+ },
3793
+ [DB_INIT]: {
3794
+ on: {
3795
+ [DB_READY]: {
3796
+ target: SAVE_CONFIG,
3797
+ },
3798
+ error: failInit('Database initialization failed'),
3799
+ },
3800
+ invoke: {
3801
+ src: 'dbInit',
3802
+ input: ({ context }) => ({ context }),
3803
+ onError: failInit('Database initialization failed'),
3804
+ },
3805
+ },
3806
+ [SAVE_CONFIG]: {
3807
+ on: {
3808
+ [SAVE_CONFIG_SUCCESS]: {
3809
+ target: PROCESS_SCHEMA_FILES,
3810
+ },
3811
+ error: failInit('Saving config failed'),
3812
+ },
3813
+ invoke: {
3814
+ src: 'saveConfig',
3815
+ input: ({ context }) => ({ context }),
3816
+ onError: failInit('Saving config failed'),
3817
+ },
3818
+ },
3819
+ [PROCESS_SCHEMA_FILES]: {
3820
+ on: {
3821
+ [PROCESS_SCHEMA_FILES_SUCCESS]: {
3822
+ target: ADD_MODELS_TO_STORE,
3823
+ },
3824
+ error: failInit('Schema processing failed'),
3825
+ },
3826
+ invoke: {
3827
+ src: 'processSchemaFiles',
3828
+ input: ({ context }) => ({ context }),
3829
+ onError: failInit('Schema processing failed'),
3830
+ },
3831
+ },
3832
+ [ADD_MODELS_TO_STORE]: {
3833
+ on: {
3834
+ [ADD_MODELS_TO_STORE_SUCCESS]: {
3835
+ target: ADD_MODELS_TO_DB,
3836
+ },
3837
+ error: failInit('Adding models to store failed'),
3838
+ },
3839
+ invoke: {
3840
+ src: 'addModelsToStore',
3841
+ input: ({ context }) => ({ context }),
3842
+ onError: failInit('Adding models to store failed'),
3843
+ },
3844
+ },
3845
+ [ADD_MODELS_TO_DB]: {
3846
+ on: {
3847
+ [ADD_MODELS_TO_DB_SUCCESS]: {
3848
+ target: IDLE,
3849
+ },
3850
+ error: failInit('Adding models to database failed'),
3851
+ },
3852
+ invoke: {
3853
+ src: 'addModelsToDb',
3854
+ input: ({ context }) => ({ context }),
3855
+ onError: failInit('Adding models to database failed'),
3856
+ },
3857
+ },
3858
+ [INIT_FAILED]: {
3859
+ entry: assign({
3860
+ isInitialized: false,
3861
+ }),
3862
+ on: {
3863
+ init: {
3864
+ target: PLATFORM_CLASSES_INIT,
3865
+ actions: assign({
3866
+ isInitialized: false,
3867
+ initError: undefined,
3868
+ saveError: undefined,
3869
+ }),
3870
+ },
3871
+ },
3872
+ },
3873
+ [IDLE]: {
3874
+ entry: assign({
3875
+ isInitialized: true,
3876
+ }),
3877
+ on: {
3878
+ [SAVE_APP_STATE_SUCCESS]: {
3879
+ actions: [
3880
+ assign(() => {
3881
+ return {
3882
+ isSaving: false,
3883
+ saveError: undefined,
3884
+ };
3885
+ }),
3886
+ ({ context, event }) => maybeRunSyncFromEasAfterAddressSave({
3887
+ context,
3888
+ event: event,
3889
+ }),
3890
+ ({ event }) => emitAddressesPersistedIfAddressesKey(event),
3891
+ ],
3892
+ },
3893
+ [SAVE_APP_STATE_ERROR]: {
3894
+ actions: assign(({ event }) => ({
3895
+ isSaving: false,
3896
+ saveError: errorFromEvent(event, 'Failed to save app state'),
3897
+ })),
3898
+ },
3899
+ [SET_ADDRESSES]: {
3900
+ actions: [
3901
+ assign(({ event, spawn }) => {
3902
+ const { addresses } = event;
3903
+ const normalized = normalizeAddressConfig(addresses);
3904
+ spawn('saveAppState', {
3905
+ input: {
3906
+ key: 'addresses',
3907
+ value: { owned: normalized.owned, watched: normalized.watched },
3908
+ },
3909
+ });
3910
+ return {
3911
+ addresses: normalized.owned,
3912
+ ownedAddresses: normalized.owned,
3913
+ watchedAddresses: normalized.watched,
3914
+ isSaving: true,
3915
+ saveError: undefined,
3916
+ };
3917
+ })
3918
+ ],
3919
+ },
3920
+ init: {
3921
+ target: PLATFORM_CLASSES_INIT,
3922
+ actions: assign({
3923
+ isInitialized: false,
3924
+ initError: undefined,
3925
+ saveError: undefined,
3926
+ }),
3927
+ },
3928
+ },
3929
+ },
3930
+ },
3931
+ });
3932
+
3933
+ /**
3934
+ * Coalesce overlapping async init calls onto one in-flight promise.
3935
+ * Skip when already successfully ready. Always clear in-flight on settle
3936
+ * so a failed init can be retried.
3937
+ */
3938
+ function createCoalescedAsync(run, shouldSkip) {
3939
+ let inFlight = null;
3940
+ return (arg) => {
3941
+ if (inFlight) {
3942
+ return inFlight;
3943
+ }
3944
+ if (shouldSkip(arg)) {
3945
+ return Promise.resolve();
3946
+ }
3947
+ const pending = run(arg).finally(() => {
3948
+ if (inFlight === pending) {
3949
+ inFlight = null;
3950
+ }
3951
+ });
3952
+ inFlight = pending;
3953
+ return pending;
3954
+ };
3955
+ }
3956
+
3957
+ function msToUnixSeconds(ms) {
3958
+ if (ms == null || !Number.isFinite(ms))
3959
+ return 0;
3960
+ // Heuristic: values already in seconds are small; ms are large
3961
+ if (ms > 1e12)
3962
+ return Math.floor(ms / 1000);
3963
+ if (ms > 1e10)
3964
+ return Math.floor(ms / 1000);
3965
+ return Math.floor(ms);
3966
+ }
3967
+ function toSnakeCaseName(name) {
3968
+ return name
3969
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
3970
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
3971
+ .toLowerCase();
3972
+ }
3973
+ function parsePropertyValue(raw) {
3974
+ if (raw == null)
3975
+ return '';
3976
+ const t = raw.trim();
3977
+ if ((t.startsWith('[') && t.endsWith(']')) ||
3978
+ (t.startsWith('{') && t.endsWith('}'))) {
3979
+ try {
3980
+ return JSON.parse(t);
3981
+ }
3982
+ catch {
3983
+ return raw;
3984
+ }
3985
+ }
3986
+ return raw;
3987
+ }
3988
+ /**
3989
+ * Revocation as this database knows it. `revoked_at` (unix seconds) is the local truth: sync keeps
3990
+ * EAS's revocationTime there and unpublish stamps it before EAS's index catches up, while
3991
+ * `attestation_raw` only records what EAS said when the row was fetched.
3992
+ */
3993
+ function isRevokedLocally(revokedAt) {
3994
+ return revokedAt != null && revokedAt !== 0;
3995
+ }
3996
+ /** Overrides the fetch-time `revoked`/`revocationTime` of a stored attestation with `revoked_at`. */
3997
+ function withLocalRevocation(att, revokedAt) {
3998
+ const revoked = isRevokedLocally(revokedAt);
3999
+ return { ...att, revoked, revocationTime: revoked ? revokedAt : 0 };
4000
+ }
4001
+ function attestationFromRaw(raw) {
4002
+ if (!raw || !raw.trim())
4003
+ return null;
4004
+ try {
4005
+ const parsed = JSON.parse(raw);
4006
+ if (parsed && typeof parsed.id === 'string')
4007
+ return parsed;
4008
+ }
4009
+ catch {
4010
+ // fall through
4011
+ }
4012
+ return null;
4013
+ }
4014
+ function seedRowToAttestation(row) {
4015
+ if (!row.uid || !isValidEasAttestationUid(row.uid))
4016
+ return null;
4017
+ const fromRaw = attestationFromRaw(row.attestationRaw);
4018
+ if (fromRaw) {
4019
+ const schemaName = row.schemaName ??
4020
+ fromRaw.schema?.schemaNames?.[0]?.name ??
4021
+ row.type ??
4022
+ 'unknown';
4023
+ return withLocalRevocation({
4024
+ ...fromRaw,
4025
+ id: row.uid,
4026
+ schema: {
4027
+ schemaNames: [{ name: schemaName }],
4028
+ },
4029
+ timeCreated: fromRaw.timeCreated ||
4030
+ msToUnixSeconds(row.attestationCreatedAt),
4031
+ attester: fromRaw.attester ?? row.publisher ?? undefined,
4032
+ schemaId: fromRaw.schemaId || row.schemaUid || '',
4033
+ }, row.revokedAt);
4034
+ }
4035
+ const schemaName = row.schemaName ?? row.type ?? 'unknown';
4036
+ return withLocalRevocation({
4037
+ id: row.uid,
4038
+ decodedDataJson: '',
4039
+ refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
4040
+ schemaId: row.schemaUid || '',
4041
+ timeCreated: msToUnixSeconds(row.attestationCreatedAt),
4042
+ attester: row.publisher ?? undefined,
4043
+ schema: { schemaNames: [{ name: schemaName }] },
4044
+ }, row.revokedAt);
4045
+ }
4046
+ function versionRowToAttestation(row) {
4047
+ if (!row.uid || !isValidEasAttestationUid(row.uid))
4048
+ return null;
4049
+ if (!row.seedUid)
4050
+ return null;
4051
+ const fromRaw = attestationFromRaw(row.attestationRaw);
4052
+ if (fromRaw) {
4053
+ return withLocalRevocation({
4054
+ ...fromRaw,
4055
+ id: row.uid,
4056
+ refUID: row.seedUid,
4057
+ timeCreated: fromRaw.timeCreated ||
4058
+ msToUnixSeconds(row.attestationCreatedAt),
4059
+ attester: fromRaw.attester ?? row.publisher ?? undefined,
4060
+ }, row.revokedAt);
4061
+ }
4062
+ return withLocalRevocation({
4063
+ id: row.uid,
4064
+ decodedDataJson: '',
4065
+ refUID: row.seedUid,
4066
+ schemaId: '',
4067
+ timeCreated: msToUnixSeconds(row.attestationCreatedAt),
4068
+ attester: row.publisher ?? undefined,
4069
+ }, row.revokedAt);
4070
+ }
4071
+ function metadataRowToAttestation(row) {
4072
+ if (!row.uid || !isValidEasAttestationUid(row.uid))
4073
+ return null;
4074
+ if (!row.versionUid)
4075
+ return null;
4076
+ const fromRaw = attestationFromRaw(row.attestationRaw);
4077
+ if (fromRaw) {
4078
+ return withLocalRevocation({
4079
+ ...fromRaw,
4080
+ id: row.uid,
4081
+ refUID: row.versionUid,
4082
+ schemaId: fromRaw.schemaId || row.schemaUid || '',
4083
+ timeCreated: fromRaw.timeCreated ||
4084
+ msToUnixSeconds(row.attestationCreatedAt),
4085
+ attester: fromRaw.attester ?? row.publisher ?? undefined,
4086
+ }, row.revokedAt);
4087
+ }
4088
+ const name = row.propertyName
4089
+ ? toSnakeCaseName(row.propertyName)
4090
+ : 'unknown';
4091
+ const value = parsePropertyValue(row.propertyValue);
4092
+ const type = row.easDataType || 'string';
4093
+ return withLocalRevocation({
4094
+ id: row.uid,
4095
+ decodedDataJson: JSON.stringify([{ value: { name, value, type } }]),
4096
+ refUID: row.versionUid,
4097
+ schemaId: row.schemaUid || '',
4098
+ timeCreated: msToUnixSeconds(row.attestationCreatedAt),
4099
+ attester: row.publisher ?? undefined,
4100
+ }, row.revokedAt);
4101
+ }
4102
+ async function resolveSchemaNameForSeedType(appDb, seedType,
4103
+ /** seeds.model_file_id: the seed's own model (model names are only unique per schema). */
4104
+ modelFileId) {
4105
+ if (!seedType)
4106
+ return null;
4107
+ const normalized = startCase(seedType);
4108
+ try {
4109
+ const schemaRows = await appDb
4110
+ .select({ schemaName: schemas.name })
4111
+ .from(models$1)
4112
+ .innerJoin(modelSchemas, eq(models$1.id, modelSchemas.modelId))
4113
+ .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
4114
+ .where(modelFileId ? eq(models$1.schemaFileId, modelFileId) : eq(models$1.name, normalized))
4115
+ .orderBy(desc(models$1.id))
4116
+ .limit(1);
4117
+ if (schemaRows[0]?.schemaName)
4118
+ return schemaRows[0].schemaName;
4119
+ }
4120
+ catch {
4121
+ // ignore
4122
+ }
4123
+ return seedType;
4124
+ }
4125
+ function monthBoundsUnix(year, month) {
4126
+ const startDate = new Date(year, month - 1, 1);
4127
+ const endDate = new Date(year, month, 0, 23, 59, 59);
4128
+ const startTs = Math.floor(startDate.getTime() / 1000);
4129
+ const endTs = Math.floor(endDate.getTime() / 1000) + 1;
4130
+ return { startTs, endTs };
4131
+ }
4132
+ async function listPublishedSeedRows(schemaName, opts) {
4133
+ const appDb = BaseDb.getAppDb();
4134
+ if (!appDb)
4135
+ return [];
4136
+ const snakeType = schemaName.includes('_')
4137
+ ? schemaName
4138
+ : schemaName.replace(/([A-Z])/g, (m, i) => i === 0 ? m.toLowerCase() : `_${m.toLowerCase()}`);
4139
+ // Prefer exact type match (EAS schema names are usually lowercase singular)
4140
+ const typeCandidates = Array.from(new Set([schemaName, schemaName.toLowerCase(), snakeType.toLowerCase()]));
4141
+ const rows = await appDb
4142
+ .select({
4143
+ uid: seeds.uid,
4144
+ schemaUid: seeds.schemaUid,
4145
+ type: seeds.type,
4146
+ modelFileId: seeds.modelFileId,
4147
+ publisher: seeds.publisher,
4148
+ attestationRaw: seeds.attestationRaw,
4149
+ attestationCreatedAt: seeds.attestationCreatedAt,
4150
+ revokedAt: seeds.revokedAt,
4151
+ markedForDeletion: seeds._markedForDeletion,
4152
+ })
4153
+ .from(seeds)
4154
+ .where(and(inArray(seeds.type, typeCandidates), isNotNull(seeds.uid), or(isNull(seeds.revokedAt), eq(seeds.revokedAt, 0)), or(isNull(seeds._markedForDeletion), eq(seeds._markedForDeletion, 0))))
4155
+ .orderBy(desc(seeds.attestationCreatedAt));
4156
+ const out = [];
4157
+ for (const row of rows) {
4158
+ if (!row.uid || !isValidEasAttestationUid(row.uid))
4159
+ continue;
4160
+ // Must have at least one published version that is not revoked: the SDK's latest published
4161
+ // version skips revoked ones, so a seed whose versions were all revoked has none.
4162
+ const versionRows = await appDb
4163
+ .select({
4164
+ uid: versions.uid,
4165
+ revokedAt: versions.revokedAt,
4166
+ })
4167
+ .from(versions)
4168
+ .where(eq(versions.seedUid, row.uid))
4169
+ .orderBy(desc(versions.createdAt));
4170
+ const hasPublished = versionRows.some((v) => v.uid && isValidEasAttestationUid(v.uid) && !isRevokedLocally(v.revokedAt));
4171
+ if (!hasPublished)
4172
+ continue;
4173
+ const schemaResolved = await resolveSchemaNameForSeedType(appDb, row.type, row.modelFileId);
4174
+ const att = seedRowToAttestation({
4175
+ ...row,
4176
+ schemaName: schemaResolved === schemaName ? schemaName : (schemaResolved ?? schemaName),
4177
+ });
4178
+ if (!att)
4179
+ continue;
4180
+ // Force requested schema name so assembleSeeds filters correctly
4181
+ att.schema = { schemaNames: [{ name: schemaName }] };
4182
+ if (opts?.startTs != null || opts?.endTs != null) {
4183
+ const t = att.timeCreated;
4184
+ if (opts.startTs != null && t < opts.startTs)
4185
+ continue;
4186
+ if (opts.endTs != null && t >= opts.endTs)
4187
+ continue;
4188
+ }
4189
+ out.push(att);
4190
+ }
4191
+ return out;
4192
+ }
4193
+ /**
4194
+ * SDK local QueryDataSource: published Seeds/Versions/metadata from SQLite.
4195
+ *
4196
+ * Revocation follows the local `revoked_at` columns, not the stored `attestation_raw`: rows with
4197
+ * `revoked_at` set are left out (as the remote source leaves out revoked attestations) and returned
4198
+ * attestations carry `revoked`/`revocationTime` from those columns.
4199
+ */
4200
+ function createLocalQueryDataSource() {
4201
+ return {
4202
+ kind: 'local',
4203
+ async getSeedByUid(seedUid) {
4204
+ const appDb = BaseDb.getAppDb();
4205
+ if (!appDb || !seedUid)
4206
+ return null;
4207
+ const rows = await appDb
4208
+ .select({
4209
+ uid: seeds.uid,
4210
+ schemaUid: seeds.schemaUid,
4211
+ type: seeds.type,
4212
+ modelFileId: seeds.modelFileId,
4213
+ publisher: seeds.publisher,
4214
+ attestationRaw: seeds.attestationRaw,
4215
+ attestationCreatedAt: seeds.attestationCreatedAt,
4216
+ revokedAt: seeds.revokedAt,
4217
+ })
4218
+ .from(seeds)
4219
+ .where(eq(seeds.uid, seedUid))
4220
+ .limit(1);
4221
+ const row = rows[0];
4222
+ if (!row?.uid || !isValidEasAttestationUid(row.uid))
4223
+ return null;
4224
+ if (isRevokedLocally(row.revokedAt))
4225
+ return null;
4226
+ const schemaName = await resolveSchemaNameForSeedType(appDb, row.type, row.modelFileId);
4227
+ return seedRowToAttestation({ ...row, schemaName });
4228
+ },
4229
+ async listSeedsBySchemaName(schemaName, opts) {
4230
+ const all = await listPublishedSeedRows(schemaName);
4231
+ return all.slice(opts.skip, opts.skip + opts.limit);
4232
+ },
4233
+ async listSeedsBySchemaNameForMonth(schemaName, year, month) {
4234
+ const { startTs, endTs } = monthBoundsUnix(year, month);
4235
+ return listPublishedSeedRows(schemaName, { startTs, endTs });
4236
+ },
4237
+ async getVersionsForSeed(seedUid) {
4238
+ return this.getVersionsForSeeds([seedUid]);
4239
+ },
4240
+ async getVersionsForSeeds(seedUids, opts) {
4241
+ const appDb = BaseDb.getAppDb();
4242
+ if (!appDb || seedUids.length === 0)
4243
+ return [];
4244
+ const rows = await appDb
4245
+ .select({
4246
+ uid: versions.uid,
4247
+ seedUid: versions.seedUid,
4248
+ publisher: versions.publisher,
4249
+ attestationRaw: versions.attestationRaw,
4250
+ attestationCreatedAt: versions.attestationCreatedAt,
4251
+ revokedAt: versions.revokedAt,
4252
+ })
4253
+ .from(versions)
4254
+ .where(inArray(versions.seedUid, seedUids));
4255
+ // Like the remote source (EAS queries exclude revoked attestations by default; with
4256
+ // includeRevoked they come back marked `revoked`).
4257
+ const out = [];
4258
+ for (const row of rows) {
4259
+ if (isRevokedLocally(row.revokedAt) && !opts?.includeRevoked)
4260
+ continue;
4261
+ const att = versionRowToAttestation(row);
4262
+ if (att)
4263
+ out.push(att);
4264
+ }
4265
+ return out;
4266
+ },
4267
+ async getPropertiesForVersionUids(versionUids) {
4268
+ const appDb = BaseDb.getAppDb();
4269
+ if (!appDb || versionUids.length === 0)
4270
+ return [];
4271
+ const rows = await appDb
4272
+ .select({
4273
+ uid: metadata$1.uid,
4274
+ schemaUid: metadata$1.schemaUid,
4275
+ propertyName: metadata$1.propertyName,
4276
+ propertyValue: metadata$1.propertyValue,
4277
+ easDataType: metadata$1.easDataType,
4278
+ versionUid: metadata$1.versionUid,
4279
+ publisher: metadata$1.publisher,
4280
+ attestationRaw: metadata$1.attestationRaw,
4281
+ attestationCreatedAt: metadata$1.attestationCreatedAt,
4282
+ revokedAt: metadata$1.revokedAt,
4283
+ })
4284
+ .from(metadata$1)
4285
+ .where(inArray(metadata$1.versionUid, versionUids));
4286
+ // Like the remote source, revoked property attestations are left out, so the query layer's
4287
+ // canonical pick (newest live per version and schema) matches the SDK's live-first readers.
4288
+ const out = [];
4289
+ for (const row of rows) {
4290
+ if (isRevokedLocally(row.revokedAt))
4291
+ continue;
4292
+ const att = metadataRowToAttestation(row);
4293
+ if (att)
4294
+ out.push(att);
4295
+ }
4296
+ return out;
4297
+ },
4298
+ async getSeedsByUids(uids) {
4299
+ const appDb = BaseDb.getAppDb();
4300
+ if (!appDb || uids.length === 0)
4301
+ return [];
4302
+ const rows = await appDb
4303
+ .select({
4304
+ uid: seeds.uid,
4305
+ schemaUid: seeds.schemaUid,
4306
+ type: seeds.type,
4307
+ modelFileId: seeds.modelFileId,
4308
+ publisher: seeds.publisher,
4309
+ attestationRaw: seeds.attestationRaw,
4310
+ attestationCreatedAt: seeds.attestationCreatedAt,
4311
+ revokedAt: seeds.revokedAt,
4312
+ })
4313
+ .from(seeds)
4314
+ .where(inArray(seeds.uid, uids));
4315
+ const out = [];
4316
+ for (const row of rows) {
4317
+ if (isRevokedLocally(row.revokedAt))
4318
+ continue;
4319
+ const schemaName = await resolveSchemaNameForSeedType(appDb, row.type, row.modelFileId);
4320
+ const att = seedRowToAttestation({ ...row, schemaName });
4321
+ if (att)
4322
+ out.push(att);
4323
+ }
4324
+ return out;
4325
+ },
4326
+ async readStorageBody(ref) {
4327
+ try {
4328
+ if (ref.localPathHint) {
4329
+ const text = await BaseFileManager.readFileAsString(ref.localPathHint);
4330
+ if (text)
4331
+ return text;
4332
+ }
4333
+ // Common layout: html/{name}.html under filesDir
4334
+ if (typeof ref.value === 'string') {
4335
+ const v = ref.value.trim();
4336
+ // Absolute or relative file path already on disk
4337
+ if (v.includes('/') && !v.startsWith('http')) {
4338
+ try {
4339
+ const text = await BaseFileManager.readFileAsString(v);
4340
+ if (text)
4341
+ return text;
4342
+ }
4343
+ catch {
4344
+ // try under html/
4345
+ }
4346
+ }
4347
+ const htmlPath = BaseFileManager.getFilesPath('html', v.endsWith('.html') ? v : `${v}.html`);
4348
+ try {
4349
+ const text = await BaseFileManager.readFileAsString(htmlPath);
4350
+ if (text)
4351
+ return text;
4352
+ }
4353
+ catch {
4354
+ // fall through
4355
+ }
4356
+ }
4357
+ }
4358
+ catch {
4359
+ return null;
4360
+ }
4361
+ return null;
4362
+ },
4363
+ };
4364
+ }
4365
+
4366
+ let registered = false;
4367
+ /**
4368
+ * Register the SDK SQLite/files adapter with `@seedprotocol/query`.
4369
+ * Safe to call multiple times; re-registers with a fresh adapter instance.
4370
+ * No-op when the app DB is not ready yet (unless `force`).
4371
+ */
4372
+ function registerSeedQueryLocalSource(options) {
4373
+ if (!options?.force && !BaseDb.isAppDbReady()) {
4374
+ return false;
4375
+ }
4376
+ try {
4377
+ registerLocalQuerySource(createLocalQueryDataSource());
4378
+ registered = true;
4379
+ return true;
4380
+ }
4381
+ catch (err) {
4382
+ console.warn('[sdk/query] registerSeedQueryLocalSource failed:', err);
4383
+ return false;
4384
+ }
4385
+ }
4386
+ /** Unregister local query source (tests / client unload). */
4387
+ function unregisterSeedQueryLocalSource() {
4388
+ clearLocalQuerySource();
4389
+ registered = false;
4390
+ }
4391
+ function isSeedQueryLocalSourceRegistered() {
4392
+ return registered;
4393
+ }
4394
+ /**
4395
+ * Thin published-JSON helper: `getSeed` via the local (or auto) query source.
4396
+ * Does not touch Item / drafts / liveQuery.
4397
+ *
4398
+ * Prefer this (or `@seedprotocol/query` `getSeed` / `queryBySchema`) for
4399
+ * canonical published Seed JSON. Use `getItemData` / Item for draft-head authoring.
4400
+ */
4401
+ async function getPublishedSeedRecord(seedUid, options) {
4402
+ if (!registered) {
4403
+ registerSeedQueryLocalSource();
4404
+ }
4405
+ const source = options?.source ?? 'local';
4406
+ return getSeed(seedUid, { ...options, source });
4407
+ }
4408
+
4409
+ const emptyResult$1 = () => ({
4410
+ removedSeedLocalIds: [],
4411
+ removedSeedUids: [],
4412
+ });
4413
+ function normalizeAddresses(addresses) {
4414
+ return [
4415
+ ...new Set(addresses
4416
+ .filter((a) => typeof a === 'string' && a.length > 0)
4417
+ .map((a) => a.toLowerCase())),
4418
+ ];
4419
+ }
4420
+ /**
4421
+ * Find local on-chain seed copies for the given publisher addresses (no delete).
4422
+ * Drafts (no uid and no attestationRaw) are excluded.
4423
+ */
4424
+ async function findLocalOnchainCopiesForAddresses(addresses) {
4425
+ const normalized = normalizeAddresses(addresses);
4426
+ if (normalized.length === 0) {
4427
+ return emptyResult$1();
4428
+ }
4429
+ const appDb = BaseDb.getAppDb();
4430
+ if (!appDb) {
4431
+ return emptyResult$1();
4432
+ }
4433
+ const addressSet = new Set(normalized);
4434
+ const candidateRows = (await appDb
4435
+ .select({
4436
+ localId: seeds.localId,
4437
+ uid: seeds.uid,
4438
+ publisher: seeds.publisher,
4439
+ attestationRaw: seeds.attestationRaw,
4440
+ })
4441
+ .from(seeds)
4442
+ .where(or(isNotNull(seeds.uid), isNotNull(seeds.attestationRaw))));
4443
+ const matched = candidateRows.filter((row) => {
4444
+ if (!row.localId)
4445
+ return false;
4446
+ const publisher = resolvePublisherFromSeedRow(row);
4447
+ return publisher != null && addressSet.has(publisher.toLowerCase());
4448
+ });
4449
+ return {
4450
+ removedSeedLocalIds: matched.map((r) => r.localId).filter(Boolean),
4451
+ removedSeedUids: matched
4452
+ .map((r) => r.uid)
4453
+ .filter((u) => typeof u === 'string' && u.length > 0),
4454
+ };
4455
+ }
4456
+ /**
4457
+ * Hard-delete the given seed local ids and related local rows (cascade).
4458
+ * Does not touch schemas, app_state, or chain.
4459
+ */
4460
+ async function hardDeleteLocalSeedsByLocalIds(seedLocalIds) {
4461
+ if (seedLocalIds.length === 0)
4462
+ return;
4463
+ const appDb = BaseDb.getAppDb();
4464
+ if (!appDb)
4465
+ return;
4466
+ await appDb
4467
+ .delete(htmlEmbeddedImageCoPublish)
4468
+ .where(or(inArray(htmlEmbeddedImageCoPublish.parentSeedLocalId, seedLocalIds), inArray(htmlEmbeddedImageCoPublish.htmlSeedLocalId, seedLocalIds), inArray(htmlEmbeddedImageCoPublish.imageSeedLocalId, seedLocalIds)));
4469
+ await appDb
4470
+ .delete(arweaveL1FinalizeJobs)
4471
+ .where(inArray(arweaveL1FinalizeJobs.seedLocalId, seedLocalIds));
4472
+ await appDb
4473
+ .delete(publishProcesses)
4474
+ .where(inArray(publishProcesses.seedLocalId, seedLocalIds));
4475
+ await appDb.delete(metadata$1).where(inArray(metadata$1.seedLocalId, seedLocalIds));
4476
+ await appDb.delete(versions).where(inArray(versions.seedLocalId, seedLocalIds));
4477
+ await appDb.delete(seeds).where(inArray(seeds.localId, seedLocalIds));
4478
+ }
4479
+
4480
+ const emptyResult = () => ({
4481
+ seeds: 0,
4482
+ versions: 0,
4483
+ metadata: 0,
4484
+ });
4485
+ /**
4486
+ * Stamp local seeds/versions/metadata that have no publisher and no real uid.
4487
+ * Does not touch sealed rows (real EAS uid or attestationRaw).
4488
+ */
4489
+ async function claimUnpublishedDrafts(publisher) {
4490
+ const normalized = normalizePublisher(publisher);
4491
+ if (!normalized)
4492
+ return emptyResult();
4493
+ const appDb = BaseDb.getAppDb();
4494
+ if (!appDb)
4495
+ return emptyResult();
4496
+ const now = Date.now();
4497
+ const claimTable = async (table) => {
4498
+ const rows = (await appDb
4499
+ .select({
4500
+ localId: table.localId,
4501
+ uid: table.uid,
4502
+ publisher: table.publisher,
4503
+ attestationRaw: table.attestationRaw,
4504
+ })
4505
+ .from(table));
4506
+ const ids = rows
4507
+ .filter((row) => typeof row.localId === 'string' &&
4508
+ row.localId !== '' &&
4509
+ isLocalUnsealedDraft(row))
4510
+ .map((row) => row.localId);
4511
+ if (ids.length === 0)
4512
+ return 0;
4513
+ await appDb
4514
+ .update(table)
4515
+ .set({ publisher: normalized, updatedAt: now })
4516
+ .where(inArray(table.localId, ids));
4517
+ return ids.length;
4518
+ };
4519
+ const [seedCount, versionCount, metadataCount] = await Promise.all([
4520
+ claimTable(seeds),
4521
+ claimTable(versions),
4522
+ claimTable(metadata$1),
4523
+ ]);
4524
+ return {
4525
+ seeds: seedCount,
4526
+ versions: versionCount,
4527
+ metadata: metadataCount,
4528
+ };
4529
+ }
4530
+
4531
+ const logger = debug('seedSdk:client');
4532
+ const clientManager = createActor(clientManagerMachine, {
4533
+ input: {
4534
+ isInitialized: false,
4535
+ addressesSet: false,
4536
+ isSaving: false,
4537
+ syncFromEasOnAddressChange: false,
4538
+ }
4539
+ });
4540
+ const subscription = clientManager.subscribe((snapshot) => {
4541
+ logger('ClientManager snapshot.value:', snapshot.value);
4542
+ logger('ClientManager snapshot.context.isInitialized:', snapshot.context.isInitialized);
4543
+ });
4544
+ clientManager.start();
4545
+ const ensureInitialized = () => {
4546
+ if (!clientManager.getSnapshot().context.isInitialized) {
4547
+ throw new Error(CLIENT_NOT_INITIALIZED);
4548
+ }
4549
+ };
4550
+ // Singleton instance - created once at module load time
4551
+ // ES modules cache exports, ensuring all imports get the same instance
4552
+ const clientInstance = {
4553
+ isInitialized: () => {
4554
+ return clientManager.getSnapshot().context.isInitialized;
4555
+ },
4556
+ getService: () => {
4557
+ // ensureInitialized();
4558
+ return clientManager;
4559
+ },
4560
+ init: createCoalescedAsync(async (options) => {
4561
+ // If the actor is stopped (e.g., from a previous failed test), restart it
4562
+ const snapshot = clientManager.getSnapshot();
4563
+ const currentState = snapshot.value;
4564
+ const isStopped = snapshot.status === 'stopped';
4565
+ if (isStopped) {
4566
+ logger('Client manager was stopped, restarting...');
4567
+ clientManager.start();
4568
+ // Wait for the actor to be ready
4569
+ try {
4570
+ await waitFor(clientManager, (snapshot) => snapshot.status !== 'stopped', { timeout: 1000 });
4571
+ }
4572
+ catch (e) {
4573
+ logger('Timeout waiting for actor to restart, continuing anyway...');
4574
+ }
4575
+ }
4576
+ // Clear any stale context from previous failed initializations
4577
+ // This ensures we start with a clean slate before the new init updates it
4578
+ if (currentState !== 'uninitialized') {
4579
+ logger(`Client manager is in state ${currentState}, clearing stale context before re-init...`);
4580
+ clientManager.send({
4581
+ type: 'updateContext',
4582
+ context: {
4583
+ isInitialized: false,
4584
+ addressesSet: false,
4585
+ isSaving: false,
4586
+ syncFromEasOnAddressChange: false,
4587
+ endpoints: undefined,
4588
+ addresses: undefined,
4589
+ ownedAddresses: undefined,
4590
+ watchedAddresses: undefined,
4591
+ models: undefined,
4592
+ arweaveDomain: undefined,
4593
+ filesDir: undefined,
4594
+ dbConfig: undefined,
4595
+ initError: undefined,
4596
+ saveError: undefined,
4597
+ }
4598
+ });
4599
+ // Wait a moment for the context update to be processed
4600
+ await new Promise(resolve => setTimeout(resolve, 10));
4601
+ }
4602
+ // Send init event - the root-level handler will transition to PLATFORM_CLASSES_INIT
4603
+ // which will stop any running actors and start fresh with the new options
4604
+ // The platformClassesInit actor will update the context with the correct endpoints from options
4605
+ clientManager.send({ type: 'init', options });
4606
+ // Wait for the state machine to transition to PLATFORM_CLASSES_INIT to ensure
4607
+ // any old actors are stopped and the new initialization flow begins
4608
+ try {
4609
+ await waitFor(clientManager, (snapshot) => {
4610
+ return snapshot.value === 'platformClassesInit' || snapshot.context.isInitialized;
4611
+ }, { timeout: 1000 });
4612
+ }
4613
+ catch (e) {
4614
+ // If we can't transition, continue anyway - the waitFor below will catch any errors
4615
+ logger('Timeout waiting for state transition, continuing...');
4616
+ }
4617
+ try {
4618
+ await waitFor(clientManager, (snapshot) => {
4619
+ return snapshot.context.isInitialized || snapshot.context.initError != null;
4620
+ }, { timeout: 120000 });
4621
+ const initError = clientManager.getSnapshot().context.initError;
4622
+ if (initError) {
4623
+ throw initError instanceof Error ? initError : new Error(String(initError));
4624
+ }
4625
+ }
4626
+ catch (error) {
4627
+ // Ensure we never throw undefined
4628
+ if (error === undefined || error === null) {
4629
+ throw new Error('Initialization failed with undefined error');
4630
+ }
4631
+ throw error;
4632
+ }
4633
+ // Wire `@seedprotocol/query` local source once DB is ready
4634
+ registerSeedQueryLocalSource({ force: true });
4635
+ }, () => {
4636
+ const snapshot = clientManager.getSnapshot();
4637
+ return (snapshot.value === ClientManagerState.IDLE &&
4638
+ snapshot.context.isInitialized === true &&
4639
+ !snapshot.context.initError);
4640
+ }),
4641
+ setAddresses: async (addresses) => {
4642
+ ensureInitialized();
4643
+ logger('setAddresses', addresses);
4644
+ clientManager.send({ type: 'setAddresses', addresses });
4645
+ await waitFor(clientManager, (snapshot) => !snapshot.context.isSaving, { timeout: 10000 });
4646
+ const saveError = clientManager.getSnapshot().context.saveError;
4647
+ if (saveError) {
4648
+ throw saveError instanceof Error ? saveError : new Error(String(saveError));
4649
+ }
4650
+ logger('setAddresses success', addresses);
4651
+ },
4652
+ getAddresses: async () => {
4653
+ ensureInitialized();
4654
+ const owned = await getOwnedAddressesFromDb();
4655
+ const watched = await getWatchedAddressesFromDb();
4656
+ return { owned, watched };
4657
+ },
4658
+ getOwnedAddresses: async () => {
4659
+ ensureInitialized();
4660
+ return getOwnedAddressesFromDb();
4661
+ },
4662
+ getWatchedAddresses: async () => {
4663
+ ensureInitialized();
4664
+ return getWatchedAddressesFromDb();
4665
+ },
4666
+ /**
4667
+ * Stamp local seeds/versions/metadata that have no publisher and no real uid.
4668
+ * Sealed rows (real EAS uid or attestationRaw) are left unchanged.
4669
+ * Use after `setAddresses` on connect; `setPublishWallet` does this automatically.
4670
+ */
4671
+ claimUnpublishedDrafts: async (publisher) => {
4672
+ ensureInitialized();
4673
+ return claimUnpublishedDrafts(publisher);
4674
+ },
4675
+ /**
4676
+ * Hard-delete local on-chain seed copies for the given publisher addresses.
4677
+ * Drafts (no uid / attestationRaw) are kept. Chain is untouched; reconnect can rehydrate via syncFromEas.
4678
+ */
4679
+ removeLocalCopiesForAddresses: async (addresses) => {
4680
+ ensureInitialized();
4681
+ const list = Array.isArray(addresses) ? addresses : [];
4682
+ const matched = await findLocalOnchainCopiesForAddresses(list);
4683
+ const cacheIds = [
4684
+ ...matched.removedSeedLocalIds,
4685
+ ...matched.removedSeedUids,
4686
+ ];
4687
+ if (cacheIds.length > 0) {
4688
+ Item.dropCachedInstancesForSeedIds(cacheIds);
4689
+ }
4690
+ await hardDeleteLocalSeedsByLocalIds(matched.removedSeedLocalIds);
4691
+ // Other tabs drop their cached instances of the removed seeds too.
4692
+ emitAcrossTabs(LOCAL_COPIES_REMOVED_EVENT, {
4693
+ addresses: list,
4694
+ removedSeedLocalIds: matched.removedSeedLocalIds,
4695
+ removedSeedUids: matched.removedSeedUids,
4696
+ });
4697
+ return matched;
4698
+ },
4699
+ /**
4700
+ * Syncs item attestations from EAS for the configured models and given addresses.
4701
+ * Uses owned + watched addresses from DB when addresses are not provided.
4702
+ */
4703
+ syncFromEas: async (options) => {
4704
+ ensureInitialized();
4705
+ startEasSyncActor();
4706
+ const correlationId = typeof globalThis.crypto?.randomUUID === 'function'
4707
+ ? globalThis.crypto.randomUUID()
4708
+ : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
4709
+ sendEasSyncClientRequest(correlationId, options);
4710
+ await waitFor(easSyncActor, (snapshot) => snapshot.context.lastFinishedCorrelationIds?.includes(correlationId) ?? false);
4711
+ const snap = easSyncActor.getSnapshot();
4712
+ if (snap.context.lastFinishedStatus === 'failed') {
4713
+ throw new Error(snap.context.lastFinishedError ?? 'EAS sync failed');
4714
+ }
4715
+ },
4716
+ addModel: async (modelDef) => {
4717
+ const db = await BaseDb.getAppDb();
4718
+ const existingModels = await db.select().from(models$1).where(eq(models$1.name, modelDef.name));
4719
+ if (existingModels.length > 0) {
4720
+ return;
4721
+ }
4722
+ await db.insert(models$1).values({
4723
+ name: modelDef.name
4724
+ });
4725
+ // Global service removed - model registration is now handled by ClientManager
4726
+ // Models are added to DB during the ADD_MODELS_TO_DB state
4727
+ },
4728
+ onReady: (callback) => {
4729
+ const subscription = clientManager.subscribe((snapshot) => {
4730
+ if (snapshot.context.isInitialized) {
4731
+ subscription.unsubscribe();
4732
+ callback();
4733
+ }
4734
+ });
4735
+ },
4736
+ stop: () => {
4737
+ ensureInitialized();
4738
+ unregisterSeedQueryLocalSource();
4739
+ clientManager.stop();
4740
+ },
4741
+ unload: () => {
4742
+ ensureInitialized();
4743
+ unregisterSeedQueryLocalSource();
4744
+ clientManager.stop();
4745
+ subscription.unsubscribe();
4746
+ },
4747
+ };
4748
+ // Export the singleton instance
4749
+ // This ensures all imports across different files get the same instance
4750
+ const ClientManager = clientInstance;
4751
+ const getClient = () => {
4752
+ return clientInstance;
4753
+ };
4754
+
4755
+ var ClientManager$1 = /*#__PURE__*/Object.freeze({
4756
+ __proto__: null,
4757
+ ClientManager: ClientManager,
4758
+ clientManager: clientManager,
4759
+ getClient: getClient
4760
+ });
4761
+
4762
+ export { startEasSyncActor as A, unregisterSeedQueryLocalSource as B, ClientManager as C, createModelsFromJson as D, EAS_CHAIN_ID_APP_STATE_KEY as E, getLatestSchemaVersion as F, schema as G, ClientManager$1 as H, SEEDPROTOCOL_Seed_Protocol_v1 as S, assertLocalDbChain as a, getRefValueType as b, claimUnpublishedDrafts as c, createLocalQueryDataSource as d, createSchema as e, easSyncActor as f, getContentHash as g, getClient as h, importJsonSchema as i, getPublishedSeedRecord as j, getSchemaNameFromId as k, loadSchemaFromFile as l, isSeedQueryLocalSourceRegistered as m, listCompleteSchemaFiles as n, loadAllSchemasFromDb as o, mergeEasSyncRequestIntents as p, readSchema as q, readJsonImportFile as r, syncSchemaFromSource as s, transformImportToSchemaFile as t, registerSeedQueryLocalSource as u, requestEasSyncFromAddressChange as v, waitForEasReadChain as w, requestEasSyncFromEventBus as x, requestEasSyncFromModelsInit as y, sendEasSyncClientRequest as z };
4763
+ //# sourceMappingURL=ClientManager-DngaZdw6.js.map