@seedprotocol/sdk 0.7.9-next.1177.ga3e08ed → 0.7.9-next.1306.g4ff6582

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 (586) hide show
  1. package/dist/BaseFileManager-BAlySv8w.js +650 -0
  2. package/dist/BaseFileManager-BAlySv8w.js.map +1 -0
  3. package/dist/{Item-0oDoTPhi.js → Item-Bf4kWKpH.js} +5647 -6573
  4. package/dist/Item-Bf4kWKpH.js.map +1 -0
  5. package/dist/{ModelProperty-i5flwzQG.js → ModelProperty-BlaEgb_U.js} +345 -709
  6. package/dist/ModelProperty-BlaEgb_U.js.map +1 -0
  7. package/dist/Schema-J_1f6p0c.js +1456 -0
  8. package/dist/Schema-J_1f6p0c.js.map +1 -0
  9. package/dist/{SchemaValidationService-DnGKel4V.js → SchemaValidationService-CnmKyzR-.js} +6 -9
  10. package/dist/{SchemaValidationService-DnGKel4V.js.map → SchemaValidationService-CnmKyzR-.js.map} +1 -1
  11. package/dist/{configurePlatform-CrMafpMW.js → configurePlatform-CiOJ1R3q.js} +3 -12
  12. package/dist/configurePlatform-CiOJ1R3q.js.map +1 -0
  13. package/dist/db/drizzle/0025_add_metadata_retract.sql +1 -0
  14. package/dist/db/drizzle/0026_add_content_hash_to_schemas.sql +1 -0
  15. package/dist/db/drizzle/0027_link_metadata_property_ids.sql +74 -0
  16. package/dist/db/drizzle/0028_add_definition_to_properties.sql +17 -0
  17. package/dist/db/drizzle/0029_dedupe_chain_uids_add_unique_indexes.sql +120 -0
  18. package/dist/db/drizzle/meta/0025_snapshot.json +1652 -0
  19. package/dist/db/drizzle/meta/0026_snapshot.json +1659 -0
  20. package/dist/db/drizzle/meta/0027_snapshot.json +1659 -0
  21. package/dist/db/drizzle/meta/0028_snapshot.json +1666 -0
  22. package/dist/db/drizzle/meta/0029_snapshot.json +1691 -0
  23. package/dist/db/drizzle/meta/_journal.json +36 -1
  24. package/dist/{detachRowsBeforeDelete-DXFYSHRR.js → detachRowsBeforeDelete-sEaUwlOU.js} +2 -13
  25. package/dist/{detachRowsBeforeDelete-DXFYSHRR.js.map → detachRowsBeforeDelete-sEaUwlOU.js.map} +1 -1
  26. package/dist/dist/{index-Dx_GTdyQ.d.ts → BaseFileManager-BAlySv8w.d.ts} +1424 -2696
  27. package/dist/dist/BaseFileManager-BAlySv8w.d.ts.map +1 -0
  28. package/dist/dist/{configurePlatform-CrMafpMW.d.ts → configurePlatform-CiOJ1R3q.d.ts} +1 -1
  29. package/dist/dist/configurePlatform-CiOJ1R3q.d.ts.map +1 -0
  30. package/dist/dist/{index-PbnKw2es.d.ts → index-D0V-mjCV.d.ts} +56 -1
  31. package/dist/dist/{index-PbnKw2es.d.ts.map → index-D0V-mjCV.d.ts.map} +1 -1
  32. package/dist/dist/{index.node-CzMwMr_d.d.ts → index.node-CYPemH-t.d.ts} +225 -5
  33. package/dist/dist/{index.node-CzMwMr_d.d.ts.map → index.node-CYPemH-t.d.ts.map} +1 -1
  34. package/dist/dist/platform.node.d.ts +2 -2
  35. package/dist/eas-wB_fQ8Tc.js +32 -0
  36. package/dist/eas-wB_fQ8Tc.js.map +1 -0
  37. package/dist/errors-DLSoqSHG.js +6550 -0
  38. package/dist/errors-DLSoqSHG.js.map +1 -0
  39. package/dist/getPublishPayload-BnIwpwz1.js +760 -0
  40. package/dist/getPublishPayload-BnIwpwz1.js.map +1 -0
  41. package/dist/getPublishPendingDiff-D6_-A5ns.js +125 -0
  42. package/dist/getPublishPendingDiff-D6_-A5ns.js.map +1 -0
  43. package/dist/getPublishUploads-sS6JfPgz.js +783 -0
  44. package/dist/getPublishUploads-sS6JfPgz.js.map +1 -0
  45. package/dist/{index-DJmWkVIK.js → index-BVBmz_cc.js} +11 -14
  46. package/dist/index-BVBmz_cc.js.map +1 -0
  47. package/dist/index-CYGWPoOx.js +27 -0
  48. package/dist/index-CYGWPoOx.js.map +1 -0
  49. package/dist/{index-PbnKw2es.js → index-D0V-mjCV.js} +2 -2
  50. package/dist/index-D0V-mjCV.js.map +1 -0
  51. package/dist/index-DmTtj1nk.js +817 -0
  52. package/dist/index-DmTtj1nk.js.map +1 -0
  53. package/dist/{index.browser-i9uPJice.js → index.browser-CUmBVYen.js} +436 -379
  54. package/dist/index.browser-CUmBVYen.js.map +1 -0
  55. package/dist/{index.node-CzMwMr_d.js → index.node-CYPemH-t.js} +10 -10
  56. package/dist/index.node-CYPemH-t.js.map +1 -0
  57. package/dist/main.js +23 -1105
  58. package/dist/main.js.map +1 -1
  59. package/dist/modelPropertyRecordSchemas-C7UgA5TH.js +48 -0
  60. package/dist/modelPropertyRecordSchemas-C7UgA5TH.js.map +1 -0
  61. package/dist/node.js +14 -18
  62. package/dist/node.js.map +1 -1
  63. package/dist/platform.browser.js +8 -13
  64. package/dist/platform.browser.js.map +1 -1
  65. package/dist/platform.node.js +6 -16
  66. package/dist/platform.node.js.map +1 -1
  67. package/dist/property-BxUYznH0.js +87 -0
  68. package/dist/property-BxUYznH0.js.map +1 -0
  69. package/dist/publishGraph-EcB40Uii.js +311 -0
  70. package/dist/publishGraph-EcB40Uii.js.map +1 -0
  71. package/dist/readHtmlBodyForStorageSeed-D6KPtWoR.js +110 -0
  72. package/dist/readHtmlBodyForStorageSeed-D6KPtWoR.js.map +1 -0
  73. package/dist/seedSchema/MetadataSchema.ts +3 -0
  74. package/dist/seedSchema/ModelSchema.ts +4 -0
  75. package/dist/seedSchema/ModelSchemaSchema.ts +5 -2
  76. package/dist/seedSchema/PendingWriteSchema.ts +4 -1
  77. package/dist/seedSchema/SchemaSchema.ts +3 -0
  78. package/dist/seedSchema/SeedSchema.ts +8 -2
  79. package/dist/seedSchema/VersionSchema.ts +7 -2
  80. package/dist/seedSchema/attestedUid.ts +10 -0
  81. package/dist/src/Item/Item.d.ts +32 -30
  82. package/dist/src/Item/Item.d.ts.map +1 -1
  83. package/dist/src/Item/loadItem.d.ts +12 -0
  84. package/dist/src/Item/loadItem.d.ts.map +1 -0
  85. package/dist/src/Item/service/itemMachineSingle.d.ts +2 -13
  86. package/dist/src/Item/service/itemMachineSingle.d.ts.map +1 -1
  87. package/dist/src/ItemProperty/ItemProperty.d.ts +98 -41
  88. package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
  89. package/dist/src/ItemProperty/loadProperty.d.ts +11 -0
  90. package/dist/src/ItemProperty/loadProperty.d.ts.map +1 -0
  91. package/dist/src/ItemProperty/propertyWrite.d.ts +54 -0
  92. package/dist/src/ItemProperty/propertyWrite.d.ts.map +1 -0
  93. package/dist/src/ItemProperty/{service/actors/saveValueToDb/rowContext.d.ts → rowContext.d.ts} +1 -1
  94. package/dist/src/ItemProperty/rowContext.d.ts.map +1 -0
  95. package/dist/src/ItemProperty/service/actors/hydrateFromDb.d.ts +6 -0
  96. package/dist/src/ItemProperty/service/actors/hydrateFromDb.d.ts.map +1 -1
  97. package/dist/src/ItemProperty/service/actors/modelPropertyRecordSchemas.d.ts +5 -15
  98. package/dist/src/ItemProperty/service/actors/modelPropertyRecordSchemas.d.ts.map +1 -1
  99. package/dist/src/ItemProperty/service/propertyMachine.d.ts +3 -46
  100. package/dist/src/ItemProperty/service/propertyMachine.d.ts.map +1 -1
  101. package/dist/src/Model/Model.d.ts +26 -56
  102. package/dist/src/Model/Model.d.ts.map +1 -1
  103. package/dist/src/Model/createModelProperties.d.ts +10 -0
  104. package/dist/src/Model/createModelProperties.d.ts.map +1 -0
  105. package/dist/src/Model/loadModel.d.ts +13 -0
  106. package/dist/src/Model/loadModel.d.ts.map +1 -0
  107. package/dist/src/Model/service/actors/validateModel.d.ts.map +1 -1
  108. package/dist/src/Model/service/modelMachine.d.ts +5 -114
  109. package/dist/src/Model/service/modelMachine.d.ts.map +1 -1
  110. package/dist/src/ModelProperty/ModelProperty.d.ts +44 -37
  111. package/dist/src/ModelProperty/ModelProperty.d.ts.map +1 -1
  112. package/dist/src/ModelProperty/service/actors/compareAndMarkDraft.d.ts.map +1 -1
  113. package/dist/src/ModelProperty/service/actors/validateProperty.d.ts.map +1 -1
  114. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts +6 -77
  115. package/dist/src/ModelProperty/service/modelPropertyMachine.d.ts.map +1 -1
  116. package/dist/src/Schema/Schema.d.ts +36 -37
  117. package/dist/src/Schema/Schema.d.ts.map +1 -1
  118. package/dist/src/Schema/loadOrCreateSchema.d.ts +21 -0
  119. package/dist/src/Schema/loadOrCreateSchema.d.ts.map +1 -0
  120. package/dist/src/Schema/service/schemaMachine.d.ts +7 -125
  121. package/dist/src/Schema/service/schemaMachine.d.ts.map +1 -1
  122. package/dist/src/browser/db/Db.d.ts.map +1 -1
  123. package/dist/src/browser/db/changeFeed.d.ts +2 -18
  124. package/dist/src/browser/db/changeFeed.d.ts.map +1 -1
  125. package/dist/src/browser/db/drizzleFiles.d.ts +7 -2
  126. package/dist/src/browser/db/drizzleFiles.d.ts.map +1 -1
  127. package/dist/src/browser/workers/filesDownload.d.ts.map +1 -1
  128. package/dist/src/browser/workers/imageResize.d.ts.map +1 -1
  129. package/dist/src/client/ClientManager.d.ts +90 -57
  130. package/dist/src/client/ClientManager.d.ts.map +1 -1
  131. package/dist/src/client/actors/addModelsToDb.d.ts +9 -7
  132. package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
  133. package/dist/src/client/actors/dbInit.d.ts +3 -3
  134. package/dist/src/client/actors/dbInit.d.ts.map +1 -1
  135. package/dist/src/client/actors/fileSystemInit.d.ts +3 -3
  136. package/dist/src/client/actors/fileSystemInit.d.ts.map +1 -1
  137. package/dist/src/client/actors/platformClassesInit.d.ts +10 -5
  138. package/dist/src/client/actors/platformClassesInit.d.ts.map +1 -1
  139. package/dist/src/client/actors/processSchemaFiles.d.ts +18 -3
  140. package/dist/src/client/actors/processSchemaFiles.d.ts.map +1 -1
  141. package/dist/src/client/actors/saveConfig.d.ts +6 -3
  142. package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
  143. package/dist/src/client/clientManagerMachine.d.ts +22 -20
  144. package/dist/src/client/clientManagerMachine.d.ts.map +1 -1
  145. package/dist/src/client/constants.d.ts +1 -9
  146. package/dist/src/client/constants.d.ts.map +1 -1
  147. package/dist/src/client/readiness.d.ts +26 -0
  148. package/dist/src/client/readiness.d.ts.map +1 -0
  149. package/dist/src/db/read/currentPropertyRow.d.ts +61 -0
  150. package/dist/src/db/read/currentPropertyRow.d.ts.map +1 -0
  151. package/dist/src/db/read/findSeedRow.d.ts +20 -0
  152. package/dist/src/db/read/findSeedRow.d.ts.map +1 -0
  153. package/dist/src/db/read/findSeedRowsByUid.d.ts +4 -1
  154. package/dist/src/db/read/findSeedRowsByUid.d.ts.map +1 -1
  155. package/dist/src/db/read/getAttesterForSeed.d.ts +1 -1
  156. package/dist/src/db/read/getAttesterForSeed.d.ts.map +1 -1
  157. package/dist/src/db/read/getItemData.d.ts.map +1 -1
  158. package/dist/src/db/read/getItemProperties.d.ts +5 -0
  159. package/dist/src/db/read/getItemProperties.d.ts.map +1 -1
  160. package/dist/src/db/read/getMetadataAttestationUidsForSeedUid.d.ts.map +1 -1
  161. package/dist/src/db/read/getModelSchemas.d.ts +0 -7
  162. package/dist/src/db/read/getModelSchemas.d.ts.map +1 -1
  163. package/dist/src/db/read/getModelsData.d.ts.map +1 -1
  164. package/dist/src/db/read/getPropertyClear.d.ts +49 -0
  165. package/dist/src/db/read/getPropertyClear.d.ts.map +1 -0
  166. package/dist/src/db/read/getPublishPayload.d.ts +35 -40
  167. package/dist/src/db/read/getPublishPayload.d.ts.map +1 -1
  168. package/dist/src/db/read/getPublishPendingDiff.d.ts +7 -2
  169. package/dist/src/db/read/getPublishPendingDiff.d.ts.map +1 -1
  170. package/dist/src/db/read/getPublishUploads.d.ts +59 -28
  171. package/dist/src/db/read/getPublishUploads.d.ts.map +1 -1
  172. package/dist/src/db/read/getSchemaUidForModel.d.ts.map +1 -1
  173. package/dist/src/db/read/getSeedPublishState.d.ts.map +1 -1
  174. package/dist/src/db/read/getSeedPublisher.d.ts.map +1 -1
  175. package/dist/src/db/read/getVersionsForSeedUid.d.ts.map +1 -1
  176. package/dist/src/db/read/isSeedRevoked.d.ts.map +1 -1
  177. package/dist/src/db/read/localModelSchemaLookups.d.ts +8 -0
  178. package/dist/src/db/read/localModelSchemaLookups.d.ts.map +1 -0
  179. package/dist/src/db/read/metadataProperty.d.ts +104 -0
  180. package/dist/src/db/read/metadataProperty.d.ts.map +1 -0
  181. package/dist/src/db/read/publishGraph.d.ts +148 -0
  182. package/dist/src/db/read/publishGraph.d.ts.map +1 -0
  183. package/dist/src/db/read/resolveModelRecord.d.ts +26 -2
  184. package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -1
  185. package/dist/src/db/read/resolveRelatedSeedRef.d.ts +1 -7
  186. package/dist/src/db/read/resolveRelatedSeedRef.d.ts.map +1 -1
  187. package/dist/src/db/read/subqueries/metadataLatest.d.ts +3 -1
  188. package/dist/src/db/read/subqueries/metadataLatest.d.ts.map +1 -1
  189. package/dist/src/db/read/summarizePublishWork.d.ts +18 -5
  190. package/dist/src/db/read/summarizePublishWork.d.ts.map +1 -1
  191. package/dist/src/db/write/applyPropertyAttestationUidsFromPublish.d.ts +20 -21
  192. package/dist/src/db/write/applyPropertyAttestationUidsFromPublish.d.ts.map +1 -1
  193. package/dist/src/db/write/createMetadata.d.ts.map +1 -1
  194. package/dist/src/db/write/createNewItem.d.ts.map +1 -1
  195. package/dist/src/db/write/createSeeds.d.ts +5 -1
  196. package/dist/src/db/write/createSeeds.d.ts.map +1 -1
  197. package/dist/src/db/write/definitions.d.ts +127 -0
  198. package/dist/src/db/write/definitions.d.ts.map +1 -0
  199. package/dist/src/db/write/markPropertyAttestationsRetracted.d.ts +11 -0
  200. package/dist/src/db/write/markPropertyAttestationsRetracted.d.ts.map +1 -0
  201. package/dist/src/db/write/mergeChainRows.d.ts +22 -0
  202. package/dist/src/db/write/mergeChainRows.d.ts.map +1 -0
  203. package/dist/src/db/write/saveDefinition.d.ts +29 -0
  204. package/dist/src/db/write/saveDefinition.d.ts.map +1 -0
  205. package/dist/src/db/write/updateItemPropertyValue.d.ts +2 -0
  206. package/dist/src/db/write/updateItemPropertyValue.d.ts.map +1 -1
  207. package/dist/src/db/write/updateSeedUid.d.ts +4 -2
  208. package/dist/src/db/write/updateSeedUid.d.ts.map +1 -1
  209. package/dist/src/db/write/updateVersionUid.d.ts +12 -4
  210. package/dist/src/db/write/updateVersionUid.d.ts.map +1 -1
  211. package/dist/src/db/write/writeModelUids.d.ts +10 -0
  212. package/dist/src/db/write/writeModelUids.d.ts.map +1 -0
  213. package/dist/src/eas.d.ts +8 -6
  214. package/dist/src/eas.d.ts.map +1 -1
  215. package/dist/src/events/files/download.d.ts.map +1 -1
  216. package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
  217. package/dist/src/helpers/arweaveContentTags.d.ts +1 -1
  218. package/dist/src/helpers/arweaveContentTags.d.ts.map +1 -1
  219. package/dist/src/helpers/compareMetadataRowsLatestFirst.d.ts +27 -12
  220. package/dist/src/helpers/compareMetadataRowsLatestFirst.d.ts.map +1 -1
  221. package/dist/src/helpers/constants.d.ts +5 -2
  222. package/dist/src/helpers/constants.d.ts.map +1 -1
  223. package/dist/src/helpers/dataUrl.d.ts +10 -0
  224. package/dist/src/helpers/dataUrl.d.ts.map +1 -0
  225. package/dist/src/helpers/db.d.ts +54 -105
  226. package/dist/src/helpers/db.d.ts.map +1 -1
  227. package/dist/src/helpers/ensureImageLocal.d.ts.map +1 -1
  228. package/dist/src/helpers/entity/contextUpdate.d.ts +20 -0
  229. package/dist/src/helpers/entity/contextUpdate.d.ts.map +1 -0
  230. package/dist/src/helpers/entity/draftSaveErrors.d.ts +5 -0
  231. package/dist/src/helpers/entity/draftSaveErrors.d.ts.map +1 -1
  232. package/dist/src/helpers/entity/entityCache.d.ts +65 -3
  233. package/dist/src/helpers/entity/entityCache.d.ts.map +1 -1
  234. package/dist/src/helpers/entity/entityCommon.d.ts +0 -22
  235. package/dist/src/helpers/entity/entityCommon.d.ts.map +1 -1
  236. package/dist/src/helpers/entity/entityLifecycle.d.ts +75 -0
  237. package/dist/src/helpers/entity/entityLifecycle.d.ts.map +1 -0
  238. package/dist/src/helpers/entity/entityLiveQuery.d.ts +1 -23
  239. package/dist/src/helpers/entity/entityLiveQuery.d.ts.map +1 -1
  240. package/dist/src/helpers/htmlEmbeddedDataUriPublish.d.ts +4 -9
  241. package/dist/src/helpers/htmlEmbeddedDataUriPublish.d.ts.map +1 -1
  242. package/dist/src/helpers/index.d.ts +2 -24
  243. package/dist/src/helpers/index.d.ts.map +1 -1
  244. package/dist/src/helpers/localImageBlobUrl.d.ts +7 -0
  245. package/dist/src/helpers/localImageBlobUrl.d.ts.map +1 -0
  246. package/dist/src/helpers/mediaRef.d.ts +3 -70
  247. package/dist/src/helpers/mediaRef.d.ts.map +1 -1
  248. package/dist/src/helpers/metadataPropertyNames.d.ts +34 -21
  249. package/dist/src/helpers/metadataPropertyNames.d.ts.map +1 -1
  250. package/dist/src/helpers/mimeTypes.d.ts +2 -1
  251. package/dist/src/helpers/mimeTypes.d.ts.map +1 -1
  252. package/dist/src/helpers/model.d.ts.map +1 -1
  253. package/dist/src/helpers/ownership.d.ts.map +1 -1
  254. package/dist/src/helpers/property.d.ts +21 -26
  255. package/dist/src/helpers/property.d.ts.map +1 -1
  256. package/dist/src/helpers/propertyDefinition.d.ts +59 -0
  257. package/dist/src/helpers/propertyDefinition.d.ts.map +1 -0
  258. package/dist/src/helpers/readHtmlBodyForStorageSeed.d.ts.map +1 -1
  259. package/dist/src/helpers/relationSeedRef.d.ts +0 -7
  260. package/dist/src/helpers/relationSeedRef.d.ts.map +1 -1
  261. package/dist/src/helpers/schema.d.ts +24 -38
  262. package/dist/src/helpers/schema.d.ts.map +1 -1
  263. package/dist/src/helpers/schemaApply.d.ts +135 -0
  264. package/dist/src/helpers/schemaApply.d.ts.map +1 -0
  265. package/dist/src/helpers/tabEvents.d.ts.map +1 -1
  266. package/dist/src/helpers/updateSchema.d.ts +2 -18
  267. package/dist/src/helpers/updateSchema.d.ts.map +1 -1
  268. package/dist/src/imports/index.d.ts +1 -1
  269. package/dist/src/imports/index.d.ts.map +1 -1
  270. package/dist/src/imports/json.d.ts +8 -78
  271. package/dist/src/imports/json.d.ts.map +1 -1
  272. package/dist/src/index.d.ts +16 -14
  273. package/dist/src/index.d.ts.map +1 -1
  274. package/dist/src/interfaces/IItem.d.ts +5 -0
  275. package/dist/src/interfaces/IItem.d.ts.map +1 -1
  276. package/dist/src/interfaces/IItemProperty.d.ts +13 -7
  277. package/dist/src/interfaces/IItemProperty.d.ts.map +1 -1
  278. package/dist/src/node/db/Db.d.ts.map +1 -1
  279. package/dist/src/node/index.d.ts +3 -2
  280. package/dist/src/node/index.d.ts.map +1 -1
  281. package/dist/src/outbox/conflicts.d.ts +2 -0
  282. package/dist/src/outbox/conflicts.d.ts.map +1 -1
  283. package/dist/src/outbox/itemAppliers.d.ts +70 -14
  284. package/dist/src/outbox/itemAppliers.d.ts.map +1 -1
  285. package/dist/src/outbox/outbox.d.ts +14 -3
  286. package/dist/src/outbox/outbox.d.ts.map +1 -1
  287. package/dist/src/outbox/pendingValues.d.ts +3 -1
  288. package/dist/src/outbox/pendingValues.d.ts.map +1 -1
  289. package/dist/src/outbox/storageValueRow.d.ts +7 -0
  290. package/dist/src/outbox/storageValueRow.d.ts.map +1 -0
  291. package/dist/src/platform/configurePlatform.d.ts.map +1 -1
  292. package/dist/src/platform/index.browser.d.ts.map +1 -1
  293. package/dist/src/platform/index.node.d.ts.map +1 -1
  294. package/dist/src/seedSchema/MetadataSchema.d.ts +17 -0
  295. package/dist/src/seedSchema/MetadataSchema.d.ts.map +1 -1
  296. package/dist/src/seedSchema/ModelSchema.d.ts +19 -0
  297. package/dist/src/seedSchema/ModelSchema.d.ts.map +1 -1
  298. package/dist/src/seedSchema/ModelSchemaSchema.d.ts.map +1 -1
  299. package/dist/src/seedSchema/PendingWriteSchema.d.ts.map +1 -1
  300. package/dist/src/seedSchema/SchemaSchema.d.ts +19 -0
  301. package/dist/src/seedSchema/SchemaSchema.d.ts.map +1 -1
  302. package/dist/src/seedSchema/SeedSchema.d.ts.map +1 -1
  303. package/dist/src/seedSchema/VersionSchema.d.ts.map +1 -1
  304. package/dist/src/seedSchema/attestedUid.d.ts +9 -0
  305. package/dist/src/seedSchema/attestedUid.d.ts.map +1 -0
  306. package/dist/src/storageValue/content.d.ts +45 -0
  307. package/dist/src/storageValue/content.d.ts.map +1 -0
  308. package/dist/src/storageValue/findFile.d.ts +7 -0
  309. package/dist/src/storageValue/findFile.d.ts.map +1 -0
  310. package/dist/src/storageValue/resolve.d.ts +71 -0
  311. package/dist/src/storageValue/resolve.d.ts.map +1 -0
  312. package/dist/src/storageValue/save.d.ts +24 -0
  313. package/dist/src/storageValue/save.d.ts.map +1 -0
  314. package/dist/src/storageValue/stage.d.ts +22 -0
  315. package/dist/src/storageValue/stage.d.ts.map +1 -0
  316. package/dist/src/types/db.d.ts +0 -14
  317. package/dist/src/types/db.d.ts.map +1 -1
  318. package/dist/src/types/index.d.ts +3 -3
  319. package/dist/src/types/index.d.ts.map +1 -1
  320. package/dist/src/types/machines.d.ts +1 -1
  321. package/dist/src/types/machines.d.ts.map +1 -1
  322. package/dist/src/types/property.d.ts +9 -16
  323. package/dist/src/types/property.d.ts.map +1 -1
  324. package/dist/src/types/publish.d.ts +1 -1
  325. package/dist/src/types/publish.d.ts.map +1 -1
  326. package/dist/{syncDbWithEas-DUu_wjUG.js → syncDbWithEas-CA7PvBgu.js} +142 -168
  327. package/dist/syncDbWithEas-CA7PvBgu.js.map +1 -0
  328. package/dist/tabEvents-CaVmngqr.js +3804 -0
  329. package/dist/tabEvents-CaVmngqr.js.map +1 -0
  330. package/dist/{tolerantWebAccess-DecKDGen.js → tolerantWebAccess-BJqViq08.js} +10 -15
  331. package/dist/{tolerantWebAccess-DecKDGen.js.map → tolerantWebAccess-BJqViq08.js.map} +1 -1
  332. package/dist/{resolveModelForSyncedSeed-4gO9Hfn2.js → updateSeedRevokedAt-BL9Bg-G7.js} +30 -4
  333. package/dist/updateSeedRevokedAt-BL9Bg-G7.js.map +1 -0
  334. package/package.json +6 -6
  335. package/dist/ClientManager-GZfXErDX.js +0 -5279
  336. package/dist/ClientManager-GZfXErDX.js.map +0 -1
  337. package/dist/Item-0oDoTPhi.js.map +0 -1
  338. package/dist/ModelProperty-i5flwzQG.js.map +0 -1
  339. package/dist/Schema-PFZwG9Nl.js +0 -4370
  340. package/dist/Schema-PFZwG9Nl.js.map +0 -1
  341. package/dist/backfillMetadataPropertyIds-BbxgavEQ.js +0 -72
  342. package/dist/backfillMetadataPropertyIds-BbxgavEQ.js.map +0 -1
  343. package/dist/changeFeed-DKdVlUmt.js +0 -726
  344. package/dist/changeFeed-DKdVlUmt.js.map +0 -1
  345. package/dist/configurePlatform-CrMafpMW.js.map +0 -1
  346. package/dist/dist/ClientManager-GZfXErDX.d.ts +0 -952
  347. package/dist/dist/ClientManager-GZfXErDX.d.ts.map +0 -1
  348. package/dist/dist/Item-0oDoTPhi.d.ts +0 -1076
  349. package/dist/dist/Item-0oDoTPhi.d.ts.map +0 -1
  350. package/dist/dist/ModelProperty-i5flwzQG.d.ts +0 -315
  351. package/dist/dist/ModelProperty-i5flwzQG.d.ts.map +0 -1
  352. package/dist/dist/Schema-PFZwG9Nl.d.ts +0 -432
  353. package/dist/dist/Schema-PFZwG9Nl.d.ts.map +0 -1
  354. package/dist/dist/SchemaValidationService-DnGKel4V.d.ts +0 -118
  355. package/dist/dist/SchemaValidationService-DnGKel4V.d.ts.map +0 -1
  356. package/dist/dist/backfillMetadataPropertyIds-BbxgavEQ.d.ts +0 -7
  357. package/dist/dist/backfillMetadataPropertyIds-BbxgavEQ.d.ts.map +0 -1
  358. package/dist/dist/changeFeed-DKdVlUmt.d.ts +0 -160
  359. package/dist/dist/changeFeed-DKdVlUmt.d.ts.map +0 -1
  360. package/dist/dist/configurePlatform-CrMafpMW.d.ts.map +0 -1
  361. package/dist/dist/detachRowsBeforeDelete-DXFYSHRR.d.ts +0 -13
  362. package/dist/dist/detachRowsBeforeDelete-DXFYSHRR.d.ts.map +0 -1
  363. package/dist/dist/eas-dyepi5jz.d.ts +0 -12
  364. package/dist/dist/eas-dyepi5jz.d.ts.map +0 -1
  365. package/dist/dist/errors-C1qUEZ7A.d.ts +0 -25
  366. package/dist/dist/errors-C1qUEZ7A.d.ts.map +0 -1
  367. package/dist/dist/errors-D1zHAqjr.d.ts +0 -515
  368. package/dist/dist/errors-D1zHAqjr.d.ts.map +0 -1
  369. package/dist/dist/eventBus-7vxTCc3a.d.ts +0 -18
  370. package/dist/dist/eventBus-7vxTCc3a.d.ts.map +0 -1
  371. package/dist/dist/findSeedRowsByUid-CT3sOXXw.d.ts +0 -13
  372. package/dist/dist/findSeedRowsByUid-CT3sOXXw.d.ts.map +0 -1
  373. package/dist/dist/getItem-Bhjecbia.d.ts +0 -6
  374. package/dist/dist/getItem-Bhjecbia.d.ts.map +0 -1
  375. package/dist/dist/getPublishPayload-DH4rMqXG.d.ts +0 -47
  376. package/dist/dist/getPublishPayload-DH4rMqXG.d.ts.map +0 -1
  377. package/dist/dist/getPublishUploads-BzUIDA2G.d.ts +0 -89
  378. package/dist/dist/getPublishUploads-BzUIDA2G.d.ts.map +0 -1
  379. package/dist/dist/index-CkYb3wcU.d.ts +0 -14
  380. package/dist/dist/index-CkYb3wcU.d.ts.map +0 -1
  381. package/dist/dist/index-DJmWkVIK.d.ts +0 -6
  382. package/dist/dist/index-DJmWkVIK.d.ts.map +0 -1
  383. package/dist/dist/index-Dx_GTdyQ.d.ts.map +0 -1
  384. package/dist/dist/index.browser-i9uPJice.d.ts +0 -9908
  385. package/dist/dist/index.browser-i9uPJice.d.ts.map +0 -1
  386. package/dist/dist/mimeTypes-C9r76DCH.d.ts +0 -30
  387. package/dist/dist/mimeTypes-C9r76DCH.d.ts.map +0 -1
  388. package/dist/dist/model-oHA3YhOd.d.ts +0 -6
  389. package/dist/dist/model-oHA3YhOd.d.ts.map +0 -1
  390. package/dist/dist/modelPropertyRecordSchemas-C5lW9aA-.d.ts +0 -7
  391. package/dist/dist/modelPropertyRecordSchemas-C5lW9aA-.d.ts.map +0 -1
  392. package/dist/dist/platform.browser.d.ts +0 -3
  393. package/dist/dist/platform.browser.d.ts.map +0 -1
  394. package/dist/dist/property-BwYtg5Q_.d.ts +0 -37
  395. package/dist/dist/property-BwYtg5Q_.d.ts.map +0 -1
  396. package/dist/dist/propertyValidationRules-CyHdpcPP.d.ts +0 -13
  397. package/dist/dist/propertyValidationRules-CyHdpcPP.d.ts.map +0 -1
  398. package/dist/dist/publishDraftGraph-C5F6b7GK.d.ts +0 -93
  399. package/dist/dist/publishDraftGraph-C5F6b7GK.d.ts.map +0 -1
  400. package/dist/dist/resolveModelForSyncedSeed-4gO9Hfn2.d.ts +0 -21
  401. package/dist/dist/resolveModelForSyncedSeed-4gO9Hfn2.d.ts.map +0 -1
  402. package/dist/dist/syncDbWithEas-DUu_wjUG.d.ts +0 -13
  403. package/dist/dist/syncDbWithEas-DUu_wjUG.d.ts.map +0 -1
  404. package/dist/dist/tolerantWebAccess-DecKDGen.d.ts +0 -113
  405. package/dist/dist/tolerantWebAccess-DecKDGen.d.ts.map +0 -1
  406. package/dist/dist/updateSeedRevokedAt-CP606vKs.d.ts +0 -11
  407. package/dist/dist/updateSeedRevokedAt-CP606vKs.d.ts.map +0 -1
  408. package/dist/eas-dyepi5jz.js +0 -67
  409. package/dist/eas-dyepi5jz.js.map +0 -1
  410. package/dist/errors-D1zHAqjr.js +0 -4006
  411. package/dist/errors-D1zHAqjr.js.map +0 -1
  412. package/dist/eventBus-7vxTCc3a.js +0 -64
  413. package/dist/eventBus-7vxTCc3a.js.map +0 -1
  414. package/dist/findSeedRowsByUid-CT3sOXXw.js +0 -47
  415. package/dist/findSeedRowsByUid-CT3sOXXw.js.map +0 -1
  416. package/dist/getItem-Bhjecbia.js +0 -45
  417. package/dist/getItem-Bhjecbia.js.map +0 -1
  418. package/dist/getPublishPayload-DH4rMqXG.js +0 -1634
  419. package/dist/getPublishPayload-DH4rMqXG.js.map +0 -1
  420. package/dist/getPublishUploads-BzUIDA2G.js +0 -1080
  421. package/dist/getPublishUploads-BzUIDA2G.js.map +0 -1
  422. package/dist/index-CkYb3wcU.js +0 -29
  423. package/dist/index-CkYb3wcU.js.map +0 -1
  424. package/dist/index-DJmWkVIK.js.map +0 -1
  425. package/dist/index-Dx_GTdyQ.js +0 -4745
  426. package/dist/index-Dx_GTdyQ.js.map +0 -1
  427. package/dist/index-PbnKw2es.js.map +0 -1
  428. package/dist/index.browser-i9uPJice.js.map +0 -1
  429. package/dist/index.node-CzMwMr_d.js.map +0 -1
  430. package/dist/mimeTypes-C9r76DCH.js +0 -209
  431. package/dist/mimeTypes-C9r76DCH.js.map +0 -1
  432. package/dist/model-oHA3YhOd.js +0 -29
  433. package/dist/model-oHA3YhOd.js.map +0 -1
  434. package/dist/modelPropertyRecordSchemas-C5lW9aA-.js +0 -94
  435. package/dist/modelPropertyRecordSchemas-C5lW9aA-.js.map +0 -1
  436. package/dist/property-BwYtg5Q_.js +0 -289
  437. package/dist/property-BwYtg5Q_.js.map +0 -1
  438. package/dist/propertyValidationRules-CyHdpcPP.js +0 -73
  439. package/dist/propertyValidationRules-CyHdpcPP.js.map +0 -1
  440. package/dist/publishDraftGraph-C5F6b7GK.js +0 -505
  441. package/dist/publishDraftGraph-C5F6b7GK.js.map +0 -1
  442. package/dist/resolveModelForSyncedSeed-4gO9Hfn2.js.map +0 -1
  443. package/dist/src/Item/service/actors/loadOrCreateItem.d.ts +0 -5
  444. package/dist/src/Item/service/actors/loadOrCreateItem.d.ts.map +0 -1
  445. package/dist/src/Item/service/actors/waitForDb.d.ts +0 -4
  446. package/dist/src/Item/service/actors/waitForDb.d.ts.map +0 -1
  447. package/dist/src/ItemProperty/service/actors/loadOrCreateProperty.d.ts +0 -5
  448. package/dist/src/ItemProperty/service/actors/loadOrCreateProperty.d.ts.map +0 -1
  449. package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts +0 -5
  450. package/dist/src/ItemProperty/service/actors/saveValueToDb/analyzeInput.d.ts.map +0 -1
  451. package/dist/src/ItemProperty/service/actors/saveValueToDb/index.d.ts +0 -6
  452. package/dist/src/ItemProperty/service/actors/saveValueToDb/index.d.ts.map +0 -1
  453. package/dist/src/ItemProperty/service/actors/saveValueToDb/rowContext.d.ts.map +0 -1
  454. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveFile.d.ts +0 -24
  455. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveFile.d.ts.map +0 -1
  456. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveHtml.d.ts +0 -5
  457. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveHtml.d.ts.map +0 -1
  458. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveImage.d.ts +0 -5
  459. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveImage.d.ts.map +0 -1
  460. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts +0 -10
  461. package/dist/src/ItemProperty/service/actors/saveValueToDb/saveItemStorage.d.ts.map +0 -1
  462. package/dist/src/ItemProperty/service/actors/saveValueToDb/storageValue.d.ts +0 -22
  463. package/dist/src/ItemProperty/service/actors/saveValueToDb/storageValue.d.ts.map +0 -1
  464. package/dist/src/ItemProperty/service/actors/waitForDb.d.ts +0 -4
  465. package/dist/src/ItemProperty/service/actors/waitForDb.d.ts.map +0 -1
  466. package/dist/src/Model/service/actors/createModelProperties.d.ts +0 -9
  467. package/dist/src/Model/service/actors/createModelProperties.d.ts.map +0 -1
  468. package/dist/src/Model/service/actors/loadOrCreateModel.d.ts +0 -5
  469. package/dist/src/Model/service/actors/loadOrCreateModel.d.ts.map +0 -1
  470. package/dist/src/Schema/service/actors/checkExistingSchema.d.ts +0 -5
  471. package/dist/src/Schema/service/actors/checkExistingSchema.d.ts.map +0 -1
  472. package/dist/src/Schema/service/actors/createModelInstances.d.ts +0 -11
  473. package/dist/src/Schema/service/actors/createModelInstances.d.ts.map +0 -1
  474. package/dist/src/Schema/service/actors/createPropertyInstances.d.ts +0 -12
  475. package/dist/src/Schema/service/actors/createPropertyInstances.d.ts.map +0 -1
  476. package/dist/src/Schema/service/actors/validateSchema.d.ts +0 -5
  477. package/dist/src/Schema/service/actors/validateSchema.d.ts.map +0 -1
  478. package/dist/src/Schema/service/actors/verifyModelInstancesInCache.d.ts +0 -6
  479. package/dist/src/Schema/service/actors/verifyModelInstancesInCache.d.ts.map +0 -1
  480. package/dist/src/Schema/service/actors/verifyModelsInDb.d.ts +0 -7
  481. package/dist/src/Schema/service/actors/verifyModelsInDb.d.ts.map +0 -1
  482. package/dist/src/Schema/service/actors/verifyPropertiesInDb.d.ts +0 -8
  483. package/dist/src/Schema/service/actors/verifyPropertiesInDb.d.ts.map +0 -1
  484. package/dist/src/Schema/service/actors/verifyPropertyInstancesInCache.d.ts +0 -6
  485. package/dist/src/Schema/service/actors/verifyPropertyInstancesInCache.d.ts.map +0 -1
  486. package/dist/src/Schema/service/actors/verifySchemaInDb.d.ts +0 -7
  487. package/dist/src/Schema/service/actors/verifySchemaInDb.d.ts.map +0 -1
  488. package/dist/src/Schema/service/actors/writeModelsToDb.d.ts +0 -9
  489. package/dist/src/Schema/service/actors/writeModelsToDb.d.ts.map +0 -1
  490. package/dist/src/Schema/service/actors/writePropertiesToDb.d.ts +0 -10
  491. package/dist/src/Schema/service/actors/writePropertiesToDb.d.ts.map +0 -1
  492. package/dist/src/Schema/service/actors/writeSchemaToDb.d.ts +0 -11
  493. package/dist/src/Schema/service/actors/writeSchemaToDb.d.ts.map +0 -1
  494. package/dist/src/browser/helpers/ArweaveClient.d.ts +0 -14
  495. package/dist/src/browser/helpers/ArweaveClient.d.ts.map +0 -1
  496. package/dist/src/browser/helpers/EasClient.d.ts +0 -10
  497. package/dist/src/browser/helpers/EasClient.d.ts.map +0 -1
  498. package/dist/src/browser/helpers/QueryClient.d.ts +0 -9
  499. package/dist/src/browser/helpers/QueryClient.d.ts.map +0 -1
  500. package/dist/src/client/actors/addModelsToStore.d.ts +0 -4
  501. package/dist/src/client/actors/addModelsToStore.d.ts.map +0 -1
  502. package/dist/src/db/backfillMetadataPropertyIds.d.ts +0 -7
  503. package/dist/src/db/backfillMetadataPropertyIds.d.ts.map +0 -1
  504. package/dist/src/db/read/getItem.d.ts +0 -3
  505. package/dist/src/db/read/getItem.d.ts.map +0 -1
  506. package/dist/src/db/read/getPropertyData.d.ts +0 -4
  507. package/dist/src/db/read/getPropertyData.d.ts.map +0 -1
  508. package/dist/src/db/read/getRelatedItemsForPublish.d.ts +0 -10
  509. package/dist/src/db/read/getRelatedItemsForPublish.d.ts.map +0 -1
  510. package/dist/src/db/read/getSeedData.d.ts +0 -12
  511. package/dist/src/db/read/getSeedData.d.ts.map +0 -1
  512. package/dist/src/db/read/itemLoads.d.ts +0 -20
  513. package/dist/src/db/read/itemLoads.d.ts.map +0 -1
  514. package/dist/src/db/read/publishDraftGraph.d.ts +0 -64
  515. package/dist/src/db/read/publishDraftGraph.d.ts.map +0 -1
  516. package/dist/src/db/write/linkModelToSchema.d.ts +0 -12
  517. package/dist/src/db/write/linkModelToSchema.d.ts.map +0 -1
  518. package/dist/src/helpers/ArweaveClient/BaseArweaveClient.d.ts +0 -2
  519. package/dist/src/helpers/ArweaveClient/BaseArweaveClient.d.ts.map +0 -1
  520. package/dist/src/helpers/ArweaveClient/arweaveL1UploadApi.d.ts +0 -2
  521. package/dist/src/helpers/ArweaveClient/arweaveL1UploadApi.d.ts.map +0 -1
  522. package/dist/src/helpers/ArweaveClient/selectReadGateway.d.ts +0 -2
  523. package/dist/src/helpers/ArweaveClient/selectReadGateway.d.ts.map +0 -1
  524. package/dist/src/helpers/ArweaveClient/uploadApiVerification.d.ts +0 -2
  525. package/dist/src/helpers/ArweaveClient/uploadApiVerification.d.ts.map +0 -1
  526. package/dist/src/helpers/EasClient/BaseEasClient.d.ts +0 -2
  527. package/dist/src/helpers/EasClient/BaseEasClient.d.ts.map +0 -1
  528. package/dist/src/helpers/QueryClient/BaseQueryClient.d.ts +0 -2
  529. package/dist/src/helpers/QueryClient/BaseQueryClient.d.ts.map +0 -1
  530. package/dist/src/helpers/easUid.d.ts +0 -2
  531. package/dist/src/helpers/easUid.d.ts.map +0 -1
  532. package/dist/src/helpers/entity/entityDestroy.d.ts +0 -49
  533. package/dist/src/helpers/entity/entityDestroy.d.ts.map +0 -1
  534. package/dist/src/helpers/entity/entityFind.d.ts +0 -52
  535. package/dist/src/helpers/entity/entityFind.d.ts.map +0 -1
  536. package/dist/src/helpers/entity/entityState.d.ts +0 -28
  537. package/dist/src/helpers/entity/entityState.d.ts.map +0 -1
  538. package/dist/src/helpers/entity/entityUnload.d.ts +0 -56
  539. package/dist/src/helpers/entity/entityUnload.d.ts.map +0 -1
  540. package/dist/src/helpers/entity/evictionEpoch.d.ts +0 -43
  541. package/dist/src/helpers/entity/evictionEpoch.d.ts.map +0 -1
  542. package/dist/src/helpers/entity/index.d.ts +0 -12
  543. package/dist/src/helpers/entity/index.d.ts.map +0 -1
  544. package/dist/src/helpers/gateway/gatewayState.d.ts +0 -2
  545. package/dist/src/helpers/gateway/gatewayState.d.ts.map +0 -1
  546. package/dist/src/helpers/gateway/resolveSeedGatewayEndpoints.d.ts +0 -2
  547. package/dist/src/helpers/gateway/resolveSeedGatewayEndpoints.d.ts.map +0 -1
  548. package/dist/src/helpers/gateway/seedGatewayConfig.d.ts +0 -2
  549. package/dist/src/helpers/gateway/seedGatewayConfig.d.ts.map +0 -1
  550. package/dist/src/helpers/getSchemaForItemProperty.d.ts +0 -15
  551. package/dist/src/helpers/getSchemaForItemProperty.d.ts.map +0 -1
  552. package/dist/src/helpers/getSegmentedItemProperties.d.ts +0 -10
  553. package/dist/src/helpers/getSegmentedItemProperties.d.ts.map +0 -1
  554. package/dist/src/helpers/isSqliteUniqueConstraintError.d.ts +0 -7
  555. package/dist/src/helpers/isSqliteUniqueConstraintError.d.ts.map +0 -1
  556. package/dist/src/helpers/propertyValidationRules.d.ts +0 -20
  557. package/dist/src/helpers/propertyValidationRules.d.ts.map +0 -1
  558. package/dist/src/helpers/propertyWriteTickets.d.ts +0 -11
  559. package/dist/src/helpers/propertyWriteTickets.d.ts.map +0 -1
  560. package/dist/src/helpers/reconcilePropertyDraftRows.d.ts +0 -10
  561. package/dist/src/helpers/reconcilePropertyDraftRows.d.ts.map +0 -1
  562. package/dist/src/helpers/waitForEntityIdle.d.ts +0 -21
  563. package/dist/src/helpers/waitForEntityIdle.d.ts.map +0 -1
  564. package/dist/src/interfaces/IQueryClient.d.ts +0 -15
  565. package/dist/src/interfaces/IQueryClient.d.ts.map +0 -1
  566. package/dist/src/node/helpers/ArweaveClient.d.ts +0 -5
  567. package/dist/src/node/helpers/ArweaveClient.d.ts.map +0 -1
  568. package/dist/src/node/helpers/EasClient.d.ts +0 -5
  569. package/dist/src/node/helpers/EasClient.d.ts.map +0 -1
  570. package/dist/src/node/helpers/QueryClient.d.ts +0 -5
  571. package/dist/src/node/helpers/QueryClient.d.ts.map +0 -1
  572. package/dist/src/services/write/actors/validateEntity.d.ts +0 -6
  573. package/dist/src/services/write/actors/validateEntity.d.ts.map +0 -1
  574. package/dist/src/services/write/actors/writeToDatabase.d.ts +0 -14
  575. package/dist/src/services/write/actors/writeToDatabase.d.ts.map +0 -1
  576. package/dist/src/services/write/writeProcessMachine.d.ts +0 -56
  577. package/dist/src/services/write/writeProcessMachine.d.ts.map +0 -1
  578. package/dist/src/stores/eas.d.ts +0 -2
  579. package/dist/src/stores/eas.d.ts.map +0 -1
  580. package/dist/src/types/arweave.d.ts +0 -57
  581. package/dist/src/types/arweave.d.ts.map +0 -1
  582. package/dist/src/types/gateway.d.ts +0 -2
  583. package/dist/src/types/gateway.d.ts.map +0 -1
  584. package/dist/syncDbWithEas-DUu_wjUG.js.map +0 -1
  585. package/dist/updateSeedRevokedAt-CP606vKs.js +0 -31
  586. package/dist/updateSeedRevokedAt-CP606vKs.js.map +0 -1
@@ -1,4745 +0,0 @@
1
- import debug from 'debug';
2
- import { isEasAttestationExplorerUrl, BaseArweaveClient, ensureReadGatewaySelected, getArweaveReadBaseUrls, getTransactionTagsByIds } from '@seedprotocol/arweave';
3
- import { VERSION_SCHEMA_UID, easName, DEFAULT_NAME_MATCH_LEVEL, compareNameMatchTiers, isNameMatchLevel, localNamesInBestMatchTier, BaseQueryClient, BaseEasClient, GET_FILES_METADATA } from '@seedprotocol/eas';
4
- import { waitFor } from 'xstate';
5
- import pluralize from 'pluralize';
6
- import { Type } from '@sinclair/typebox';
7
- import { sqliteTable, integer, text, unique, int, index, blob, uniqueIndex } from 'drizzle-orm/sqlite-core';
8
- import { relations, and, eq, inArray, desc, isNull, isNotNull, or } from 'drizzle-orm';
9
- import { customAlphabet } from 'nanoid';
10
- import { alphanumeric } from 'nanoid-dictionary';
11
- import { sha256 } from '@noble/hashes/sha256';
12
- import { sha3_256 } from '@noble/hashes/sha3';
13
- import { bytesToHex } from '@noble/hashes/utils';
14
- import { throttle } from 'lodash-es';
15
-
16
- const logger$5 = debug('seedSdk:browser:db:sqliteBusyRetry');
17
- /** Waits between attempts; about 1 s in total. */
18
- const SQLITE_BUSY_RETRY_DELAYS_MS = [25, 75, 150, 300, 450];
19
- /**
20
- * Another connection (usually another tab) holds the database lock. SQLite reports this as
21
- * SQLITE_BUSY ("database is locked") or SQLITE_LOCKED ("database table is locked").
22
- */
23
- function isSqliteBusyError(error) {
24
- // Drizzle wraps a driver's error ("Failed query: ...") and keeps it as `cause`
25
- for (let current = error, depth = 0; current != null && depth < 5; depth++) {
26
- const message = current instanceof Error ? current.message : String(current);
27
- if (/SQLITE_BUSY|SQLITE_LOCKED|database (table )?is locked/i.test(message))
28
- return true;
29
- current = current.cause;
30
- }
31
- return false;
32
- }
33
- /**
34
- * Retries a statement another tab's lock rejected. Safe for the SDK's writes because each is a
35
- * single autocommit statement: one that failed with SQLITE_BUSY didn't apply. Don't use this for
36
- * statements inside a transaction, where a retry would replay part of it.
37
- */
38
- function withSqliteBusyRetry(driver, delaysMs = SQLITE_BUSY_RETRY_DELAYS_MS) {
39
- return async (...args) => {
40
- for (let attempt = 0;; attempt++) {
41
- try {
42
- return await driver(...args);
43
- }
44
- catch (error) {
45
- if (!isSqliteBusyError(error) || attempt >= delaysMs.length)
46
- throw error;
47
- logger$5(`database busy (attempt ${attempt + 1}); retrying`);
48
- await new Promise((resolve) => setTimeout(resolve, delaysMs[attempt]));
49
- }
50
- }
51
- };
52
- }
53
-
54
- class BaseDb {
55
- static configure(impl) {
56
- if (!impl) {
57
- throw new Error('Cannot configure Db with undefined or null. Ensure the platform-specific Db is properly created.');
58
- }
59
- BaseDb._impl = impl;
60
- }
61
- static requireImpl() {
62
- if (!BaseDb._impl) {
63
- throw new Error('Db not configured. Call BaseDb.configure() during platform init.');
64
- }
65
- return BaseDb._impl;
66
- }
67
- static getAppDb() {
68
- return BaseDb.requireImpl().getAppDb();
69
- }
70
- static prepareDb(filesDir, config) {
71
- return BaseDb.requireImpl().prepareDb(filesDir, config);
72
- }
73
- static isAppDbReady() {
74
- if (!BaseDb._impl) {
75
- return false;
76
- }
77
- return BaseDb._impl.isAppDbReady();
78
- }
79
- static connectToDb(pathToDir) {
80
- return BaseDb.requireImpl().connectToDb(pathToDir);
81
- }
82
- static async migrate(pathToDbDir, dbName, dbId) {
83
- return BaseDb.requireImpl().migrate(pathToDbDir, dbName, dbId);
84
- }
85
- /**
86
- * Execute a reactive query that emits new results whenever the underlying data changes.
87
- *
88
- * Supports two usage patterns:
89
- * 1. SQL tag function (browser only): liveQuery((sql) => sql`SELECT * FROM models`)
90
- * 2. Drizzle query builder: liveQuery(db.select().from(models))
91
- *
92
- * On Node only the query builder works (a tag function throws), and it is re-run every second;
93
- * the observable emits when the result changes.
94
- */
95
- static liveQuery(query) {
96
- return BaseDb.requireImpl().liveQuery(query);
97
- }
98
- /**
99
- * Runs `fn` in one transaction: every query it runs on `tx` (a Drizzle database, used like
100
- * `getAppDb()`) commits together, or none does when `fn` throws. Queries on `getAppDb()` inside
101
- * `fn` are not part of the transaction, and in the browser they wait for it to end, so don't
102
- * call `getAppDb()` or `BaseDb.transaction` from `fn`.
103
- *
104
- * When another connection (another tab) holds the database lock, the whole transaction is
105
- * retried, so `fn` must have no side effects outside `tx`.
106
- */
107
- static async transaction(fn) {
108
- const impl = BaseDb.requireImpl();
109
- for (let attempt = 0;; attempt++) {
110
- try {
111
- return await impl.transaction(fn);
112
- }
113
- catch (error) {
114
- if (!isSqliteBusyError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length)
115
- throw error;
116
- await new Promise((resolve) => setTimeout(resolve, SQLITE_BUSY_RETRY_DELAYS_MS[attempt]));
117
- }
118
- }
119
- }
120
- }
121
- BaseDb._impl = null;
122
-
123
- var BaseDb$1 = /*#__PURE__*/Object.freeze({
124
- __proto__: null,
125
- BaseDb: BaseDb
126
- });
127
-
128
- const models = sqliteTable('models', {
129
- id: integer('id').primaryKey({ autoIncrement: true }),
130
- name: text('name').notNull(),
131
- schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking - must be unique
132
- isEdited: integer('is_edited', { mode: 'boolean' }), // true if model has been edited locally, false if matches schema file
133
- }, (table) => {
134
- return {
135
- uniqueSchemaFileId: unique('unique_schema_file_id').on(table.schemaFileId),
136
- };
137
- });
138
- const modelsRelations = relations(models, ({ many }) => ({
139
- properties: many(properties),
140
- }));
141
- const properties = sqliteTable('properties', {
142
- id: integer('id').primaryKey({ autoIncrement: true }),
143
- name: text('name').notNull(),
144
- dataType: text('data_type').notNull(),
145
- modelId: integer('model_id')
146
- .notNull()
147
- .references(() => models.id),
148
- refModelId: integer('ref_model_id').references(() => models.id),
149
- refValueType: text('ref_value_type'),
150
- schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking
151
- isEdited: integer('is_edited', { mode: 'boolean' }), // true if property has been edited locally, false if matches schema file
152
- required: integer('required', { mode: 'boolean' }), // true if relation/image property is required (publish fails when related item not found)
153
- // Storage settings from the schema file's `storage: { type, path, extension }`, in the internal
154
- // shape ModelProperty uses. Null when the property has no storage config.
155
- storageType: text('storage_type'), // 'ItemStorage' | 'PropertyStorage'
156
- localStorageDir: text('local_storage_dir'), // storage.path, e.g. '/html'
157
- filenameSuffix: text('filename_suffix'), // storage.extension, e.g. '.html'
158
- }, (table) => {
159
- return {
160
- uniqueNameModelId: unique('unique_name_model_id').on(table.name, table.modelId),
161
- uniqueSchemaFileId: unique('unique_property_schema_file_id').on(table.schemaFileId),
162
- };
163
- });
164
- const propertiesRelations = relations(properties, ({ one }) => ({
165
- model: one(models),
166
- refModel: one(models),
167
- }));
168
-
169
- var ModelSchema = /*#__PURE__*/Object.freeze({
170
- __proto__: null,
171
- models: models,
172
- modelsRelations: modelsRelations,
173
- properties: properties,
174
- propertiesRelations: propertiesRelations
175
- });
176
-
177
- class BaseFileManager {
178
- static configure(impl) {
179
- if (!impl) {
180
- throw new Error('Cannot configure FileManager with undefined or null. Ensure the platform-specific FileManager is properly created.');
181
- }
182
- BaseFileManager._impl = impl;
183
- }
184
- static requireImpl() {
185
- if (!BaseFileManager._impl) {
186
- throw new Error('FileManager not configured. Call BaseFileManager.configure() during platform init.');
187
- }
188
- return BaseFileManager._impl;
189
- }
190
- /**
191
- * Clears FS init flags so a later call can retry. Used after a failed init
192
- * and by tests. Does not unmount or delete any files.
193
- */
194
- static resetInitializationState() {
195
- this.fileSystemInitialized = false;
196
- this.initializing = false;
197
- this.initializePromise = null;
198
- this.workingDir = undefined;
199
- }
200
- static initializeFileSystem(workingDir) {
201
- if (this.fileSystemInitialized) {
202
- return Promise.resolve();
203
- }
204
- if (this.initializePromise) {
205
- return this.initializePromise;
206
- }
207
- this.initializing = true;
208
- const pending = this.runInitializeFileSystem(workingDir).finally(() => {
209
- this.initializing = false;
210
- if (this.initializePromise === pending) {
211
- this.initializePromise = null;
212
- }
213
- });
214
- this.initializePromise = pending;
215
- return pending;
216
- }
217
- static async runInitializeFileSystem(workingDir) {
218
- await BaseFileManager.requireImpl().initializeFileSystem(workingDir);
219
- this.fileSystemInitialized = true;
220
- this.workingDir = workingDir;
221
- }
222
- static getWorkingDir() {
223
- if (!this.workingDir) {
224
- throw new Error('Working directory is not set');
225
- }
226
- return this.workingDir;
227
- }
228
- /**
229
- * Build a path under the configured files root (e.g. /app-files).
230
- * Use this instead of hardcoding /files/ for images, html, json, etc.
231
- * @param subpaths - path segments to join (e.g. 'images', fileName)
232
- * @returns full path like /app-files/images/egg.jpg
233
- */
234
- static getFilesPath(...subpaths) {
235
- const root = this.getWorkingDir().replace(/\/$/, '');
236
- const parts = [root, ...subpaths].filter(Boolean);
237
- return parts.join('/').replace(/\/+/g, '/');
238
- }
239
- static getContentUrlFromPath(path) {
240
- return BaseFileManager.requireImpl().getContentUrlFromPath(path);
241
- }
242
- static downloadAllFiles(params) {
243
- return BaseFileManager.requireImpl().downloadAllFiles(params);
244
- }
245
- static downloadFileByTransactionId({ transactionId, arweaveHost, arweaveBaseUrls, excludedTransactions, expectedContentHash, compositeChildPaths, skipExisting, }) {
246
- return BaseFileManager.requireImpl().downloadAllFiles({
247
- transactionIds: [transactionId],
248
- arweaveHost,
249
- arweaveBaseUrls,
250
- excludedTransactions,
251
- expectedContentHashes: expectedContentHash ? { [transactionId]: expectedContentHash } : undefined,
252
- compositeChildPaths: compositeChildPaths ? { [transactionId]: compositeChildPaths } : undefined,
253
- skipExisting,
254
- });
255
- }
256
- static resizeImage({ filePath, width, height }) {
257
- return BaseFileManager.requireImpl().resizeImage({ filePath, width, height });
258
- }
259
- static resizeAllImages({ width, height }) {
260
- return BaseFileManager.requireImpl().resizeAllImages({ width, height });
261
- }
262
- static pathExists(filePath) {
263
- return BaseFileManager.requireImpl().pathExists(filePath);
264
- }
265
- static invalidateCachedPaths(filePaths) {
266
- return BaseFileManager.requireImpl().invalidateCachedPaths(filePaths);
267
- }
268
- /** File size in bytes, or null if the path does not exist / cannot be stated. */
269
- static getFileSize(filePath) {
270
- return BaseFileManager.requireImpl().getFileSize(filePath);
271
- }
272
- /**
273
- * Returns a list of filenames in the given directory (e.g. 'images', 'files').
274
- */
275
- static listFiles(dir) {
276
- return BaseFileManager.requireImpl().listFiles(dir);
277
- }
278
- /**
279
- * Returns a list of image filenames in the images folder (originals only, excludes size subdirs).
280
- * Use this to get all stored images without traversing 480/760/1024/1440/1920 subdirectories.
281
- */
282
- static listImageFiles() {
283
- return this.listFiles('images');
284
- }
285
- static createDirIfNotExists(filePath) {
286
- return BaseFileManager.requireImpl().createDirIfNotExists(filePath);
287
- }
288
- static async waitForFile(filePath) {
289
- return BaseFileManager.requireImpl().waitForFile(filePath);
290
- }
291
- static async waitForFileWithContent(filePath, interval, timeout) {
292
- return BaseFileManager.requireImpl().waitForFileWithContent(filePath, interval, timeout);
293
- }
294
- static async saveFile(filePath, content) {
295
- return BaseFileManager.requireImpl().saveFile(filePath, content);
296
- }
297
- static saveFileSync(filePath, content) {
298
- return BaseFileManager.requireImpl().saveFileSync(filePath, content);
299
- }
300
- static async readFile(filePath) {
301
- return BaseFileManager.requireImpl().readFile(filePath);
302
- }
303
- static readFileSync(filePath) {
304
- return BaseFileManager.requireImpl().readFileSync(filePath);
305
- }
306
- static async readFileAsBuffer(filePath) {
307
- return BaseFileManager.requireImpl().readFileAsBuffer(filePath);
308
- }
309
- static async readFileAsString(filePath) {
310
- return BaseFileManager.requireImpl().readFileAsString(filePath);
311
- }
312
- static async getFs() {
313
- return BaseFileManager.requireImpl().getFs();
314
- }
315
- static getFsSync() {
316
- return BaseFileManager.requireImpl().getFsSync();
317
- }
318
- static getPathModule() {
319
- return BaseFileManager.requireImpl().getPathModule();
320
- }
321
- static getParentDirPath(filePath) {
322
- return BaseFileManager.requireImpl().getParentDirPath(filePath);
323
- }
324
- static getFilenameFromPath(filePath) {
325
- return BaseFileManager.requireImpl().getFilenameFromPath(filePath);
326
- }
327
- }
328
- BaseFileManager.fileSystemInitialized = false;
329
- BaseFileManager.initializing = false;
330
- BaseFileManager._impl = null;
331
- BaseFileManager.initializePromise = null;
332
-
333
- var BaseFileManager$1 = /*#__PURE__*/Object.freeze({
334
- __proto__: null,
335
- BaseFileManager: BaseFileManager
336
- });
337
-
338
- /** Default maximum length for Text properties when not explicitly set. Html holds arbitrary-length content and does not use this limit. */
339
- const DEFAULT_TEXT_MAX_LENGTH = 255;
340
- /**
341
- * TypeBox schema for ValidationRules
342
- */
343
- const TValidationRules = Type.Object({
344
- pattern: Type.Optional(Type.String()),
345
- minLength: Type.Optional(Type.Integer({ minimum: 0 })),
346
- maxLength: Type.Optional(Type.Integer({ minimum: 1 })),
347
- enum: Type.Optional(Type.Array(Type.Any())),
348
- custom: Type.Optional(Type.String()),
349
- }, { additionalProperties: true });
350
-
351
- /**
352
- * Enum for all property data types
353
- */
354
- var ModelPropertyDataTypes;
355
- (function (ModelPropertyDataTypes) {
356
- ModelPropertyDataTypes["Text"] = "Text";
357
- ModelPropertyDataTypes["Number"] = "Number";
358
- ModelPropertyDataTypes["List"] = "List";
359
- ModelPropertyDataTypes["Relation"] = "Relation";
360
- ModelPropertyDataTypes["Image"] = "Image";
361
- ModelPropertyDataTypes["Json"] = "Json";
362
- ModelPropertyDataTypes["File"] = "File";
363
- ModelPropertyDataTypes["Boolean"] = "Boolean";
364
- ModelPropertyDataTypes["Date"] = "Date";
365
- ModelPropertyDataTypes["Html"] = "Html";
366
- })(ModelPropertyDataTypes || (ModelPropertyDataTypes = {}));
367
- /** Known data types in lowercase for normalization */
368
- const DATA_TYPE_LOWER_MAP = {
369
- text: 'Text',
370
- string: 'Text', // JSON Schema convention
371
- number: 'Number',
372
- list: 'List',
373
- relation: 'Relation',
374
- image: 'Image',
375
- json: 'Json',
376
- file: 'File',
377
- boolean: 'Boolean',
378
- date: 'Date',
379
- html: 'Html',
380
- blob: 'Blob',
381
- };
382
- /**
383
- * Normalize dataType to canonical PascalCase form.
384
- * Accepts lowercase (e.g. 'text') or mixed case and returns 'Text'.
385
- * Use when ingesting dataType from JSON schema files.
386
- */
387
- function normalizeDataType(value) {
388
- if (!value || typeof value !== 'string')
389
- return value ?? '';
390
- const lower = value.toLowerCase().trim();
391
- return DATA_TYPE_LOWER_MAP[lower] ?? value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
392
- }
393
- /**
394
- * Case-insensitive dataType check. Use instead of `dataType === 'List'` so schemas
395
- * (or existing DB rows) with lowercase types like "list" still match.
396
- */
397
- function isDataType(value, expected) {
398
- if (!value)
399
- return false;
400
- return normalizeDataType(value) === expected;
401
- }
402
- /**
403
- * Normalize a property definition to the internal shape ItemProperty reads (`dataType`,
404
- * `refValueType`, `ref`, `storageType`, ...). Accepts schema-file shape (`type`, `model`,
405
- * `items`, `storage`) and any casing. Mirrors the conversion in imports/json createModelFromJson
406
- * for the fallback paths that read schema-file properties directly.
407
- */
408
- function normalizePropertyRecordSchema(def) {
409
- if (!def || typeof def !== 'object')
410
- return def;
411
- const src = def;
412
- const out = { ...src };
413
- const rawType = src.dataType ?? src.type;
414
- if (typeof rawType === 'string' && rawType)
415
- out.dataType = normalizeDataType(rawType);
416
- const ref = src.ref ?? src.refModelName ?? src.items?.model ?? src.model;
417
- if (ref && !out.ref)
418
- out.ref = ref;
419
- if (ref && !out.refModelName)
420
- out.refModelName = ref;
421
- const rawRefValueType = src.refValueType ?? src.items?.type;
422
- if (typeof rawRefValueType === 'string' && rawRefValueType) {
423
- out.refValueType = normalizeDataType(rawRefValueType);
424
- }
425
- else if (out.dataType === ModelPropertyDataTypes.List && ref) {
426
- out.refValueType = ModelPropertyDataTypes.Relation;
427
- }
428
- if (src.storage && typeof src.storage === 'object' && !out.storageType) {
429
- out.storageType = src.storage.type === 'ItemStorage' ? 'ItemStorage' : 'PropertyStorage';
430
- if (src.storage.path !== undefined && out.localStorageDir === undefined)
431
- out.localStorageDir = src.storage.path;
432
- if (src.storage.extension !== undefined && out.filenameSuffix === undefined)
433
- out.filenameSuffix = src.storage.extension;
434
- }
435
- return out;
436
- }
437
- // String literals, not enum members: PropertyDataType must accept plain strings
438
- // such as `{ dataType: 'Text' }`. ModelPropertyDataTypes members still fit.
439
- const TPropertyDataType = Type.Union([
440
- Type.Literal('Text'),
441
- Type.Literal('Number'),
442
- Type.Literal('List'),
443
- Type.Literal('Relation'),
444
- Type.Literal('Image'),
445
- Type.Literal('Json'),
446
- Type.Literal('File'),
447
- Type.Literal('Boolean'),
448
- Type.Literal('Date'),
449
- Type.Literal('Html'),
450
- ]);
451
- const TStorageType = Type.Union([
452
- Type.Literal('ItemStorage'), // Looks for a storageTransactionId property on the item
453
- Type.Literal('PropertyStorage'), // Looks for a storageTransactionId value on the property
454
- ]);
455
- /** How publish handles `data:image/*` URIs inside Html storage. Only applies when `dataType === 'Html'`. */
456
- const THtmlEmbeddedDataUriPolicy = Type.Union([
457
- Type.Literal('materialize'),
458
- Type.Literal('preserve'),
459
- ]);
460
- const TProperty = Type.Object({
461
- id: Type.Optional(Type.String()), // schemaFileId (string) - public ID
462
- _dbId: Type.Optional(Type.Number()), // Database integer ID - internal only
463
- name: Type.Optional(Type.String()),
464
- dataType: TPropertyDataType,
465
- ref: Type.Optional(Type.String()),
466
- // modelId: string (modelFileId) in code; number only for DB foreign keys when resolved
467
- modelId: Type.Optional(Type.Union([Type.Number(), Type.String()])),
468
- modelName: Type.Optional(Type.String()),
469
- refModelId: Type.Optional(Type.Number()),
470
- refModelName: Type.Optional(Type.String()),
471
- refValueType: Type.Optional(TPropertyDataType),
472
- storageType: Type.Optional(TStorageType),
473
- localStorageDir: Type.Optional(Type.String()),
474
- filenameSuffix: Type.Optional(Type.String()),
475
- validation: Type.Optional(TValidationRules),
476
- required: Type.Optional(Type.Boolean()),
477
- /** When set on an Html property: `materialize` uploads embedded images as Image seeds and rewrites src; `preserve` leaves data URIs. */
478
- htmlEmbeddedDataUriPolicy: Type.Optional(THtmlEmbeddedDataUriPolicy),
479
- });
480
- const TPropertyConstructor = Type.Function([
481
- Type.Optional(Type.Union([Type.String(), TStorageType, Type.Undefined()])),
482
- Type.Optional(Type.Union([Type.String(), TPropertyDataType])),
483
- Type.Optional(Type.Union([Type.String(), Type.Boolean()])),
484
- ], TProperty);
485
- const TPropertyDefs = Type.Record(TPropertyDataType, TPropertyConstructor);
486
- function htmlPropertyFactory(opts) {
487
- return {
488
- dataType: ModelPropertyDataTypes.Html,
489
- ...(opts?.htmlEmbeddedDataUriPolicy
490
- ? { htmlEmbeddedDataUriPolicy: opts.htmlEmbeddedDataUriPolicy }
491
- : {}),
492
- };
493
- }
494
- const Property = {
495
- Text: (storageType, localStorageDir, filenameSuffix) => ({
496
- dataType: ModelPropertyDataTypes.Text,
497
- storageType,
498
- localStorageDir,
499
- filenameSuffix,
500
- TObject: Type.String(),
501
- }),
502
- Json: () => ({ dataType: ModelPropertyDataTypes.Json }),
503
- File: () => ({ dataType: ModelPropertyDataTypes.File }),
504
- Number: () => ({ dataType: ModelPropertyDataTypes.Number }),
505
- /**
506
- * Create a List property. refValueType is required; ref is required only for list of relations.
507
- * @param refValueType - Element type: 'Text', 'Number', 'Relation', etc.
508
- * @param ref - Model name when refValueType is 'Relation' (optional for primitives)
509
- * @example List('Text') - list of strings
510
- * @example List('Relation', 'Tag') - list of relations to Tag model
511
- */
512
- List: (refValueType, ref) => ({
513
- dataType: ModelPropertyDataTypes.List,
514
- ref,
515
- refValueType,
516
- }),
517
- Relation: (ref, refValueType, required) => ({
518
- dataType: ModelPropertyDataTypes.Relation,
519
- ref,
520
- refValueType,
521
- required,
522
- }),
523
- Image: () => ({ dataType: ModelPropertyDataTypes.Image }),
524
- Boolean: () => ({ dataType: ModelPropertyDataTypes.Boolean }),
525
- Date: () => ({ dataType: ModelPropertyDataTypes.Date }),
526
- Html: htmlPropertyFactory,
527
- /** `Html` opts are not representable in `TPropertyConstructor`; keep runtime shape via double assert. */
528
- };
529
-
530
- // Define zero constants ourselves since they're not exported from eas-sdk in newer versions
531
- const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
532
- const ZERO_BYTES = '0x';
533
- const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
534
- const INTERNAL_DATA_TYPES = {
535
- Text: {
536
- eas: 'string',
537
- },
538
- // ADR 0010: decimal strings, so negatives, fractions and values above 255 publish.
539
- Number: {
540
- eas: 'string',
541
- },
542
- Image: {
543
- eas: 'bytes32',
544
- },
545
- Relation: {
546
- eas: 'bytes32',
547
- },
548
- List: {
549
- eas: 'bytes32[]',
550
- },
551
- File: {
552
- eas: 'bytes32',
553
- },
554
- Json: {
555
- eas: 'bytes32',
556
- },
557
- Blob: {
558
- eas: 'bytes32',
559
- },
560
- Boolean: {
561
- eas: 'bool',
562
- },
563
- Date: {
564
- eas: 'uint256',
565
- },
566
- Html: {
567
- eas: 'bytes32',
568
- },
569
- };
570
- /** Full property names ending in `Id` that must not be stripped to a “base” name for `allProperties` keys. */
571
- const PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP = new Set(['storageTransactionId']);
572
- const INTERNAL_PROPERTY_NAMES = [
573
- 'localId',
574
- 'uid',
575
- 'seedLocalId',
576
- 'seedUid',
577
- 'schemaUid',
578
- 'attestationCreatedAt',
579
- 'attestationRaw',
580
- 'createdAt',
581
- 'updatedAt',
582
- 'lastVersionPublishedAt',
583
- 'latestVersionLocalId',
584
- 'latestVersionUid',
585
- 'lastLocalUpdateAt',
586
- 'modelName',
587
- 'refSeedType',
588
- 'refValueType',
589
- 'refResolvedValue',
590
- 'refResolvedDisplayValue',
591
- 'type',
592
- // Image
593
- 'src',
594
- 'alt',
595
- //
596
- 'versionLocalId',
597
- 'versionsCount',
598
- 'versionUid',
599
- '_markedForDeletion',
600
- // SDK-internal: wallet address of attester; never publish as property attestation
601
- 'publisher',
602
- // SDK-internal: Unix timestamp when attestations were revoked; never a schema field
603
- 'revokedAt',
604
- ];
605
- /** @deprecated The UID is chain-independent. Use {@link VERSION_SCHEMA_UID}. */
606
- const VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA = VERSION_SCHEMA_UID;
607
- const defaultAttestationData = {
608
- recipient: ZERO_ADDRESS,
609
- revocable: true,
610
- value: BigInt(0),
611
- refUID: ZERO_BYTES32,
612
- expirationTime: BigInt(0),
613
- data: ZERO_BYTES,
614
- };
615
- var ImageSize;
616
- (function (ImageSize) {
617
- ImageSize[ImageSize["EXTRA_SMALL"] = 480] = "EXTRA_SMALL";
618
- ImageSize[ImageSize["SMALL"] = 760] = "SMALL";
619
- ImageSize[ImageSize["MEDIUM"] = 1024] = "MEDIUM";
620
- ImageSize[ImageSize["LARGE"] = 1440] = "LARGE";
621
- ImageSize[ImageSize["EXTRA_LARGE"] = 1920] = "EXTRA_LARGE";
622
- })(ImageSize || (ImageSize = {}));
623
- const CLIENT_NOT_INITIALIZED = 'ClientManager is not initialized. Please call init() first.';
624
- var SeedModels;
625
- (function (SeedModels) {
626
- SeedModels["Seed"] = "Seed";
627
- SeedModels["Metadata"] = "Metadata";
628
- SeedModels["Version"] = "Version";
629
- })(SeedModels || (SeedModels = {}));
630
- // Internal SDK schema that should not be created in app's files directory
631
- const SEED_PROTOCOL_SCHEMA_NAME = 'Seed Protocol';
632
- const INTERNAL_SCHEMA_IDS = ['SEEDPROTOCOL'];
633
- /** Fired after `syncDbWithEas` persists EAS seeds/versions/metadata to SQLite (see `Item.rehydrateCachedItemsFromDbAfterEasSync`). */
634
- const EAS_SEED_DATA_SYNCED_TO_DB_EVENT = 'easSeedDataSyncedToDb';
635
- /**
636
- * Check if a schema is an internal SDK schema that should not be created in app files
637
- */
638
- function isInternalSchema(schemaName, schemaId) {
639
- return schemaName === SEED_PROTOCOL_SCHEMA_NAME ||
640
- (schemaId !== undefined && INTERNAL_SCHEMA_IDS.includes(schemaId));
641
- }
642
- /**
643
- * Model ids (schemaFileId) of the internal Seed Protocol storage models. Storage seeds are always
644
- * these models, even if a user schema also defines a model named Image/File/Html.
645
- */
646
- const INTERNAL_STORAGE_MODEL_FILE_IDS = {
647
- image: 'SEEDPROTOCOL_IMAGE',
648
- file: 'SEEDPROTOCOL_FILE',
649
- html: 'SEEDPROTOCOL_HTML',
650
- };
651
-
652
- /** Ref model name when propDef is a List of Relation, else undefined. */
653
- function listRelationRef(propDef) {
654
- if (!propDef)
655
- return undefined;
656
- if (normalizeDataType(propDef.dataType) !== 'List')
657
- return undefined;
658
- return propDef.ref || propDef.refModelName || undefined;
659
- }
660
- /**
661
- * Storage/EAS name for a List-of-relation property (`authorIdentityIds`), given either its
662
- * schema key (`authors`) or its storage name. A name already in storage form is returned as-is,
663
- * so this is safe on ItemProperties built before their schema was known.
664
- * Returns undefined when propDef is not a List of Relation.
665
- */
666
- function listRelationEasPropertyName(propertyName, propDef) {
667
- const ref = listRelationRef(propDef);
668
- if (!ref || !propertyName)
669
- return undefined;
670
- if (propertyName.endsWith(`${ref}Ids`))
671
- return propertyName;
672
- return `${pluralize(propertyName, 1)}${ref}Ids`;
673
- }
674
- /**
675
- * For a schema property key like `authors` (List of Relation to Identity), returns the
676
- * internal storage/EAS name: `authorIdentityIds` (singular + ref + Ids).
677
- */
678
- function listRelationStoragePropertyName(propertySchemas, schemaPropertyKey) {
679
- return listRelationEasPropertyName(schemaPropertyKey, propertySchemas[schemaPropertyKey]);
680
- }
681
- /**
682
- * Reverse of listRelationStoragePropertyName: maps DB/EAS name `authorIdentityIds` to schema key `authors`.
683
- */
684
- function resolveStorageNameToSchemaName(propertySchemas, storagePropertyName) {
685
- for (const [schemaKey, propDef] of Object.entries(propertySchemas)) {
686
- if (normalizeDataType(propDef?.dataType) !== 'List')
687
- continue;
688
- const ref = propDef.ref || propDef.refModelName;
689
- if (!ref)
690
- continue;
691
- const internalName = `${pluralize(schemaKey, 1)}${ref}Ids`;
692
- if (internalName === storagePropertyName) {
693
- return schemaKey;
694
- }
695
- }
696
- return undefined;
697
- }
698
- /** Internal property names that never use Id-suffix metadata convention */
699
- const ID_SUFFIX_EXCLUDE = new Set(['storageTransactionId', 'transactionId']);
700
- /** Data types that store metadata with Id suffix in DB */
701
- const METADATA_ID_SUFFIX_TYPES = ['Image', 'File', 'Html', 'Relation'];
702
- /**
703
- * Whether this type uses Id suffix in metadata table.
704
- */
705
- function needsMetadataIdSuffix(dataType, refValueType) {
706
- const dt = normalizeDataType(dataType);
707
- const rvt = normalizeDataType(refValueType);
708
- return (METADATA_ID_SUFFIX_TYPES.includes(dt) ||
709
- METADATA_ID_SUFFIX_TYPES.includes(rvt));
710
- }
711
- /**
712
- * Canonical metadata property name for WRITES.
713
- * Use when creating/updating metadata.
714
- */
715
- function toMetadataPropertyName(propertyName, dataType, refValueType) {
716
- if (ID_SUFFIX_EXCLUDE.has(propertyName))
717
- return propertyName;
718
- if (propertyName.endsWith('Ids'))
719
- return propertyName;
720
- if (propertyName.endsWith('Id'))
721
- return propertyName;
722
- if (needsMetadataIdSuffix(dataType, refValueType)) {
723
- return `${propertyName}Id`;
724
- }
725
- return propertyName;
726
- }
727
- /**
728
- * For READS: property names to include in OR query (both variants).
729
- * When dataType is undefined, includes all variants for safety (backwards compat with getPropertyData).
730
- */
731
- function getMetadataPropertyNamesForQuery(propertyName, dataType, refValueType) {
732
- if (ID_SUFFIX_EXCLUDE.has(propertyName))
733
- return [propertyName];
734
- if (propertyName.endsWith('Ids'))
735
- return [propertyName];
736
- if (propertyName.endsWith('Id')) {
737
- const base = propertyName.slice(0, -2);
738
- return [propertyName, base];
739
- }
740
- const needsId = needsMetadataIdSuffix(dataType, refValueType) ||
741
- (dataType === undefined && refValueType === undefined);
742
- if (needsId) {
743
- return [propertyName, `${propertyName}Id`, `${propertyName}Ids`];
744
- }
745
- return [propertyName];
746
- }
747
- /**
748
- * The name an item exposes a property under, from the name it is stored under: its alias when it
749
- * has one (List of relation), else `tagIds` → `tags`, `authorId` → `author`. Names in
750
- * PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP keep their `Id`.
751
- */
752
- function toItemPropertyKey(storedName, alias) {
753
- if (alias)
754
- return alias;
755
- if (storedName.endsWith('Ids'))
756
- return pluralize(storedName.slice(0, -3));
757
- if (storedName.endsWith('Id') && !PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP.has(storedName)) {
758
- return storedName.slice(0, -2);
759
- }
760
- return storedName;
761
- }
762
- /**
763
- * Schema property name from metadata name (strip Id/Ids suffix).
764
- */
765
- function toSchemaPropertyName(propertyName) {
766
- if (propertyName.endsWith('Ids'))
767
- return propertyName.slice(0, -3);
768
- if (propertyName.endsWith('Id'))
769
- return propertyName.slice(0, -2);
770
- return undefined;
771
- }
772
- /**
773
- * Alternate property name for instance lookup (base <-> Id variant).
774
- */
775
- function getAlternatePropertyNameForInstanceLookup(propertyName) {
776
- if (ID_SUFFIX_EXCLUDE.has(propertyName))
777
- return undefined;
778
- if (propertyName.endsWith('Id'))
779
- return propertyName.slice(0, -2);
780
- return `${propertyName}Id`;
781
- }
782
- /**
783
- * Resolve the best metadata record when multiple variants exist (base + Id).
784
- * For Image/File/Html: prefer the record with refResolvedValue (Id variant has filename).
785
- */
786
- function resolveMetadataRecord(records, preferredPropertyName, dataType, refValueType) {
787
- if (records.length === 0)
788
- throw new Error('No records to resolve');
789
- if (records.length === 1)
790
- return records[0];
791
- const needsId = needsMetadataIdSuffix(dataType, refValueType) ||
792
- (dataType === undefined && refValueType === undefined);
793
- // For Image/File/Html: prefer the record with refResolvedValue (Id variant has filename)
794
- if (needsId) {
795
- const withRef = records.find((r) => r.refResolvedValue);
796
- if (withRef)
797
- return withRef;
798
- }
799
- // Also prefer by refSeedType when we need file ref
800
- if (needsId) {
801
- const byRefSeed = records.find((r) => r.refSeedType === 'image' ||
802
- r.refSeedType === 'file' ||
803
- r.refSeedType === 'html');
804
- if (byRefSeed)
805
- return byRefSeed;
806
- }
807
- // Fallback: prefer exact propertyName match, else first
808
- return (records.find((r) => r.propertyName === preferredPropertyName) ?? records[0]);
809
- }
810
- /**
811
- * The EAS property name publish writes for an item property (ADR 0008 §3), already in EAS form:
812
- *
813
- * - List of relations: its `<name><Model>Ids` storage name (`author_identity_ids`).
814
- * - Single Relation, Image, File or Html: its schema key (`author`), never the local `<name>Id`
815
- * storage name. The key is `propertyDef.name` when known, else `propertyName` without its `Id`.
816
- * - Anything else: the property name.
817
- */
818
- function easPropertyNameForPublish(propertyName, propertyDef) {
819
- const listRelationName = listRelationEasPropertyName(propertyName, propertyDef ?? undefined);
820
- if (listRelationName)
821
- return easName(listRelationName);
822
- if (propertyDef &&
823
- normalizeDataType(propertyDef.dataType) !== 'List' &&
824
- needsMetadataIdSuffix(propertyDef.dataType, propertyDef.refValueType) &&
825
- !ID_SUFFIX_EXCLUDE.has(propertyName)) {
826
- const key = propertyDef.name ?? (propertyName.endsWith('Id') ? propertyName.slice(0, -2) : propertyName);
827
- return easName(key);
828
- }
829
- return easName(propertyName);
830
- }
831
- /**
832
- * The EAS schema definition publish attests a property under (`string title`, `bytes32 author`),
833
- * or undefined when its data type has no EAS type. Look schemas up by this, not by name: a name
834
- * can have schemas of several types (Optimism Sepolia has `bytes32 storage_transaction_id` beside
835
- * `string storage_transaction_id`).
836
- */
837
- function easSchemaDefinitionForProperty(propertyName, propertyDef) {
838
- if (!propertyDef?.dataType)
839
- return undefined;
840
- const dataType = normalizeDataType(String(propertyDef.dataType));
841
- const easType = INTERNAL_DATA_TYPES[dataType]?.eas;
842
- if (!easType)
843
- return undefined;
844
- return `${easType} ${easPropertyNameForPublish(propertyName, propertyDef)}`;
845
- }
846
-
847
- /** Same rules as getCorrectId in helpers/index (avoid barrel circular import). */
848
- function parseLocalIdOrUid(localIdOrUid) {
849
- const id = { localId: undefined, uid: undefined };
850
- if (!localIdOrUid)
851
- return id;
852
- if (localIdOrUid.length === 10) {
853
- id.localId = localIdOrUid;
854
- }
855
- if (localIdOrUid.startsWith('0x') && localIdOrUid.length === 66) {
856
- id.uid = localIdOrUid;
857
- }
858
- return id;
859
- }
860
- /** Relation/image refs may be string ids or `{ seedLocalId?, seedUid? }`. */
861
- function normalizeRelationPropertyValue(value) {
862
- if (value == null)
863
- return undefined;
864
- if (typeof value === 'string') {
865
- const t = value.trim();
866
- return t !== '' ? t : undefined;
867
- }
868
- if (typeof value === 'object' && value !== null) {
869
- const o = value;
870
- if (typeof o.seedLocalId === 'string' && o.seedLocalId.trim() !== '') {
871
- return o.seedLocalId.trim();
872
- }
873
- if (typeof o.seedUid === 'string' && o.seedUid.trim() !== '') {
874
- return o.seedUid.trim();
875
- }
876
- }
877
- return undefined;
878
- }
879
- /** getCorrectId only accepts 10-char local ids and 0x66 uids; local seed refs can be up to 21 chars. */
880
- const LOCAL_SEED_REF = /^[a-zA-Z0-9_-]{10,21}$/;
881
- /** Resolve seed ids from a single string (context propertyValue or DB row). */
882
- function resolveSeedIdsFromRefString(s) {
883
- const fromCorrect = parseLocalIdOrUid(s);
884
- if (fromCorrect.localId || fromCorrect.uid) {
885
- return { seedLocalId: fromCorrect.localId, seedUid: fromCorrect.uid };
886
- }
887
- if (LOCAL_SEED_REF.test(s) && !s.startsWith('0x')) {
888
- return { seedLocalId: s };
889
- }
890
- return {};
891
- }
892
- /**
893
- * True when a relation ref is an attested seed uid (0x…) rather than a local id. A uid only exists once
894
- * the seed is on EAS, so publish can attest the uid as-is when no local copy exists (e.g. a server
895
- * publishing on someone's behalf that only holds the uids it was given). Callers must not walk into it.
896
- */
897
- function isPublishedSeedRef(value) {
898
- const ref = normalizeRelationPropertyValue(value);
899
- if (!ref)
900
- return false;
901
- const { seedLocalId, seedUid } = resolveSeedIdsFromRefString(ref);
902
- return !!seedUid && !seedLocalId;
903
- }
904
- /**
905
- * Storage seed local id (nanoid) or 0x uid. Stricter than resolveSeedIdsFromRefString, which accepts
906
- * any 10-char string (so a 10-char html snippet like "<p>raw</p>" would pass as a local id).
907
- */
908
- const STORAGE_SEED_REF = /^(?:[A-Za-z0-9_-]{10,21}|0x[0-9a-fA-F]{64})$/;
909
- /** True when a File/Image/Html value is already a storage seed reference rather than raw content. */
910
- function isStorageSeedRef(value) {
911
- return typeof value === 'string' && STORAGE_SEED_REF.test(value.trim());
912
- }
913
-
914
- const seeds = sqliteTable('seeds', {
915
- localId: text('local_id').unique(),
916
- uid: text('uid'),
917
- schemaUid: text('schema_uid'),
918
- type: text('type'),
919
- // schemaFileId of the models row this item belongs to (= Model.id). Disambiguates items whose
920
- // model name exists in more than one schema; null for legacy rows that couldn't be backfilled.
921
- modelFileId: text('model_file_id'),
922
- publisher: text('publisher'),
923
- attestationRaw: text('attestation_raw'),
924
- attestationCreatedAt: int('attestation_created_at'),
925
- // Set on insert when the writer leaves it out. No $onUpdate: readers rank rows by
926
- // COALESCE(attestation_created_at, created_at), and some writers move it on purpose.
927
- createdAt: int('created_at').$defaultFn(() => Date.now()),
928
- // Set on every insert and update unless the writer passes a value.
929
- updatedAt: int('updated_at')
930
- .$defaultFn(() => Date.now())
931
- .$onUpdate(() => Date.now()),
932
- _markedForDeletion: int('_marked_for_deletion'),
933
- revokedAt: int('revoked_at'),
934
- }, (table) => [index('seeds_uid_idx').on(table.uid), index('seeds_type_idx').on(table.type)]);
935
-
936
- const versions = sqliteTable('versions', {
937
- localId: text('local_id').unique(),
938
- uid: text('uid'),
939
- seedLocalId: text('seed_local_id'),
940
- seedUid: text('seed_uid'),
941
- seedType: text('seed_type'),
942
- note: text('note'),
943
- // Set on insert when the writer leaves it out. No $onUpdate: readers rank rows by
944
- // COALESCE(attestation_created_at, created_at), and some writers move it on purpose.
945
- createdAt: int('created_at').$defaultFn(() => Date.now()),
946
- // Set on every insert and update unless the writer passes a value.
947
- updatedAt: int('updated_at')
948
- .$defaultFn(() => Date.now())
949
- .$onUpdate(() => Date.now()),
950
- attestationCreatedAt: int('attestation_created_at'),
951
- attestationRaw: text('attestation_raw'),
952
- publisher: text('publisher'),
953
- /**
954
- * When the version attestation was revoked, in Unix seconds (EAS `revocationTime`, or the local
955
- * unpublish time until sync confirms it). Null while live or unpublished.
956
- */
957
- revokedAt: int('revoked_at'),
958
- }, (table) => [index('versions_seed_local_id_idx').on(table.seedLocalId)]);
959
-
960
- const metadata = sqliteTable('metadata', {
961
- localId: text('local_id').unique(),
962
- uid: text('uid'),
963
- propertyId: integer('property_id').references(() => properties.id),
964
- propertyName: text('property_name'),
965
- propertyValue: text('property_value'),
966
- schemaUid: text('schema_uid'),
967
- modelType: text('model_type'),
968
- seedLocalId: text('seed_local_id'),
969
- seedUid: text('seed_uid'),
970
- versionLocalId: text('version_local_id'),
971
- versionUid: text('version_uid'),
972
- easDataType: text('eas_data_type'),
973
- refValueType: text('ref_value_type'),
974
- refModelUid: text('ref_schema_uid'),
975
- refSeedType: text('ref_seed_type'),
976
- refResolvedValue: text('ref_resolved_value'),
977
- refResolvedDisplayValue: text('ref_resolved_display_value'),
978
- localStorageDir: text('local_storage_dir'),
979
- attestationRaw: text('attestation_raw'),
980
- attestationCreatedAt: int('attestation_created_at'),
981
- contentHash: text('content_hash'),
982
- // Set on insert when the writer leaves it out. No $onUpdate: readers rank rows by
983
- // COALESCE(attestation_created_at, created_at), and some writers move it on purpose.
984
- createdAt: int('created_at').$defaultFn(() => Date.now()),
985
- // Set on every insert and update unless the writer passes a value.
986
- updatedAt: int('updated_at')
987
- .$defaultFn(() => Date.now())
988
- .$onUpdate(() => Date.now()),
989
- publisher: text('publisher'),
990
- // Unix seconds when this row's attestation was revoked on EAS; null while it's live (or for a
991
- // local, unpublished row). EAS sync keeps only the canonical attestation per (version, property
992
- // schema): the newest live one, or the newest revoked one when all are revoked. So a non-null
993
- // value marks a property kept only for its last value, not one that is present on the version.
994
- revokedAt: int('revoked_at'),
995
- // For a row EAS sync derived for an ItemStorage property from a `storage_transaction_id`
996
- // attestation (uid null, ref_value_type 'file', value = the transaction id): that attestation's
997
- // uid. Null for every other row. Marks the row as published content, not a local draft.
998
- derivedFromUid: text('derived_from_uid'),
999
- }, (table) => [
1000
- index('metadata_seed_local_id_property_name_idx').on(table.seedLocalId, table.propertyName),
1001
- index('metadata_seed_uid_idx').on(table.seedUid),
1002
- ]);
1003
-
1004
- const appState = sqliteTable('appState', {
1005
- key: text('key').unique(),
1006
- value: text('value'),
1007
- createdAt: int('created_at'),
1008
- updatedAt: int('updated_at'),
1009
- });
1010
-
1011
- const modelUids = sqliteTable('model_uids', {
1012
- id: int('id').primaryKey({ autoIncrement: true }),
1013
- uid: text('uid').notNull(),
1014
- modelId: int('model_id')
1015
- .notNull()
1016
- .unique()
1017
- .references(() => models.id),
1018
- });
1019
- const modelRelations = relations(modelUids, ({ one }) => ({
1020
- model: one(models),
1021
- }));
1022
-
1023
- const propertyUids = sqliteTable('property_uids', {
1024
- id: int('id').primaryKey({ autoIncrement: true }),
1025
- uid: text('uid').notNull(),
1026
- propertyId: int('property_id')
1027
- .notNull()
1028
- .unique()
1029
- .references(() => properties.id),
1030
- });
1031
- const propertyUidRelations = relations(propertyUids, ({ one }) => ({
1032
- property: one(properties),
1033
- }));
1034
-
1035
- const config = sqliteTable('config', {
1036
- id: integer('id').primaryKey({ autoIncrement: true }),
1037
- key: text('key').notNull(),
1038
- text: text('text'),
1039
- json: text('json'),
1040
- blob: blob('blob'),
1041
- });
1042
-
1043
- const schemas = sqliteTable('schemas', {
1044
- id: integer('id').primaryKey({ autoIncrement: true }),
1045
- name: text('name').notNull(),
1046
- version: integer('version').notNull(),
1047
- schemaFileId: text('schema_file_id'), // ID from JSON file for change tracking - must be unique
1048
- schemaData: text('schema_data'), // Full JSON schema content (SchemaFileFormat as JSON string)
1049
- isDraft: integer('is_draft', { mode: 'boolean' }), // true if schema is in draft/editing state, false if published to file
1050
- isEdited: integer('is_edited', { mode: 'boolean' }), // true if schema has been edited locally, false if matches schema file
1051
- createdAt: int('created_at'),
1052
- updatedAt: int('updated_at'),
1053
- }, (table) => {
1054
- return {
1055
- uniqueSchemaFileId: unique('unique_schema_schema_file_id').on(table.schemaFileId),
1056
- };
1057
- });
1058
-
1059
- var SchemaSchema = /*#__PURE__*/Object.freeze({
1060
- __proto__: null,
1061
- schemas: schemas
1062
- });
1063
-
1064
- const modelSchemas = sqliteTable('model_schemas', {
1065
- id: integer('id').primaryKey({ autoIncrement: true }),
1066
- modelId: integer('model_id').references(() => models.id),
1067
- schemaId: integer('schema_id').references(() => schemas.id),
1068
- });
1069
-
1070
- var ModelSchemaSchema = /*#__PURE__*/Object.freeze({
1071
- __proto__: null,
1072
- modelSchemas: modelSchemas
1073
- });
1074
-
1075
- const publishProcesses = sqliteTable('publish_processes', {
1076
- id: int('id').primaryKey({ autoIncrement: true }),
1077
- seedLocalId: text('seed_local_id').notNull(),
1078
- modelName: text('model_name').notNull(),
1079
- schemaId: text('schema_id'),
1080
- status: text('status').notNull(), // 'in_progress' | 'completed' | 'failed' | 'interrupted'
1081
- startedAt: int('started_at').notNull(),
1082
- completedAt: int('completed_at'),
1083
- errorMessage: text('error_message'),
1084
- errorStep: text('error_step'),
1085
- errorDetails: text('error_details'),
1086
- errorCode: text('error_code'), // e.g. ManagedAccountPublishError.code
1087
- persistedSnapshot: text('persisted_snapshot').notNull(),
1088
- seedId: text('seed_id'),
1089
- existingSeedUid: text('existing_seed_uid'),
1090
- createdAt: int('created_at'),
1091
- updatedAt: int('updated_at'),
1092
- });
1093
-
1094
- const uploadProcesses = sqliteTable('upload_processes', {
1095
- id: int('id').primaryKey({ autoIncrement: true }),
1096
- reimbursementConfirmed: int('reimbursement_confirmed').notNull(), // 0 or 1 for boolean
1097
- reimbursementTransactionId: text('reimbursement_transaction_id'),
1098
- transactionKeys: text('transaction_keys'),
1099
- persistedSnapshot: text('persisted_snapshot').notNull(), // JSON string
1100
- createdAt: int('created_at'),
1101
- updatedAt: int('updated_at'),
1102
- });
1103
-
1104
- /** Background job: resolve L1 tx id and wait for L1 confirmation after bundler publish. */
1105
- const arweaveL1FinalizeJobs = sqliteTable('arweave_l1_finalize_jobs', {
1106
- id: int('id').primaryKey({ autoIncrement: true }),
1107
- seedLocalId: text('seed_local_id').notNull(),
1108
- dataItemId: text('data_item_id').notNull(),
1109
- /** L1 bundle / anchor transaction id from gateway GraphQL `bundledIn.id`. */
1110
- l1TransactionId: text('l1_transaction_id'),
1111
- bundleId: text('bundle_id'),
1112
- versionLocalId: text('version_local_id'),
1113
- itemPropertyName: text('item_property_name'),
1114
- /** `pending_l1` | `confirmed` | `failed` */
1115
- phase: text('phase').notNull(),
1116
- statusJson: text('status_json'),
1117
- errorMessage: text('error_message'),
1118
- createdAt: int('created_at').notNull(),
1119
- updatedAt: int('updated_at').notNull(),
1120
- }, (table) => ({
1121
- dataItemIdUnique: uniqueIndex('arweave_l1_finalize_jobs_data_item_id_unique').on(table.dataItemId),
1122
- }));
1123
-
1124
- const easSyncProcesses = sqliteTable('eas_sync_processes', {
1125
- id: int('id').primaryKey({ autoIncrement: true }),
1126
- status: text('status').notNull(), // 'in_progress' | 'completed' | 'failed'
1127
- startedAt: int('started_at').notNull(),
1128
- completedAt: int('completed_at'),
1129
- requestPayload: text('request_payload').notNull(),
1130
- errorMessage: text('error_message'),
1131
- errorDetails: text('error_details'),
1132
- persistedSnapshot: text('persisted_snapshot').notNull(),
1133
- createdAt: int('created_at'),
1134
- updatedAt: int('updated_at'),
1135
- });
1136
-
1137
- /**
1138
- * Links Image seeds materialized from Html `data:image/*` URIs to the parent Item seed for co-publish.
1139
- * Rows are removed after a successful publish (see clearHtmlEmbeddedImageCoPublishRows).
1140
- */
1141
- const htmlEmbeddedImageCoPublish = sqliteTable('html_embedded_image_co_publish', {
1142
- id: int('id').primaryKey({ autoIncrement: true }),
1143
- parentSeedLocalId: text('parent_seed_local_id').notNull(),
1144
- htmlSeedLocalId: text('html_seed_local_id').notNull(),
1145
- imageSeedLocalId: text('image_seed_local_id').notNull(),
1146
- /** Stable dedup key (e.g. sha256 of data URI or normalized URI string). */
1147
- stableKey: text('stable_key').notNull(),
1148
- createdAt: int('created_at').notNull(),
1149
- }, (t) => [
1150
- uniqueIndex('html_embed_co_pub_parent_html_stable').on(t.parentSeedLocalId, t.htmlSeedLocalId, t.stableKey),
1151
- ]);
1152
-
1153
- /**
1154
- * The item write outbox (ADR 0019): one row per edit to an item, appended when the value is
1155
- * assigned and applied to `seeds`, `versions` and `metadata` by the leader tab's drainer.
1156
- */
1157
- const pendingWrites = sqliteTable('pending_writes', {
1158
- id: int('id').primaryKey({ autoIncrement: true }),
1159
- /** Unique per op; the drainer and `save()` refer to an op by it. */
1160
- opId: text('op_id').notNull(),
1161
- /** Unused: nothing appends grouped ops. Kept until a migration drops it. */
1162
- groupId: text('group_id'),
1163
- seedLocalId: text('seed_local_id').notNull(),
1164
- /** Null for ops on the seed itself (`create`, `delete`). */
1165
- propertyName: text('property_name'),
1166
- /** `create` | `set` | `delete` | `storage` */
1167
- kind: text('kind').notNull(),
1168
- /** JSON-encoded value. */
1169
- value: text('value'),
1170
- /** Unused: a storage op's staged path is in its JSON value. Kept until a migration drops it. */
1171
- valueRef: text('value_ref'),
1172
- /** The stored metadata row the edit was made against, to detect a synced row written since. */
1173
- baseRowId: text('base_row_id'),
1174
- /** Unused: the base uid is read through base_row_id. Kept until a migration drops it. */
1175
- baseUid: text('base_uid'),
1176
- createdAt: int('created_at').notNull(),
1177
- /** The tab that appended the op. */
1178
- tabId: text('tab_id'),
1179
- /** `pending` | `applied` | `superseded` | `failed`. An op is claimed and applied in one transaction. */
1180
- status: text('status').notNull(),
1181
- error: text('error'),
1182
- /** The name of the error that failed the op (`OwnershipError`, say), so a waiter can tell why. */
1183
- errorName: text('error_name'),
1184
- attempts: int('attempts').notNull().default(0),
1185
- appliedAt: int('applied_at'),
1186
- /** JSON the applier recorded when the op applied (for a `set`: the metadata row written). */
1187
- result: text('result'),
1188
- /** JSON: the synced value this op was applied over, when there was one (ADR 0019). */
1189
- conflict: text('conflict'),
1190
- conflictResolvedAt: int('conflict_resolved_at'),
1191
- }, (table) => ({
1192
- opIdUnique: uniqueIndex('pending_writes_op_id_unique').on(table.opId),
1193
- /** The drainer's order: per property, oldest first, among one status. */
1194
- drainOrder: index('pending_writes_drain_order').on(table.status, table.seedLocalId, table.propertyName, table.createdAt),
1195
- }));
1196
-
1197
- /**
1198
- * The entity change log (ADR 0020): one row per row written to `seeds`, `versions` or `metadata`,
1199
- * inserted by triggers (migration 0023), so it commits or rolls back with the write. The browser's
1200
- * change feed reads it to tell loaded Items and ItemProperties which seeds changed. Nothing else
1201
- * writes it; the leader tab prunes rows older than 10 minutes.
1202
- */
1203
- const entityChanges = sqliteTable('entity_changes', {
1204
- seq: int('seq').primaryKey({ autoIncrement: true }),
1205
- /** `seeds` | `versions` | `metadata` */
1206
- tbl: text('tbl').notNull(),
1207
- seedLocalId: text('seed_local_id'),
1208
- seedUid: text('seed_uid'),
1209
- /** Set for `metadata` rows only. */
1210
- propertyName: text('property_name'),
1211
- /** Unix ms when the trigger ran. */
1212
- at: int('at').notNull(),
1213
- }, (table) => ({
1214
- at: index('entity_changes_at').on(table.at),
1215
- }));
1216
-
1217
- const seedUidToStorageTransactionId = new Map();
1218
- const getStorageTransactionIdForSeedUid = async (seedUid) => {
1219
- if (seedUidToStorageTransactionId.has(seedUid)) {
1220
- return seedUidToStorageTransactionId.get(seedUid);
1221
- }
1222
- const appDb = BaseDb.getAppDb();
1223
- const results = (await appDb
1224
- .select({
1225
- storageTransactionId: metadata.propertyValue,
1226
- })
1227
- .from(metadata)
1228
- .where(and(eq(metadata.seedUid, seedUid), eq(metadata.propertyName, 'storageTransactionId'))));
1229
- if (!results || results.length === 0) {
1230
- return;
1231
- }
1232
- seedUidToStorageTransactionId.set(seedUid, results[0].storageTransactionId);
1233
- return results[0].storageTransactionId;
1234
- };
1235
-
1236
- const logger$4 = debug('seedSdk:helpers:mediaRef');
1237
- /** Typical Arweave transaction id: 43 URL-safe base64 characters. */
1238
- const ARWEAVE_TX_ID = /^[a-zA-Z0-9_-]{43}$/;
1239
- function trimOrEmpty(raw) {
1240
- if (raw == null)
1241
- return '';
1242
- return typeof raw === 'string' ? raw.trim() : String(raw).trim();
1243
- }
1244
- function coalesceStringField(item, key) {
1245
- const v = item[key];
1246
- if (v == null)
1247
- return '';
1248
- if (typeof v === 'string')
1249
- return v.trim();
1250
- if (typeof v === 'number' || typeof v === 'boolean')
1251
- return String(v);
1252
- return '';
1253
- }
1254
- /**
1255
- * Try JSON relation payloads and plain relation id strings.
1256
- */
1257
- function unwrapRelationString(raw) {
1258
- const t = raw.trim();
1259
- if (!t)
1260
- return t;
1261
- try {
1262
- const parsed = JSON.parse(t);
1263
- const fromObj = normalizeRelationPropertyValue(parsed);
1264
- if (fromObj)
1265
- return fromObj;
1266
- }
1267
- catch {
1268
- // not JSON
1269
- }
1270
- return t;
1271
- }
1272
- /**
1273
- * Pure, sync classification of a media-related string from feeds or XML.
1274
- */
1275
- function classifyMediaRef(raw, options) {
1276
- const s = trimOrEmpty(raw);
1277
- if (!s) {
1278
- return { kind: 'empty' };
1279
- }
1280
- const treatAs = options?.treatAs;
1281
- if (treatAs === 'url') {
1282
- return { kind: 'url', href: s };
1283
- }
1284
- if (treatAs === 'arweaveTx') {
1285
- return { kind: 'arweaveTxId', txId: s };
1286
- }
1287
- if (treatAs === 'seedUid') {
1288
- if (s.startsWith('0x') && s.length === 66) {
1289
- return { kind: 'seedUid', uid: s };
1290
- }
1291
- return { kind: 'unknown', raw: s };
1292
- }
1293
- if (s.startsWith('http://') || s.startsWith('https://') || s.startsWith('blob:') || s.startsWith('data:')) {
1294
- if (isEasAttestationExplorerUrl(s)) {
1295
- return { kind: 'nonMediaUrl', href: s };
1296
- }
1297
- return { kind: 'url', href: s };
1298
- }
1299
- const unwrapped = unwrapRelationString(s);
1300
- const ids = resolveSeedIdsFromRefString(unwrapped);
1301
- if (ids.seedUid) {
1302
- return { kind: 'seedUid', uid: ids.seedUid };
1303
- }
1304
- if (ids.seedLocalId) {
1305
- return { kind: 'seedLocalId', localId: ids.seedLocalId };
1306
- }
1307
- if (unwrapped.startsWith('0x') && unwrapped.length === 66) {
1308
- return { kind: 'seedUid', uid: unwrapped };
1309
- }
1310
- if (ARWEAVE_TX_ID.test(unwrapped) && !unwrapped.startsWith('0x')) {
1311
- return { kind: 'arweaveTxId', txId: unwrapped };
1312
- }
1313
- return { kind: 'unknown', raw: s };
1314
- }
1315
- /**
1316
- * Same as convertTxIdToImage but local to this module to avoid circular imports with helpers/index.
1317
- */
1318
- async function tryLocalBlobUrlFromTxId(txId) {
1319
- const imageFilePath = BaseFileManager.getFilesPath('images', txId);
1320
- const fileExists = await BaseFileManager.pathExists(imageFilePath);
1321
- if (!fileExists) {
1322
- logger$4(`[tryLocalBlobUrlFromTxId] ${imageFilePath} does not exist`);
1323
- return undefined;
1324
- }
1325
- const buffer = await BaseFileManager.readFileAsBuffer(imageFilePath);
1326
- let arrayBuffer;
1327
- if (buffer instanceof Blob) {
1328
- arrayBuffer = await buffer.arrayBuffer();
1329
- }
1330
- else if (buffer instanceof ArrayBuffer) {
1331
- arrayBuffer = buffer;
1332
- }
1333
- else {
1334
- const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
1335
- if (sliced instanceof SharedArrayBuffer) {
1336
- const uint8Array = new Uint8Array(sliced);
1337
- arrayBuffer = uint8Array.buffer.slice(0);
1338
- }
1339
- else {
1340
- arrayBuffer = sliced;
1341
- }
1342
- }
1343
- const uint = new Uint8Array(arrayBuffer);
1344
- const imageBlob = new Blob([uint]);
1345
- return URL.createObjectURL(imageBlob);
1346
- }
1347
- async function resolveTxId(txId) {
1348
- const local = await tryLocalBlobUrlFromTxId(txId);
1349
- if (local) {
1350
- return { status: 'ready', href: local, source: 'localBlob' };
1351
- }
1352
- return {
1353
- status: 'ready',
1354
- href: BaseArweaveClient.getRawUrl(txId),
1355
- source: 'gateway',
1356
- };
1357
- }
1358
- /**
1359
- * Resolve a classified media reference to a display URL (https/blob/data), using local files when present.
1360
- */
1361
- async function resolveMediaRef(raw, options) {
1362
- const classification = classifyMediaRef(raw, options);
1363
- switch (classification.kind) {
1364
- case 'empty':
1365
- return { status: 'empty' };
1366
- case 'url':
1367
- return {
1368
- status: 'ready',
1369
- href: classification.href,
1370
- source: 'direct',
1371
- };
1372
- case 'nonMediaUrl':
1373
- return {
1374
- status: 'unresolved',
1375
- reason: 'non_media_url',
1376
- classification,
1377
- };
1378
- case 'seedLocalId':
1379
- return {
1380
- status: 'unresolved',
1381
- reason: 'seed_local_id_not_portable',
1382
- classification,
1383
- };
1384
- case 'unknown':
1385
- return {
1386
- status: 'unresolved',
1387
- reason: 'unknown_ref',
1388
- classification,
1389
- };
1390
- case 'arweaveTxId':
1391
- return resolveTxId(classification.txId);
1392
- case 'seedUid': {
1393
- const appDb = BaseDb.getAppDb();
1394
- if (!appDb) {
1395
- return {
1396
- status: 'unresolved',
1397
- reason: 'seed_uid_requires_app_db',
1398
- classification,
1399
- };
1400
- }
1401
- const storageTransactionId = await getStorageTransactionIdForSeedUid(classification.uid);
1402
- if (!storageTransactionId) {
1403
- return {
1404
- status: 'unresolved',
1405
- reason: 'seed_uid_no_storage_transaction',
1406
- classification,
1407
- };
1408
- }
1409
- return resolveTxId(storageTransactionId);
1410
- }
1411
- default:
1412
- return {
1413
- status: 'unresolved',
1414
- reason: 'unknown_ref',
1415
- classification,
1416
- };
1417
- }
1418
- }
1419
- /**
1420
- * Read a string field from a parsed item, trying camelCase and snake_case keys.
1421
- */
1422
- function getFeedItemStringField(item, key) {
1423
- const direct = coalesceStringField(item, key);
1424
- if (direct)
1425
- return direct;
1426
- const snake = key.replace(/[A-Z]/g, (l) => `_${l.toLowerCase()}`);
1427
- if (snake !== key) {
1428
- const alt = coalesceStringField(item, snake);
1429
- if (alt)
1430
- return alt;
1431
- }
1432
- const parts = key.split('_');
1433
- if (parts.length > 1) {
1434
- const camel = parts[0] + parts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
1435
- const alt2 = coalesceStringField(item, camel);
1436
- if (alt2)
1437
- return alt2;
1438
- }
1439
- return '';
1440
- }
1441
- /**
1442
- * Apply a field manifest to a plain feed item: classify media/file fields; pass through html/text.
1443
- */
1444
- function normalizeFeedItemFields(item, manifest) {
1445
- const out = {};
1446
- for (const [fieldKey, descriptor] of Object.entries(manifest)) {
1447
- const raw = getFeedItemStringField(item, fieldKey);
1448
- if (!raw) {
1449
- out[fieldKey] = undefined;
1450
- continue;
1451
- }
1452
- if (descriptor.role === 'html') {
1453
- out[fieldKey] = { role: 'html', raw };
1454
- continue;
1455
- }
1456
- if (descriptor.role === 'text') {
1457
- out[fieldKey] = { role: 'text', raw };
1458
- continue;
1459
- }
1460
- const classification = classifyMediaRef(raw, { treatAs: descriptor.treatAs });
1461
- out[fieldKey] = {
1462
- role: descriptor.role,
1463
- raw,
1464
- classification,
1465
- };
1466
- }
1467
- return out;
1468
- }
1469
-
1470
- let refresher;
1471
- function setCachedPropertyRefresher(fn) {
1472
- refresher = fn;
1473
- }
1474
- /** Give cached ModelProperty instances of these properties their rows' current values. */
1475
- async function refreshCachedProperties(propertyFileIds) {
1476
- if (!refresher || propertyFileIds.length === 0)
1477
- return;
1478
- await refresher(propertyFileIds);
1479
- }
1480
-
1481
- /**
1482
- * Detect SQLite UNIQUE constraint failures across better-sqlite3 and drizzle/wasm wrappers.
1483
- * Drizzle often surfaces `Failed query: insert into...` without "UNIQUE constraint" on message,
1484
- * with the real SQLITE_CONSTRAINT_UNIQUE on cause / nested error.
1485
- */
1486
- function isSqliteUniqueConstraintError(error) {
1487
- const seen = new Set();
1488
- const queue = [error];
1489
- while (queue.length > 0) {
1490
- const current = queue.shift();
1491
- if (current == null || seen.has(current))
1492
- continue;
1493
- seen.add(current);
1494
- if (typeof current === 'string') {
1495
- if (matchesUniqueConstraintText(current))
1496
- return true;
1497
- continue;
1498
- }
1499
- if (typeof current !== 'object')
1500
- continue;
1501
- const err = current;
1502
- const code = err.code;
1503
- if (code === 'SQLITE_CONSTRAINT_UNIQUE' ||
1504
- code === 2067 ||
1505
- code === '2067') {
1506
- return true;
1507
- }
1508
- for (const key of ['message', 'msg', 'stack']) {
1509
- const raw = err[key];
1510
- const value = key === 'stack' && typeof raw === 'string' ? stackWithoutFrames(raw) : raw;
1511
- if (typeof value === 'string' && matchesUniqueConstraintText(value)) {
1512
- return true;
1513
- }
1514
- }
1515
- // Drizzle / wasm often nest the real SQLite error
1516
- for (const key of ['cause', 'reason', 'error', 'originalError', 'err']) {
1517
- if (err[key] != null)
1518
- queue.push(err[key]);
1519
- }
1520
- }
1521
- return false;
1522
- }
1523
- function matchesUniqueConstraintText(text) {
1524
- const lower = text.toLowerCase();
1525
- return (lower.includes('unique constraint') ||
1526
- lower.includes('sqlite_constraint_unique') ||
1527
- (lower.includes('constraint failed') && lower.includes('unique')) ||
1528
- /\brc\s*=\s*2067\b/i.test(text) ||
1529
- (text.includes('2067') && lower.includes('constraint')));
1530
- }
1531
- /**
1532
- * A stack's message lines without its frames. Frames hold file names and line:column numbers, so
1533
- * a frame in a file named like this one, with a 2067 in its position or URL timestamp, would match.
1534
- */
1535
- function stackWithoutFrames(stack) {
1536
- return stack
1537
- .split('\n')
1538
- .filter((line) => !/^\s*at\s/.test(line) && !/@\S*:\d+:\d+\s*$/.test(line))
1539
- .join('\n');
1540
- }
1541
-
1542
- const level = (value) => (isNameMatchLevel(value) ? value : undefined);
1543
- const tighter = (a, b) => a === undefined || compareNameMatchTiers(b, a) < 0 ? b : a;
1544
- /** The levels in one schema file for one of its models, or undefined when the file lacks the model. */
1545
- const modelLevelsFromFile = (file, modelName) => {
1546
- const modelDef = file.models?.[modelName];
1547
- if (!modelDef)
1548
- return undefined;
1549
- const model = level(modelDef.nameMatch) ?? level(file.nameMatch) ?? DEFAULT_NAME_MATCH_LEVEL;
1550
- const properties = new Map();
1551
- for (const [propertyName, def] of Object.entries(modelDef.properties ?? {})) {
1552
- properties.set(propertyName, level(def?.nameMatch) ?? model);
1553
- }
1554
- return { model, properties };
1555
- };
1556
- /**
1557
- * Builds NameMatchLevels from the schema files that own each model row. Exported for tests;
1558
- * use {@link nameMatchLevels}.
1559
- */
1560
- const nameMatchLevelsFromRows = (rows) => {
1561
- const byFileId = new Map();
1562
- const byName = new Map();
1563
- const parsed = new Map();
1564
- for (const row of rows) {
1565
- if (!row.schemaData)
1566
- continue;
1567
- if (!parsed.has(row.schemaData)) {
1568
- try {
1569
- parsed.set(row.schemaData, JSON.parse(row.schemaData));
1570
- }
1571
- catch {
1572
- parsed.set(row.schemaData, undefined);
1573
- }
1574
- }
1575
- const file = parsed.get(row.schemaData);
1576
- const levels = file && modelLevelsFromFile(file, row.modelName);
1577
- if (!levels)
1578
- continue;
1579
- if (row.modelFileId)
1580
- byFileId.set(row.modelFileId, levels);
1581
- const seen = byName.get(row.modelName);
1582
- if (!seen) {
1583
- byName.set(row.modelName, { model: levels.model, properties: new Map(levels.properties) });
1584
- continue;
1585
- }
1586
- seen.model = tighter(seen.model, levels.model);
1587
- for (const [name, value] of levels.properties)
1588
- seen.properties.set(name, tighter(seen.properties.get(name), value));
1589
- }
1590
- return {
1591
- model: (modelName) => byName.get(modelName)?.model ?? DEFAULT_NAME_MATCH_LEVEL,
1592
- property: (modelName, propertyName, modelFileId) => {
1593
- const levels = (modelFileId ? byFileId.get(modelFileId) : undefined) ?? byName.get(modelName);
1594
- return levels?.properties.get(propertyName) ?? levels?.model ?? DEFAULT_NAME_MATCH_LEVEL;
1595
- },
1596
- };
1597
- };
1598
- const DEFAULT_LEVELS = {
1599
- model: () => DEFAULT_NAME_MATCH_LEVEL,
1600
- property: () => DEFAULT_NAME_MATCH_LEVEL,
1601
- };
1602
- let current;
1603
- let loading;
1604
- /** Reads the levels from the schema files stored with each model's schema row (schemas.schema_data). */
1605
- const loadNameMatchLevels = async (db = BaseDb.getAppDb()) => {
1606
- if (!db)
1607
- return DEFAULT_LEVELS;
1608
- const load = (async () => {
1609
- const rows = (await db
1610
- .select({ modelName: models.name, modelFileId: models.schemaFileId, schemaData: schemas.schemaData })
1611
- .from(models)
1612
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
1613
- .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id)));
1614
- return nameMatchLevelsFromRows(rows);
1615
- })();
1616
- loading = load;
1617
- try {
1618
- const levels = await load;
1619
- if (loading === load) {
1620
- current = levels;
1621
- previous = undefined;
1622
- }
1623
- return levels;
1624
- }
1625
- finally {
1626
- if (loading === load)
1627
- loading = undefined;
1628
- }
1629
- };
1630
- /** The levels, loaded once and reused until a schema write clears them. */
1631
- const nameMatchLevels = async (db = BaseDb.getAppDb()) => current ?? loading ?? loadNameMatchLevels(db);
1632
- /**
1633
- * The last loaded levels, for synchronous callers (Model.findByModelType). Init loads them before
1634
- * it reports ready. After a schema write clears them, this returns the levels from before the
1635
- * write until the reload that clear starts finishes.
1636
- */
1637
- const currentNameMatchLevels = () => current ?? previous ?? DEFAULT_LEVELS;
1638
- let previous;
1639
- let reloadScheduled = false;
1640
- /**
1641
- * Called when a schema row or its models are written. The next {@link nameMatchLevels} read
1642
- * reloads, and a reload starts on the next tick (one per burst of writes) for synchronous callers.
1643
- */
1644
- const clearNameMatchLevels = () => {
1645
- if (current)
1646
- previous = current;
1647
- current = undefined;
1648
- loading = undefined;
1649
- if (reloadScheduled)
1650
- return;
1651
- reloadScheduled = true;
1652
- setTimeout(() => {
1653
- reloadScheduled = false;
1654
- if (current || loading || !BaseDb.isAppDbReady())
1655
- return;
1656
- loadNameMatchLevels().catch(() => {
1657
- // The next nameMatchLevels() read retries.
1658
- });
1659
- }, 0);
1660
- };
1661
- /**
1662
- * `nameMatch` values in a schema file that aren't a level. They are ignored (the next level up
1663
- * applies); schema loads warn about them.
1664
- */
1665
- const invalidNameMatchValues = (file) => {
1666
- const problems = [];
1667
- const check = (where, value) => {
1668
- if (value !== undefined && !isNameMatchLevel(value)) {
1669
- problems.push(`${where} nameMatch ${JSON.stringify(value)} is not 'exact', 'normalized' or 'fuzzy'`);
1670
- }
1671
- };
1672
- check('schema', file.nameMatch);
1673
- for (const [modelName, modelDef] of Object.entries(file.models ?? {})) {
1674
- check(modelName, modelDef?.nameMatch);
1675
- for (const [propertyName, def] of Object.entries(modelDef?.properties ?? {})) {
1676
- check(`${modelName}.${propertyName}`, def?.nameMatch);
1677
- }
1678
- }
1679
- return problems;
1680
- };
1681
- /** Warns about {@link invalidNameMatchValues}. */
1682
- const warnOnInvalidNameMatch = (file) => {
1683
- const problems = invalidNameMatchValues(file);
1684
- for (const problem of problems) {
1685
- console.warn(`[seed] schema ${file.metadata?.name ?? ''}: ${problem}; it is ignored.`);
1686
- }
1687
- return problems;
1688
- };
1689
-
1690
- /**
1691
- * Thrown when a model name exists in more than one schema and nothing says which one is meant.
1692
- * Model names are only unique within a schema; pass `schemaName` (or `modelFileId`) to choose.
1693
- */
1694
- class AmbiguousModelError extends Error {
1695
- constructor(modelName, schemaNames) {
1696
- const names = [...new Set(schemaNames)].sort();
1697
- super(`Model "${modelName}" exists in schemas ${names.map((n) => `"${n}"`).join(', ')}; ` +
1698
- `pass schemaName (or modelFileId) to choose one.`);
1699
- this.name = 'AmbiguousModelError';
1700
- this.modelName = modelName;
1701
- this.schemaNames = names;
1702
- }
1703
- }
1704
-
1705
- const columns = {
1706
- id: models.id,
1707
- name: models.name,
1708
- schemaFileId: models.schemaFileId,
1709
- };
1710
- /**
1711
- * Resolve a models row from a model name plus whatever scope is known, most specific first:
1712
- * modelFileId, modelId, then schemaId/schemaName + name, then the name alone. The name alone is
1713
- * only accepted when it is unambiguous; otherwise this throws AmbiguousModelError.
1714
- */
1715
- const resolveModelRecord = async (modelName, scope = {}, db = BaseDb.getAppDb()) => {
1716
- if (!db)
1717
- return undefined;
1718
- if (scope.modelFileId) {
1719
- const rows = await db
1720
- .select(columns)
1721
- .from(models)
1722
- .where(eq(models.schemaFileId, scope.modelFileId))
1723
- .limit(1);
1724
- if (rows.length > 0)
1725
- return rows[0];
1726
- }
1727
- if (scope.modelId) {
1728
- const rows = await db.select(columns).from(models).where(eq(models.id, scope.modelId)).limit(1);
1729
- if (rows.length > 0)
1730
- return rows[0];
1731
- }
1732
- if (!modelName)
1733
- return undefined;
1734
- if (scope.schemaId || scope.schemaName) {
1735
- const rows = await db
1736
- .select(columns)
1737
- .from(models)
1738
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
1739
- .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
1740
- .where(and(eq(models.name, modelName), scope.schemaId ? eq(schemas.id, scope.schemaId) : eq(schemas.name, scope.schemaName)))
1741
- .limit(1);
1742
- if (rows.length > 0)
1743
- return rows[0];
1744
- }
1745
- return resolveModelRecordByNameOnly(modelName, db);
1746
- };
1747
- /**
1748
- * Name-only resolution. Rows linked to a schema win over unlinked stubs (null-schemaFileId rows
1749
- * from ref resolution); if more than one linked row has the name it is ambiguous and this throws.
1750
- * `label` names the lookup in the error (defaults to the model name).
1751
- */
1752
- const resolveModelRecordByNameOnly = async (modelName, db, label = Array.isArray(modelName) ? modelName.join(', ') : modelName) => {
1753
- const rows = (await db
1754
- .select({ ...columns, schemaName: schemas.name })
1755
- .from(models)
1756
- .leftJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
1757
- .leftJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
1758
- .where(Array.isArray(modelName) ? inArray(models.name, modelName) : eq(models.name, modelName))
1759
- .orderBy(desc(models.id)));
1760
- if (rows.length === 0)
1761
- return undefined;
1762
- const linked = rows.filter((r) => r.schemaName);
1763
- const linkedIds = new Set(linked.map((r) => r.id));
1764
- if (linkedIds.size > 1) {
1765
- throw new AmbiguousModelError(label, linked.map((r) => r.schemaName));
1766
- }
1767
- const chosen = linked[0] ?? rows[0];
1768
- return { id: chosen.id, name: chosen.name, schemaFileId: chosen.schemaFileId };
1769
- };
1770
- /**
1771
- * The model names a model name or EAS model type can refer to: the name itself, else the names
1772
- * that match it in the tightest tier (ADR 0008, as Model.findByModelType matches): `new_model` →
1773
- * "New model", `sync_storage_post` → "SyncStoragePost", `htmlpage` → "HTMLPage". Each name at its
1774
- * nameMatch level (ADR 0009).
1775
- */
1776
- const modelNamesForNameOrType = async (nameOrType, db) => {
1777
- const rows = (await db.selectDistinct({ name: models.name }).from(models));
1778
- const names = rows.map((r) => r.name);
1779
- if (names.includes(nameOrType))
1780
- return [nameOrType];
1781
- const levels = await nameMatchLevels(db);
1782
- return [...localNamesInBestMatchTier(nameOrType, names, { level: (name) => levels.model(name) })];
1783
- };
1784
- /**
1785
- * resolveModelRecord for a model name **or** an EAS model type (snake_case model name, what sync
1786
- * stores in seeds.type): "New model" resolves from `new_model`. Scope and ambiguity behave as in
1787
- * resolveModelRecord: modelFileId/modelId first, then the schema, then the name(s) alone, which throws
1788
- * AmbiguousModelError when models in several schemas match.
1789
- */
1790
- const resolveModelRecordByNameOrType = async (nameOrType, scope = {}, db = BaseDb.getAppDb()) => {
1791
- if (!db)
1792
- return undefined;
1793
- if (scope.modelFileId || scope.modelId) {
1794
- const byId = await resolveModelRecord(undefined, scope, db);
1795
- if (byId)
1796
- return byId;
1797
- }
1798
- if (!nameOrType)
1799
- return undefined;
1800
- const names = await modelNamesForNameOrType(nameOrType, db);
1801
- if (names.length === 0)
1802
- return undefined;
1803
- if (names.length === 1)
1804
- return resolveModelRecord(names[0], { ...scope, modelFileId: undefined, modelId: undefined }, db);
1805
- // Several model names map to this type (e.g. "New model" and "NewModel" in different schemas).
1806
- if (scope.schemaId || scope.schemaName) {
1807
- const rows = (await db
1808
- .select(columns)
1809
- .from(models)
1810
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
1811
- .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
1812
- .where(and(inArray(models.name, names), scope.schemaId ? eq(schemas.id, scope.schemaId) : eq(schemas.name, scope.schemaName))));
1813
- if (new Set(rows.map((r) => r.id)).size === 1)
1814
- return rows[0];
1815
- }
1816
- return resolveModelRecordByNameOnly(names, db, nameOrType);
1817
- };
1818
- /**
1819
- * Work out which model (schemaFileId) an item belongs to from whatever is at hand: an explicit
1820
- * modelFileId, the seed's model_file_id, or a metadata row's property_id → properties.model_id.
1821
- */
1822
- const resolveItemModelFileId = async (hints, db = BaseDb.getAppDb()) => {
1823
- if (hints.modelFileId)
1824
- return hints.modelFileId;
1825
- if (!db)
1826
- return undefined;
1827
- if (hints.seedLocalId || hints.seedUid) {
1828
- const rows = await db
1829
- .select({ modelFileId: seeds.modelFileId })
1830
- .from(seeds)
1831
- .where(hints.seedLocalId ? eq(seeds.localId, hints.seedLocalId) : eq(seeds.uid, hints.seedUid))
1832
- .limit(1);
1833
- if (rows[0]?.modelFileId)
1834
- return rows[0].modelFileId;
1835
- }
1836
- if (hints.propertyId) {
1837
- const rows = await db
1838
- .select({ modelFileId: models.schemaFileId })
1839
- .from(properties)
1840
- .innerJoin(models, eq(properties.modelId, models.id))
1841
- .where(eq(properties.id, hints.propertyId))
1842
- .limit(1);
1843
- if (rows[0]?.modelFileId)
1844
- return rows[0].modelFileId;
1845
- }
1846
- return undefined;
1847
- };
1848
- /** ModelScope for an Item (or ItemProperty) instance: its recorded modelFileId (context or seed row) and schemaName. */
1849
- const getItemModelScope = async (item) => {
1850
- let context;
1851
- try {
1852
- context = item.getService?.().getSnapshot().context;
1853
- }
1854
- catch {
1855
- context = undefined;
1856
- }
1857
- const modelFileId = await resolveItemModelFileId({
1858
- modelFileId: context?.modelFileId,
1859
- seedLocalId: item.seedLocalId ?? context?.seedLocalId,
1860
- seedUid: item.seedUid ?? context?.seedUid,
1861
- propertyId: context?.propertyId,
1862
- });
1863
- return { modelFileId, schemaName: context?.schemaName };
1864
- };
1865
- /**
1866
- * Resolve a ref (e.g. a Relation's target model) by name from the schema(s) its owning model belongs
1867
- * to, so "Tag" means the owner schema's Tag rather than any Tag. Falls back to resolveModelRecord.
1868
- */
1869
- const resolveRefModelRecord = async (refModelName, owner, db = BaseDb.getAppDb()) => {
1870
- if (!db || !refModelName)
1871
- return undefined;
1872
- const ownerRow = owner.modelFileId || owner.modelId ? await resolveModelRecord(undefined, owner, db) : undefined;
1873
- const schemaFilter = ownerRow
1874
- ? inArray(modelSchemas.schemaId,
1875
- // any schema the owner is linked to
1876
- db.select({ schemaId: modelSchemas.schemaId }).from(modelSchemas).where(eq(modelSchemas.modelId, ownerRow.id)))
1877
- : owner.schemaId
1878
- ? eq(modelSchemas.schemaId, owner.schemaId)
1879
- : owner.schemaName
1880
- ? eq(schemas.name, owner.schemaName)
1881
- : undefined;
1882
- if (schemaFilter) {
1883
- const rows = await db
1884
- .select(columns)
1885
- .from(models)
1886
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
1887
- .innerJoin(schemas, eq(modelSchemas.schemaId, schemas.id))
1888
- .where(and(eq(models.name, refModelName), schemaFilter))
1889
- .limit(1);
1890
- if (rows.length > 0)
1891
- return rows[0];
1892
- }
1893
- return resolveModelRecord(refModelName, {}, db);
1894
- };
1895
-
1896
- var resolveModelRecord$1 = /*#__PURE__*/Object.freeze({
1897
- __proto__: null,
1898
- getItemModelScope: getItemModelScope,
1899
- resolveItemModelFileId: resolveItemModelFileId,
1900
- resolveModelRecord: resolveModelRecord,
1901
- resolveModelRecordByNameOrType: resolveModelRecordByNameOrType,
1902
- resolveRefModelRecord: resolveRefModelRecord
1903
- });
1904
-
1905
- /**
1906
- * Lowercase + trim an address for persist and compare.
1907
- * Does not checksum or validate — test and placeholder addresses must keep working.
1908
- */
1909
- function normalizeHexAddress(addr) {
1910
- return addr.trim().toLowerCase();
1911
- }
1912
- /**
1913
- * Lowercase, drop empties, and dedupe (first-seen order).
1914
- */
1915
- function normalizeAddressList(addrs) {
1916
- if (!addrs?.length)
1917
- return [];
1918
- const seen = new Set();
1919
- const out = [];
1920
- for (const addr of addrs) {
1921
- if (addr == null || addr === '')
1922
- continue;
1923
- const normalized = normalizeHexAddress(String(addr));
1924
- if (!normalized || seen.has(normalized))
1925
- continue;
1926
- seen.add(normalized);
1927
- out.push(normalized);
1928
- }
1929
- return out;
1930
- }
1931
- /**
1932
- * Lowercase a publisher stamp. Empty / null → undefined (omit on write).
1933
- */
1934
- function normalizePublisher(publisher) {
1935
- if (publisher == null || publisher === '')
1936
- return undefined;
1937
- const normalized = normalizeHexAddress(publisher);
1938
- return normalized || undefined;
1939
- }
1940
- /**
1941
- * Normalizes AddressConfiguration to { owned, watched }.
1942
- * - string[] -> { owned: addresses, watched: [] }
1943
- * - { owned, watched? } -> { owned, watched: watched ?? [] }
1944
- * Addresses are lowercased and deduped.
1945
- */
1946
- function normalizeAddressConfig(addresses) {
1947
- if (!addresses) {
1948
- return { owned: [], watched: [] };
1949
- }
1950
- if (Array.isArray(addresses)) {
1951
- return { owned: normalizeAddressList(addresses), watched: [] };
1952
- }
1953
- return {
1954
- owned: normalizeAddressList(addresses.owned),
1955
- watched: normalizeAddressList(addresses.watched),
1956
- };
1957
- }
1958
-
1959
- const pendingLinks = new Map();
1960
- /**
1961
- * Insert the `model_schemas` row linking a model to a schema, unless it already exists.
1962
- *
1963
- * `model_schemas` has no unique constraint, and several writers link the same model during one
1964
- * import (importJsonSchema's addModelsToDb and the write service's writeModelToDb). Each used to
1965
- * check-then-insert on its own, so concurrent writers inserted the link twice. The check and insert
1966
- * run serialized per (model, schema) here.
1967
- *
1968
- * @returns true when this call inserted the link
1969
- */
1970
- function linkModelToSchema(db, modelId, schemaId) {
1971
- const key = `${modelId}:${schemaId}`;
1972
- const previous = pendingLinks.get(key) ?? Promise.resolve(false);
1973
- const current = previous
1974
- .catch(() => false)
1975
- .then(async () => {
1976
- const existing = await db
1977
- .select({ id: modelSchemas.id })
1978
- .from(modelSchemas)
1979
- .where(and(eq(modelSchemas.modelId, modelId), eq(modelSchemas.schemaId, schemaId)))
1980
- .limit(1);
1981
- if (existing.length > 0)
1982
- return false;
1983
- await db.insert(modelSchemas).values({ modelId, schemaId });
1984
- return true;
1985
- });
1986
- pendingLinks.set(key, current);
1987
- const cleanup = () => {
1988
- if (pendingLinks.get(key) === current)
1989
- pendingLinks.delete(key);
1990
- };
1991
- current.then(cleanup, cleanup);
1992
- return current;
1993
- }
1994
-
1995
- const logger$3 = debug('seedSdk:helpers:db');
1996
- /**
1997
- * Posts one message and closes the channel. A message already posted is still delivered after its
1998
- * sender closes, and an open BroadcastChannel keeps a Node process alive (each was a MessagePort
1999
- * handle that stopped scripts exiting).
2000
- */
2001
- const postBroadcast = (channelName, message) => {
2002
- const channel = new BroadcastChannel(channelName);
2003
- try {
2004
- channel.postMessage(message);
2005
- }
2006
- finally {
2007
- channel.close();
2008
- }
2009
- };
2010
- /**
2011
- * The `properties` storage columns for a property definition, in either the internal shape
2012
- * (`storageType`, `localStorageDir`, `filenameSuffix`) or the schema-file shape
2013
- * (`storage: { type, path, extension }`). All null when the definition has no storage config.
2014
- */
2015
- function propertyStorageColumns(def) {
2016
- const normalized = normalizePropertyRecordSchema((def ?? {}));
2017
- return {
2018
- storageType: normalized.storageType ?? null,
2019
- localStorageDir: normalized.localStorageDir ?? null,
2020
- filenameSuffix: normalized.filenameSuffix ?? null,
2021
- };
2022
- }
2023
- /** True when the definition carries any storage setting (so writing its columns can't wipe them by omission). */
2024
- const hasStorageSettings = (columns) => columns.storageType != null || columns.localStorageDir != null || columns.filenameSuffix != null;
2025
- /**
2026
- * Resolve property_id (integer properties.id) from a property's schemaFileId (the string `id` in
2027
- * schema files and propertyRecordSchema). Scoped to the model since schemaFileIds are per schema file.
2028
- * Reads `db` (a transaction's `tx`, say), the app database by default.
2029
- * @returns properties.id or null if not found
2030
- */
2031
- async function getPropertyIdForSchemaFileId(modelNameOrType, schemaFileId, scope = {}, db = BaseDb.getAppDb()) {
2032
- if (!db || !modelNameOrType || !schemaFileId)
2033
- return null;
2034
- const modelRecord = await resolveModelRecordByNameOrType(modelNameOrType, scope, db);
2035
- if (!modelRecord)
2036
- return null;
2037
- const rows = await db
2038
- .select({ id: properties.id })
2039
- .from(properties)
2040
- .where(and(eq(properties.modelId, modelRecord.id), eq(properties.schemaFileId, schemaFileId)))
2041
- .limit(1);
2042
- return rows[0]?.id ?? null;
2043
- }
2044
- /**
2045
- * Resolve property_id from model name/type and property name.
2046
- * Handles property name variants (e.g. htmlId -> html, avatarImageIds -> avatarImages).
2047
- * @param modelNameOrType - Model name (PascalCase) or model type (snake_case)
2048
- * @param propertyName - Property name as stored in metadata (may have Id/Ids suffix)
2049
- * @param scope - Which model is meant when several schemas define one with this name
2050
- * @param db - The database or transaction to read; the app database by default
2051
- * @returns properties.id or null if not found
2052
- */
2053
- async function getPropertyIdForModelAndName(modelNameOrType, propertyName, scope = {}, db = BaseDb.getAppDb()) {
2054
- if (!db)
2055
- return null;
2056
- if (!modelNameOrType || !propertyName)
2057
- return null;
2058
- const modelRecord = await resolveModelRecordByNameOrType(modelNameOrType, scope, db);
2059
- if (!modelRecord)
2060
- return null;
2061
- const propertyNamesToTry = [propertyName];
2062
- if (propertyName.endsWith('Ids')) {
2063
- propertyNamesToTry.push(propertyName.slice(0, -3));
2064
- }
2065
- if (propertyName.endsWith('Id')) {
2066
- propertyNamesToTry.push(propertyName.slice(0, -2));
2067
- }
2068
- for (const pName of propertyNamesToTry) {
2069
- const rows = await db
2070
- .select({ id: properties.id })
2071
- .from(properties)
2072
- .where(and(eq(properties.modelId, modelRecord.id), eq(properties.name, pName)))
2073
- .limit(1);
2074
- if (rows.length > 0 && rows[0].id != null) {
2075
- return rows[0].id;
2076
- }
2077
- }
2078
- return null;
2079
- }
2080
- /**
2081
- * A model's ItemStorage properties as stored in `properties` (storage_type 'ItemStorage'), with
2082
- * their storage path and extension. Reads the table, not ModelProperty instances, so it doesn't
2083
- * depend on whether (or from where) the model's property instances have loaded.
2084
- */
2085
- async function getItemStoragePropertiesForModel(modelNameOrType, scope = {}) {
2086
- const db = BaseDb.getAppDb();
2087
- if (!db || !modelNameOrType)
2088
- return [];
2089
- const modelRecord = await resolveModelRecordByNameOrType(modelNameOrType, scope, db);
2090
- if (!modelRecord)
2091
- return [];
2092
- return db
2093
- .select({
2094
- id: properties.id,
2095
- name: properties.name,
2096
- localStorageDir: properties.localStorageDir,
2097
- filenameSuffix: properties.filenameSuffix,
2098
- })
2099
- .from(properties)
2100
- .where(and(eq(properties.modelId, modelRecord.id), eq(properties.storageType, 'ItemStorage')));
2101
- }
2102
- /**
2103
- * Migrate metadata rows when a property is renamed.
2104
- * Updates metadata.propertyName from old to new for all rows that reference the renamed property.
2105
- * Called from renameModelProperty to prevent duplicate ItemProperties.
2106
- * @param modelName - Model name (PascalCase)
2107
- * @param oldPropertyName - Current property name before rename
2108
- * @param newPropertyName - New property name after rename
2109
- * @param propertySchemaFileId - Optional schema file ID for the property (most reliable lookup)
2110
- * @returns Count of metadata rows updated
2111
- */
2112
- async function migrateMetadataForPropertyRename(modelName, oldPropertyName, newPropertyName, propertySchemaFileId, scope = {}) {
2113
- const db = BaseDb.getAppDb();
2114
- if (!db) {
2115
- logger$3('[migrateMetadataForPropertyRename] Database not available, skipping');
2116
- return 0;
2117
- }
2118
- // Find the property row by schemaFileId (preferred) or by name
2119
- let propertyId = null;
2120
- if (propertySchemaFileId) {
2121
- propertyId = await getPropertyIdForSchemaFileId(modelName, propertySchemaFileId, scope);
2122
- }
2123
- if (propertyId == null) {
2124
- propertyId = await getPropertyIdForModelAndName(modelName, oldPropertyName, scope);
2125
- }
2126
- if (propertyId == null) {
2127
- logger$3(`[migrateMetadataForPropertyRename] Property not found: ${modelName}.${oldPropertyName}, skipping`);
2128
- return 0;
2129
- }
2130
- // Primary path: update metadata by propertyId
2131
- const toUpdateByPropertyId = await db
2132
- .select({ localId: metadata.localId })
2133
- .from(metadata)
2134
- .where(eq(metadata.propertyId, propertyId));
2135
- const updatedByPropertyId = toUpdateByPropertyId.length;
2136
- if (updatedByPropertyId > 0) {
2137
- await db
2138
- .update(metadata)
2139
- .set({ propertyName: newPropertyName })
2140
- .where(eq(metadata.propertyId, propertyId));
2141
- }
2142
- // Fallback: update metadata with null propertyId (legacy rows) by propertyName + modelType
2143
- const modelType = easName(modelName);
2144
- const toUpdateByFallback = await db
2145
- .select({ localId: metadata.localId })
2146
- .from(metadata)
2147
- .where(and(eq(metadata.propertyName, oldPropertyName), eq(metadata.modelType, modelType), isNull(metadata.propertyId)));
2148
- const updatedByFallback = toUpdateByFallback.length;
2149
- if (updatedByFallback > 0) {
2150
- await db
2151
- .update(metadata)
2152
- .set({ propertyName: newPropertyName })
2153
- .where(and(eq(metadata.propertyName, oldPropertyName), eq(metadata.modelType, modelType), isNull(metadata.propertyId)));
2154
- }
2155
- if (updatedByFallback > 0) {
2156
- logger$3(`[migrateMetadataForPropertyRename] Fallback updated ${updatedByFallback} rows (propertyId was null)`);
2157
- }
2158
- // Edits of the property still in the outbox (ADR 0019) apply under its new name
2159
- await db
2160
- .update(pendingWrites)
2161
- .set({ propertyName: newPropertyName })
2162
- .where(and(eq(pendingWrites.propertyName, oldPropertyName), eq(pendingWrites.status, 'pending'), inArray(pendingWrites.seedLocalId, db.select({ localId: seeds.localId }).from(seeds).where(eq(seeds.type, modelType)))));
2163
- const total = updatedByPropertyId + updatedByFallback;
2164
- if (total > 0) {
2165
- logger$3(`[migrateMetadataForPropertyRename] Migrated ${total} metadata rows for ${modelName}.${oldPropertyName} -> ${newPropertyName}`);
2166
- }
2167
- return total;
2168
- }
2169
- /**
2170
- * Moves a renamed property's metadata rows to its new name: rows linked by propertyId, and legacy
2171
- * rows without one by name and model type. Each stored form of the name moves to the same form of
2172
- * the new one: the key (`author` → `writer`), a single relation's `<key>Id` (`authorId` →
2173
- * `writerId`), a list relation's storage name (`authorIdentityIds` → `writerIdentityIds`).
2174
- * Returns the number of rows moved.
2175
- */
2176
- async function moveMetadataToRenamedProperty(db, { propertyId, modelType, oldName, newName, propertyDef, }) {
2177
- const forms = (name) => [
2178
- name,
2179
- toMetadataPropertyName(name, propertyDef?.dataType, propertyDef?.refValueType),
2180
- listRelationEasPropertyName(name, propertyDef) ?? name,
2181
- ];
2182
- const oldForms = forms(oldName);
2183
- const newForms = forms(newName);
2184
- let moved = 0;
2185
- const seen = new Set();
2186
- for (let i = 0; i < oldForms.length; i++) {
2187
- const from = oldForms[i];
2188
- const to = newForms[i];
2189
- if (from === to || seen.has(from))
2190
- continue;
2191
- seen.add(from);
2192
- const byId = await db
2193
- .update(metadata)
2194
- .set({ propertyName: to })
2195
- .where(and(eq(metadata.propertyId, propertyId), eq(metadata.propertyName, from)))
2196
- .returning({ localId: metadata.localId });
2197
- const legacy = await db
2198
- .update(metadata)
2199
- .set({ propertyName: to })
2200
- .where(and(isNull(metadata.propertyId), eq(metadata.propertyName, from), eq(metadata.modelType, modelType)))
2201
- .returning({ localId: metadata.localId });
2202
- moved += byId.length + legacy.length;
2203
- }
2204
- if (moved > 0)
2205
- logger$3(`[moveMetadataToRenamedProperty] moved ${moved} metadata rows ${oldName} -> ${newName}`);
2206
- return moved;
2207
- }
2208
- /**
2209
- * True when the models row is linked (model_schemas) to any schema other than schemaId.
2210
- */
2211
- const isModelLinkedToOtherSchema = async (db, modelId, schemaId) => {
2212
- const links = await db
2213
- .select({ schemaId: modelSchemas.schemaId })
2214
- .from(modelSchemas)
2215
- .where(eq(modelSchemas.modelId, modelId));
2216
- return links.some((l) => l.schemaId !== schemaId);
2217
- };
2218
- const isModelLinkedToAnySchema = async (db, modelId) => {
2219
- const links = await db
2220
- .select({ id: modelSchemas.id })
2221
- .from(modelSchemas)
2222
- .where(eq(modelSchemas.modelId, modelId))
2223
- .limit(1);
2224
- return links.length > 0;
2225
- };
2226
- /**
2227
- * Find or create a models row by schemaFileId (preferred) or name.
2228
- * Reuses an existing same-name row (especially schemaFileId=null stubs created via
2229
- * ref resolution) instead of inserting a second row — models.name is not unique,
2230
- * and duplicate names break name lookups (e.g. Resource.tags → Tag).
2231
- *
2232
- * When modelFileId is given, a same-name row that already carries a *different*
2233
- * non-null schemaFileId is only adopted if no other schema is linked to it via
2234
- * model_schemas. Otherwise it belongs to another schema (e.g. two schemas that each
2235
- * define "Post"), and re-id'ing it would strand that schema's cached Model instance,
2236
- * so a new row is inserted instead.
2237
- */
2238
- const findOrCreateModelRecord = async (db, modelName, modelFileId, schemaId) => {
2239
- if (modelFileId) {
2240
- const byFileId = await db
2241
- .select()
2242
- .from(models)
2243
- .where(eq(models.schemaFileId, modelFileId))
2244
- .limit(1);
2245
- if (byFileId.length > 0) {
2246
- const record = byFileId[0];
2247
- if (record.name !== modelName) {
2248
- await db
2249
- .update(models)
2250
- .set({ name: modelName })
2251
- .where(eq(models.id, record.id));
2252
- return { ...record, name: modelName };
2253
- }
2254
- return record;
2255
- }
2256
- }
2257
- const byName = (await db
2258
- .select()
2259
- .from(models)
2260
- .where(eq(models.name, modelName)));
2261
- if (byName.length > 0) {
2262
- // Prefer exact file-id match, then (for refs without a file id) this schema's row, then an
2263
- // adoptable null schemaFileId stub, then any single row.
2264
- let chosen = modelFileId
2265
- ? byName.find((r) => r.schemaFileId === modelFileId)
2266
- : undefined;
2267
- if (!chosen && !modelFileId && schemaId && byName.length > 1) {
2268
- for (const candidate of byName) {
2269
- if (!(await isModelLinkedToOtherSchema(db, candidate.id, schemaId))) {
2270
- const links = await db
2271
- .select({ schemaId: modelSchemas.schemaId })
2272
- .from(modelSchemas)
2273
- .where(eq(modelSchemas.modelId, candidate.id));
2274
- if (links.length > 0) {
2275
- chosen = candidate;
2276
- break;
2277
- }
2278
- }
2279
- }
2280
- }
2281
- chosen ?? (chosen = byName.find((r) => !r.schemaFileId));
2282
- if (!chosen && modelFileId) {
2283
- // Every same-name row has a different file id. Only adopt one that no other
2284
- // schema claims; otherwise fall through and insert a row for this file id.
2285
- // A row with a file id that no schema links is a deleted schema's leftover (its old
2286
- // properties are still attached), not an unclaimed row: don't adopt it either.
2287
- for (const candidate of byName) {
2288
- if (candidate.schemaFileId && !(await isModelLinkedToAnySchema(db, candidate.id)))
2289
- continue;
2290
- if (!(await isModelLinkedToOtherSchema(db, candidate.id, schemaId))) {
2291
- chosen = candidate;
2292
- break;
2293
- }
2294
- }
2295
- }
2296
- else if (!chosen) {
2297
- chosen =
2298
- (byName.length === 1 ? byName[0] : undefined) ||
2299
- byName.find((r) => !!r.schemaFileId) ||
2300
- byName[0];
2301
- }
2302
- if (chosen && modelFileId && chosen.schemaFileId !== modelFileId) {
2303
- const conflict = await db
2304
- .select()
2305
- .from(models)
2306
- .where(eq(models.schemaFileId, modelFileId))
2307
- .limit(1);
2308
- if (conflict.length > 0) {
2309
- return conflict[0];
2310
- }
2311
- // Adopt stub (null schemaFileId) or an unclaimed same-name row by attaching this file id.
2312
- await db
2313
- .update(models)
2314
- .set({ schemaFileId: modelFileId, isEdited: false })
2315
- .where(eq(models.id, chosen.id));
2316
- chosen = { ...chosen, schemaFileId: modelFileId, isEdited: false };
2317
- logger$3(`Adopted existing model "${modelName}" (id: ${chosen.id}) with schemaFileId "${modelFileId}"`);
2318
- }
2319
- if (chosen) {
2320
- return chosen;
2321
- }
2322
- }
2323
- try {
2324
- // A Model instance's own write can insert this file id between the lookups above and here
2325
- // (schema import writes each model twice at once). DO NOTHING instead of a failed statement,
2326
- // which sqlite-wasm logs as a warning even though the error is caught.
2327
- const inserted = await db
2328
- .insert(models)
2329
- .values({
2330
- name: modelName,
2331
- schemaFileId: modelFileId || null,
2332
- isEdited: false,
2333
- })
2334
- .onConflictDoNothing()
2335
- .returning();
2336
- if (inserted.length > 0) {
2337
- return inserted[0];
2338
- }
2339
- if (modelFileId) {
2340
- const existing = await db
2341
- .select()
2342
- .from(models)
2343
- .where(eq(models.schemaFileId, modelFileId))
2344
- .limit(1);
2345
- if (existing.length > 0) {
2346
- return existing[0];
2347
- }
2348
- }
2349
- throw new Error(`Model "${modelName}" was not inserted and no row has schemaFileId "${modelFileId}"`);
2350
- }
2351
- catch (error) {
2352
- if (isSqliteUniqueConstraintError(error) && modelFileId) {
2353
- const existing = await db
2354
- .select()
2355
- .from(models)
2356
- .where(eq(models.schemaFileId, modelFileId))
2357
- .limit(1);
2358
- if (existing.length > 0) {
2359
- return existing[0];
2360
- }
2361
- }
2362
- // Race: another writer inserted by name — reuse rather than throw on duplicates.
2363
- const raced = (await db
2364
- .select()
2365
- .from(models)
2366
- .where(eq(models.name, modelName)));
2367
- if (raced.length > 0) {
2368
- return ((modelFileId
2369
- ? raced.find((r) => r.schemaFileId === modelFileId)
2370
- : undefined) ||
2371
- raced.find((r) => !r.schemaFileId) ||
2372
- raced[0]);
2373
- }
2374
- throw error;
2375
- }
2376
- };
2377
- /**
2378
- * Find or create the models row a Relation property on ownerModelId points to. Model names are only
2379
- * unique per schema, so the ref is looked up in the owner's schema(s) first; a global name lookup
2380
- * by name threw "Multiple records found" once two schemas defined the ref model.
2381
- */
2382
- const findOrCreateRefModelRecord = async (db, refModelName, ownerModelId) => {
2383
- try {
2384
- const ref = await resolveRefModelRecord(refModelName, { modelId: ownerModelId }, db);
2385
- if (ref)
2386
- return ref;
2387
- }
2388
- catch (error) {
2389
- // Not in the owner's schema and defined by several others: pick/create like an import does
2390
- if (!(error instanceof AmbiguousModelError))
2391
- throw error;
2392
- }
2393
- const [ownerLink] = await db
2394
- .select({ schemaId: modelSchemas.schemaId })
2395
- .from(modelSchemas)
2396
- .where(eq(modelSchemas.modelId, ownerModelId))
2397
- .limit(1);
2398
- return findOrCreateModelRecord(db, refModelName, undefined, ownerLink?.schemaId ?? undefined);
2399
- };
2400
- /**
2401
- * Searches the database for an existing schema by schemaFileId (preferred) or name and creates it if it doesn't exist
2402
- * @param schema - The schema to add to the database
2403
- * @param schemaFileId - Optional schemaFileId from JSON file for change tracking
2404
- * @param schemaData - Optional full schema content as JSON string (SchemaFileFormat)
2405
- * @param isDraft - Optional flag indicating if schema is in draft state (default: false)
2406
- * @param isEdited - Optional flag indicating if schema has been edited locally (default: false)
2407
- * @returns The schema record (either existing or newly created)
2408
- */
2409
- const addSchemaToDb = async (schema, schemaFileId, schemaData, isDraft, isEdited) => {
2410
- const db = BaseDb.getAppDb();
2411
- if (!db) {
2412
- throw new Error('Database not found');
2413
- }
2414
- // The stored schema file may change its nameMatch levels (ADR 0009).
2415
- clearNameMatchLevels();
2416
- // First, try to find existing schema by schemaFileId (preferred for change tracking)
2417
- if (schemaFileId) {
2418
- const existingByFileId = await db
2419
- .select()
2420
- .from(schemas)
2421
- .where(eq(schemas.schemaFileId, schemaFileId))
2422
- .limit(1);
2423
- if (existingByFileId.length > 0) {
2424
- // Update fields if provided
2425
- const updates = {};
2426
- if (schemaFileId && !existingByFileId[0].schemaFileId) {
2427
- updates.schemaFileId = schemaFileId;
2428
- }
2429
- // Always update name if it's different (handles name changes)
2430
- if (schema.name && existingByFileId[0].name !== schema.name) {
2431
- updates.name = schema.name;
2432
- }
2433
- if (schemaData !== undefined) {
2434
- updates.schemaData = schemaData;
2435
- }
2436
- if (schema.version !== undefined && existingByFileId[0].version !== schema.version) {
2437
- updates.version = schema.version;
2438
- }
2439
- if (isDraft !== undefined && existingByFileId[0].isDraft !== isDraft) {
2440
- updates.isDraft = isDraft;
2441
- }
2442
- if (isEdited !== undefined && existingByFileId[0].isEdited !== isEdited) {
2443
- updates.isEdited = isEdited;
2444
- }
2445
- if (schema.updatedAt && existingByFileId[0].updatedAt !== schema.updatedAt) {
2446
- updates.updatedAt = schema.updatedAt;
2447
- }
2448
- if (Object.keys(updates).length > 0) {
2449
- await db
2450
- .update(schemas)
2451
- .set(updates)
2452
- .where(eq(schemas.id, existingByFileId[0].id));
2453
- return { ...existingByFileId[0], ...updates };
2454
- }
2455
- return existingByFileId[0];
2456
- }
2457
- }
2458
- // For drafts, check if there's an existing draft by name (not by schemaFileId)
2459
- if (isDraft === true) {
2460
- const existingDrafts = await db
2461
- .select()
2462
- .from(schemas)
2463
- .where(eq(schemas.name, schema.name))
2464
- .limit(1);
2465
- if (existingDrafts.length > 0 && existingDrafts[0].isDraft === true) {
2466
- // Update existing draft
2467
- const updates = {};
2468
- // Update schemaFileId if provided and different from existing
2469
- if (schemaFileId && existingDrafts[0].schemaFileId !== schemaFileId) {
2470
- updates.schemaFileId = schemaFileId;
2471
- }
2472
- if (schemaData !== undefined) {
2473
- updates.schemaData = schemaData;
2474
- }
2475
- if (schema.version !== undefined) {
2476
- updates.version = schema.version;
2477
- }
2478
- updates.updatedAt = schema.updatedAt || Date.now();
2479
- if (Object.keys(updates).length > 0) {
2480
- await db
2481
- .update(schemas)
2482
- .set(updates)
2483
- .where(eq(schemas.id, existingDrafts[0].id));
2484
- return { ...existingDrafts[0], ...updates };
2485
- }
2486
- return existingDrafts[0];
2487
- }
2488
- }
2489
- // Fallback: Search for existing schema by name
2490
- // BUT: If we have a schemaFileId, also check if any record with that schemaFileId exists
2491
- // (in case the name changed but we're looking up by the new name)
2492
- if (schemaFileId) {
2493
- // Double-check by schemaFileId - maybe the name changed and we're looking up by new name
2494
- const doubleCheckByFileId = await db
2495
- .select()
2496
- .from(schemas)
2497
- .where(eq(schemas.schemaFileId, schemaFileId))
2498
- .limit(1);
2499
- if (doubleCheckByFileId.length > 0) {
2500
- // Found by schemaFileId - update it (name might have changed)
2501
- const updates = {};
2502
- // Always update name if it's different (handles name changes)
2503
- if (schema.name && doubleCheckByFileId[0].name !== schema.name) {
2504
- updates.name = schema.name;
2505
- }
2506
- if (schemaData !== undefined) {
2507
- updates.schemaData = schemaData;
2508
- }
2509
- if (schema.version !== undefined && doubleCheckByFileId[0].version !== schema.version) {
2510
- updates.version = schema.version;
2511
- }
2512
- if (isDraft !== undefined && doubleCheckByFileId[0].isDraft !== isDraft) {
2513
- updates.isDraft = isDraft;
2514
- }
2515
- if (isEdited !== undefined && doubleCheckByFileId[0].isEdited !== isEdited) {
2516
- updates.isEdited = isEdited;
2517
- }
2518
- if (schema.updatedAt && doubleCheckByFileId[0].updatedAt !== schema.updatedAt) {
2519
- updates.updatedAt = schema.updatedAt;
2520
- }
2521
- if (Object.keys(updates).length > 0) {
2522
- await db
2523
- .update(schemas)
2524
- .set(updates)
2525
- .where(eq(schemas.id, doubleCheckByFileId[0].id));
2526
- return { ...doubleCheckByFileId[0], ...updates };
2527
- }
2528
- return doubleCheckByFileId[0];
2529
- }
2530
- }
2531
- // Final fallback: Search by name (only if no schemaFileId or schemaFileId lookup failed)
2532
- // If isDraft is explicitly false, first try to find non-draft, but also check for drafts
2533
- // to handle the case where a draft exists and we're loading a non-draft version
2534
- let existingSchemas = await db
2535
- .select()
2536
- .from(schemas)
2537
- .where(isDraft === false
2538
- ? and(eq(schemas.name, schema.name), eq(schemas.isDraft, false))
2539
- : eq(schemas.name, schema.name))
2540
- .limit(1);
2541
- // If isDraft is false and we didn't find a non-draft, check for a draft with the same name
2542
- // This handles the case where a draft was created first and we're now loading the non-draft version
2543
- if (isDraft === false && existingSchemas.length === 0) {
2544
- const draftSchemas = await db
2545
- .select()
2546
- .from(schemas)
2547
- .where(and(eq(schemas.name, schema.name), eq(schemas.isDraft, true)))
2548
- .limit(1);
2549
- if (draftSchemas.length > 0) {
2550
- // Found a draft - we'll update it to non-draft below
2551
- logger$3(`Found draft schema "${schema.name}" with schemaFileId "${draftSchemas[0].schemaFileId}", will update to non-draft with schemaFileId "${schemaFileId}"`);
2552
- existingSchemas = draftSchemas;
2553
- }
2554
- }
2555
- if (existingSchemas.length > 0) {
2556
- // The row is per schema name and holds its latest version. A complete schema file can bring
2557
- // a version under its own id (SDK version bumps keep the id; imported files may not).
2558
- const isOtherVersionFile = !!schemaFileId &&
2559
- !!existingSchemas[0].schemaFileId &&
2560
- existingSchemas[0].schemaFileId !== schemaFileId &&
2561
- existingSchemas[0].isDraft !== true &&
2562
- schema.version !== undefined &&
2563
- schema.version !== existingSchemas[0].version;
2564
- if (isOtherVersionFile && schema.version < existingSchemas[0].version) {
2565
- // An older version's file: rolling the row back to it would leave the newer version's id
2566
- // pointing at the older version's data.
2567
- logger$3(`Keeping schema "${schema.name}" at v${existingSchemas[0].version}; not replacing it with older v${schema.version}`);
2568
- return existingSchemas[0];
2569
- }
2570
- // Update fields if provided
2571
- const updates = {};
2572
- // Update schemaFileId if provided and different from existing (or if existing doesn't have one or is null)
2573
- // This is important for preserving IDs from schema files when drafts exist
2574
- // BUT: Don't overwrite a non-draft schema's schemaFileId with a new generated one
2575
- // This prevents loadOrCreateSchema from overwriting correct IDs from fixture files
2576
- if (schemaFileId && existingSchemas[0].schemaFileId !== schemaFileId) {
2577
- // Only update if:
2578
- // 1. Existing schema is a draft (can be updated)
2579
- // 2. Existing schema has no schemaFileId (should be set)
2580
- // 3. We're explicitly setting isDraft to false (updating from draft to non-draft)
2581
- // 4. It's a newer version's file: the row takes that version's data, so it takes its id too
2582
- const shouldUpdate = existingSchemas[0].isDraft === true ||
2583
- !existingSchemas[0].schemaFileId ||
2584
- (isDraft === false && existingSchemas[0].isDraft === true) ||
2585
- isOtherVersionFile;
2586
- if (shouldUpdate) {
2587
- logger$3(`Updating schemaFileId from "${existingSchemas[0].schemaFileId}" to "${schemaFileId}" for schema "${schema.name}"`);
2588
- updates.schemaFileId = schemaFileId;
2589
- }
2590
- else {
2591
- logger$3(`Preserving existing schemaFileId "${existingSchemas[0].schemaFileId}" for non-draft schema "${schema.name}" (new schemaFileId "${schemaFileId}" would overwrite it)`);
2592
- }
2593
- }
2594
- // Always update name if it's different (handles name changes)
2595
- if (schema.name && existingSchemas[0].name !== schema.name) {
2596
- updates.name = schema.name;
2597
- }
2598
- if (schemaData !== undefined) {
2599
- updates.schemaData = schemaData;
2600
- }
2601
- if (schema.version !== undefined && existingSchemas[0].version !== schema.version) {
2602
- updates.version = schema.version;
2603
- }
2604
- // If isDraft is explicitly false and we found a draft, update it to non-draft
2605
- if (isDraft === false && existingSchemas[0].isDraft === true) {
2606
- updates.isDraft = false;
2607
- }
2608
- else if (isDraft !== undefined && existingSchemas[0].isDraft !== isDraft) {
2609
- updates.isDraft = isDraft;
2610
- }
2611
- if (isEdited !== undefined && existingSchemas[0].isEdited !== isEdited) {
2612
- updates.isEdited = isEdited;
2613
- }
2614
- if (schema.updatedAt && existingSchemas[0].updatedAt !== schema.updatedAt) {
2615
- updates.updatedAt = schema.updatedAt;
2616
- }
2617
- if (Object.keys(updates).length > 0) {
2618
- logger$3(`Applying updates to schema "${schema.name}" (id: ${existingSchemas[0].id}):`, Object.keys(updates));
2619
- await db
2620
- .update(schemas)
2621
- .set(updates)
2622
- .where(eq(schemas.id, existingSchemas[0].id));
2623
- const updated = { ...existingSchemas[0], ...updates };
2624
- logger$3(`Schema "${schema.name}" updated. New schemaFileId: "${updated.schemaFileId}", isDraft: ${updated.isDraft}`);
2625
- return updated;
2626
- }
2627
- logger$3(`No updates needed for schema "${schema.name}" (id: ${existingSchemas[0].id})`);
2628
- return existingSchemas[0];
2629
- }
2630
- const newSchema = await db.insert(schemas).values({
2631
- name: schema.name,
2632
- version: schema.version,
2633
- schemaFileId: schemaFileId || null,
2634
- schemaData: schemaData || null,
2635
- isDraft: isDraft ?? false,
2636
- isEdited: isEdited ?? false,
2637
- createdAt: schema.createdAt,
2638
- updatedAt: schema.updatedAt,
2639
- }).returning();
2640
- // Notify React useSchemas so it can invalidate; live query often doesn't re-run when schemas table is inserted.
2641
- if (typeof BroadcastChannel !== 'undefined') {
2642
- try {
2643
- await new Promise((r) => setTimeout(r, 10));
2644
- postBroadcast('seed-schemas-invalidate', {});
2645
- }
2646
- catch (_) { }
2647
- }
2648
- return newSchema[0];
2649
- };
2650
- /**
2651
- * Rename a model in the database
2652
- * Updates the model name and all properties that reference it
2653
- * @param oldName - The current model name
2654
- * @param newName - The new model name
2655
- * @param schemaNameOrId - Schema name or schema ID to scope the rename (required when multiple schemas have models with the same name)
2656
- * @returns The updated model record
2657
- */
2658
- const renameModelInDb = async (oldName, newName, schemaNameOrId) => {
2659
- const db = BaseDb.getAppDb();
2660
- if (!db) {
2661
- throw new Error('Database not found');
2662
- }
2663
- const { schemas: schemasTable } = await Promise.resolve().then(function () { return SchemaSchema; });
2664
- // Find the model by old name, optionally scoped by schema
2665
- let existingModels;
2666
- if (schemaNameOrId !== undefined) {
2667
- if (typeof schemaNameOrId === 'number') {
2668
- const rows = await db
2669
- .select({
2670
- id: models.id,
2671
- name: models.name,
2672
- schemaFileId: models.schemaFileId,
2673
- isEdited: models.isEdited,
2674
- })
2675
- .from(models)
2676
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
2677
- .where(and(eq(models.name, oldName), eq(modelSchemas.schemaId, schemaNameOrId)))
2678
- .limit(1);
2679
- existingModels = rows;
2680
- }
2681
- else {
2682
- const rows = await db
2683
- .select({
2684
- id: models.id,
2685
- name: models.name,
2686
- schemaFileId: models.schemaFileId,
2687
- isEdited: models.isEdited,
2688
- })
2689
- .from(models)
2690
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
2691
- .innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
2692
- .where(and(eq(models.name, oldName), eq(schemasTable.name, schemaNameOrId)))
2693
- .limit(1);
2694
- existingModels = rows;
2695
- }
2696
- }
2697
- else {
2698
- existingModels = await db
2699
- .select({
2700
- id: models.id,
2701
- name: models.name,
2702
- schemaFileId: models.schemaFileId,
2703
- isEdited: models.isEdited,
2704
- })
2705
- .from(models)
2706
- .where(eq(models.name, oldName));
2707
- // Model names are only unique per schema: an unscoped rename must not pick one arbitrarily.
2708
- if (existingModels.length > 1) {
2709
- throw new Error(`Model "${oldName}" exists in more than one schema; pass the schema name or id to rename it`);
2710
- }
2711
- }
2712
- if (existingModels.length === 0) {
2713
- if (schemaNameOrId !== undefined) {
2714
- throw new Error(`Model "${oldName}" not found in schema "${schemaNameOrId}"`);
2715
- }
2716
- // Backward compat: model doesn't exist, create it with new name
2717
- const newModel = await db.insert(models).values({ name: newName }).returning();
2718
- return newModel[0];
2719
- }
2720
- const modelToRename = existingModels[0];
2721
- // Check if a model with the new name already exists in the same schema
2722
- let existingWithNewName;
2723
- if (schemaNameOrId !== undefined) {
2724
- if (typeof schemaNameOrId === 'number') {
2725
- existingWithNewName = await db
2726
- .select({
2727
- id: models.id,
2728
- name: models.name,
2729
- schemaFileId: models.schemaFileId,
2730
- isEdited: models.isEdited,
2731
- })
2732
- .from(models)
2733
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
2734
- .where(and(eq(models.name, newName), eq(modelSchemas.schemaId, schemaNameOrId)))
2735
- .limit(1);
2736
- }
2737
- else {
2738
- existingWithNewName = await db
2739
- .select({
2740
- id: models.id,
2741
- name: models.name,
2742
- schemaFileId: models.schemaFileId,
2743
- isEdited: models.isEdited,
2744
- })
2745
- .from(models)
2746
- .innerJoin(modelSchemas, eq(models.id, modelSchemas.modelId))
2747
- .innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
2748
- .where(and(eq(models.name, newName), eq(schemasTable.name, schemaNameOrId)))
2749
- .limit(1);
2750
- }
2751
- }
2752
- else {
2753
- existingWithNewName = await db
2754
- .select({
2755
- id: models.id,
2756
- name: models.name,
2757
- schemaFileId: models.schemaFileId,
2758
- isEdited: models.isEdited,
2759
- })
2760
- .from(models)
2761
- .where(eq(models.name, newName))
2762
- .limit(1);
2763
- }
2764
- if (existingWithNewName.length > 0) {
2765
- // New name already exists in this schema, return it
2766
- return existingWithNewName[0];
2767
- }
2768
- // Update the model name
2769
- await db
2770
- .update(models)
2771
- .set({ name: newName })
2772
- .where(eq(models.id, modelToRename.id));
2773
- return { ...modelToRename, name: newName };
2774
- };
2775
- /**
2776
- * Adds models and their properties to the database.
2777
- * Optionally connects models to a schema via join records.
2778
- * @param models - The model definitions to add
2779
- * @param schema - Optional schema to connect models to
2780
- * @param modelRenames - Optional map of old model names to new model names for handling renames
2781
- * @param schemaFileData - Optional object containing schemaFileId mappings from JSON file: { schemaFileId, modelFileIds: Map<modelName, id>, propertyFileIds: Map<modelName, Map<propertyName, id>> }
2782
- */
2783
- const addModelsToDb = async (models$1, schema, modelRenames, schemaFileData) => {
2784
- const db = BaseDb.getAppDb();
2785
- if (!db) {
2786
- throw new Error('Database not found');
2787
- }
2788
- // Model rows and their schema links decide which schema file's nameMatch applies (ADR 0009).
2789
- clearNameMatchLevels();
2790
- // If schema is provided, add it to the database
2791
- // Note: schema might already be a database record (with id) if passed from importJsonSchema
2792
- let schemaRecord;
2793
- if (schema) {
2794
- // Check if schema already has an id (it's already a database record)
2795
- // SchemaType includes id, so check if it's set
2796
- if (schema.id !== null && schema.id !== undefined) {
2797
- schemaRecord = schema;
2798
- logger$3(`Using existing schema record with id: ${schemaRecord.id}`);
2799
- }
2800
- else {
2801
- schemaRecord = await addSchemaToDb(schema, schemaFileData?.schemaFileId);
2802
- logger$3(`Created/found schema record with id: ${schemaRecord.id}`);
2803
- }
2804
- }
2805
- // Handle model renames first if provided
2806
- if (modelRenames && schemaRecord) {
2807
- const schemaNameOrId = schemaRecord.name ?? schemaRecord.id;
2808
- for (const [oldName, newName] of modelRenames.entries()) {
2809
- await renameModelInDb(oldName, newName, schemaNameOrId);
2810
- }
2811
- }
2812
- // Search for all models by schemaFileId (preferred) or name and create them if they don't exist
2813
- const modelRecords = new Map();
2814
- const modelProcessingErrors = [];
2815
- // Property rows this call changed, so cached ModelProperty instances can be given the new values
2816
- const updatedPropertyFileIds = [];
2817
- for (const [modelName, modelClass] of Object.entries(models$1)) {
2818
- try {
2819
- const modelFileId = schemaFileData?.modelFileIds?.get(modelName);
2820
- let modelRecord = await findOrCreateModelRecord(db, modelName, modelFileId, schemaRecord?.id);
2821
- // Keep schemaFileId aligned when findOrCreate returned a row that still needs adoption
2822
- // (rare: findOrCreate handles stubs and unclaimed same-name rows itself).
2823
- if (modelFileId && modelRecord.schemaFileId !== modelFileId) {
2824
- const existingWithFileId = await db
2825
- .select()
2826
- .from(models)
2827
- .where(eq(models.schemaFileId, modelFileId))
2828
- .limit(1);
2829
- if (existingWithFileId.length > 0 && existingWithFileId[0].id !== modelRecord.id) {
2830
- logger$3(`WARNING: Model "${modelName}" (id: ${modelRecord.id}) conflicts with existing model "${existingWithFileId[0].name}" (id: ${existingWithFileId[0].id}) both trying to use schemaFileId "${modelFileId}"`);
2831
- modelRecord = existingWithFileId[0];
2832
- }
2833
- else if (!modelRecord.schemaFileId) {
2834
- await db
2835
- .update(models)
2836
- .set({ schemaFileId: modelFileId })
2837
- .where(eq(models.id, modelRecord.id));
2838
- modelRecord = { ...modelRecord, schemaFileId: modelFileId };
2839
- }
2840
- }
2841
- modelRecords.set(modelName, modelRecord);
2842
- // Get all existing properties for this model upfront to handle renamed properties
2843
- const allDbProperties = await db
2844
- .select()
2845
- .from(properties)
2846
- .where(eq(properties.modelId, modelRecord.id));
2847
- // Track which DB properties have been matched to schema properties
2848
- const matchedDbPropertyIds = new Set();
2849
- // Get properties from Model context (where they're stored as an object when Model is created)
2850
- // modelClass.properties returns ModelProperty instances from DB, but we need the context properties
2851
- // which are stored as an object when Model.create() is called with properties
2852
- const modelContext = modelClass._getSnapshotContext?.() || {};
2853
- const contextProperties = modelContext.properties || {};
2854
- // When models are created from JSON, properties are stored as _pendingPropertyDefinitions
2855
- const pendingProperties = modelContext._pendingPropertyDefinitions || {};
2856
- // If context properties are empty, try modelClass.properties (for backward compatibility)
2857
- // but convert ModelProperty instances to object format
2858
- // Property definitions read from the schema file win: a cached Model keeps the properties it
2859
- // was created with, so after a rename its context still has the old name.
2860
- const filePropertyDefinitions = schemaFileData?.propertyDefinitions?.get(modelName);
2861
- let schema = {};
2862
- if (filePropertyDefinitions) {
2863
- schema = filePropertyDefinitions;
2864
- }
2865
- else if (Object.keys(contextProperties).length > 0) {
2866
- schema = contextProperties;
2867
- }
2868
- else if (Object.keys(pendingProperties).length > 0) {
2869
- // Use pending property definitions (from JSON import)
2870
- schema = pendingProperties;
2871
- }
2872
- else {
2873
- const modelHelpersMod = await import('./model-oHA3YhOd.js');
2874
- const { modelPropertiesToObject } = modelHelpersMod;
2875
- const modelProperties = modelClass.properties || [];
2876
- schema = modelPropertiesToObject(modelProperties);
2877
- }
2878
- // Search for all properties and create them if they don't exist
2879
- // Properties are unique by name + modelId, so we search using both
2880
- const schemaEntries = Object.entries(schema);
2881
- for (let index = 0; index < schemaEntries.length; index++) {
2882
- const [propertyName, propertyValues] = schemaEntries[index];
2883
- if (!propertyValues) {
2884
- throw new Error(`Property values not found for ${propertyName}`);
2885
- }
2886
- // Get property schemaFileId if available
2887
- let propertyFileId = schemaFileData?.propertyFileIds?.get(modelName)?.get(propertyName);
2888
- const propertyFileIdGenerated = !propertyFileId;
2889
- // If no propertyFileId from map, generate a random ID
2890
- // IDs should be generated in the import process before calling addModelsToDb
2891
- if (!propertyFileId) {
2892
- const { generateId } = await Promise.resolve().then(function () { return generateId$1; });
2893
- propertyFileId = generateId();
2894
- logger$3(`Generated propertyFileId "${propertyFileId}" for property "${modelName}:${propertyName}" (not found in propertyFileIds map)`);
2895
- }
2896
- // First, try to find by schemaFileId (preferred for change tracking)
2897
- let existingProperty;
2898
- if (propertyFileId) {
2899
- existingProperty = allDbProperties.find((p) => p.schemaFileId === propertyFileId && !matchedDbPropertyIds.has(p.id));
2900
- }
2901
- // Fallback: check if property exists by name + modelId
2902
- if (!existingProperty) {
2903
- existingProperty = allDbProperties.find((p) => p.name === propertyName && !matchedDbPropertyIds.has(p.id));
2904
- }
2905
- // Prepare property values
2906
- const propertyData = {
2907
- name: propertyName,
2908
- modelId: modelRecord.id,
2909
- dataType: normalizeDataType(propertyValues.dataType),
2910
- schemaFileId: propertyFileId || null,
2911
- required: propertyValues.required ?? false,
2912
- // The schema file is the source of truth here: no storage config means none.
2913
- ...propertyStorageColumns(propertyValues),
2914
- };
2915
- // Handle ref property - create ref model if needed
2916
- // Check both ref and refModelName (refModelName is set by createModelFromJson)
2917
- const refModelName = propertyValues.ref || propertyValues.refModelName;
2918
- if (refModelName) {
2919
- // When ref model was already processed in this batch, use it (avoids db round-trip)
2920
- const cachedRef = modelRecords.get(refModelName);
2921
- const refModelFileId = schemaFileData?.modelFileIds?.get(refModelName);
2922
- const refModel = cachedRef
2923
- ? cachedRef
2924
- : await findOrCreateModelRecord(db, refModelName, refModelFileId, schemaRecord?.id);
2925
- if (!cachedRef) {
2926
- modelRecords.set(refModelName, refModel);
2927
- }
2928
- propertyData.refModelId = refModel.id ?? null;
2929
- }
2930
- else {
2931
- // If it's not a Relation type, ensure refModelId is null
2932
- propertyData.refModelId = null;
2933
- }
2934
- if (propertyValues.refValueType) {
2935
- propertyData.refValueType = normalizeDataType(propertyValues.refValueType);
2936
- }
2937
- else {
2938
- // If refValueType is not set, ensure it's null
2939
- propertyData.refValueType = null;
2940
- }
2941
- // ADR 0008: a rename needs the property's id. A name that matches no stored property by id
2942
- // or name is a new property, even if one of the same type was removed in the same edit.
2943
- if (existingProperty) {
2944
- // Mark this DB property as matched
2945
- matchedDbPropertyIds.add(existingProperty.id);
2946
- // A property edited locally keeps its row.
2947
- const isPropertyEdited = existingProperty.isEdited === true;
2948
- if (isPropertyEdited) {
2949
- // Property has been locally edited - skip update to preserve local changes
2950
- logger$3(`Skipping update for property ${modelName}:${existingProperty.name} -> ${propertyName} - it has been locally edited and will be preserved`);
2951
- continue;
2952
- }
2953
- // Property exists (possibly with different name) - update it with new values from schema file
2954
- // This handles both regular updates and renames
2955
- // Ensure schemaFileId is updated if we have it
2956
- // Preserve existing isEdited value if it was true (local edits take precedence)
2957
- const updateData = { ...propertyData };
2958
- // A generated id must not replace the one the row already has from its schema file
2959
- if (propertyFileIdGenerated && existingProperty.schemaFileId) {
2960
- updateData.schemaFileId = existingProperty.schemaFileId;
2961
- }
2962
- if (existingProperty.isEdited === true) {
2963
- // Preserve isEdited = true if property was locally edited
2964
- delete updateData.isEdited;
2965
- }
2966
- else {
2967
- updateData.isEdited = false;
2968
- }
2969
- // Skip db.update when data unchanged (avoids slow OPFS writes in browser)
2970
- const unchanged = existingProperty.name === updateData.name &&
2971
- existingProperty.dataType === updateData.dataType &&
2972
- existingProperty.schemaFileId === (updateData.schemaFileId ?? null) &&
2973
- existingProperty.required === (updateData.required ?? false) &&
2974
- existingProperty.refModelId === (updateData.refModelId ?? null) &&
2975
- existingProperty.refValueType === (updateData.refValueType ?? null) &&
2976
- (existingProperty.storageType ?? null) === (updateData.storageType ?? null) &&
2977
- (existingProperty.localStorageDir ?? null) === (updateData.localStorageDir ?? null) &&
2978
- (existingProperty.filenameSuffix ?? null) === (updateData.filenameSuffix ?? null) &&
2979
- existingProperty.isEdited === (updateData.isEdited ?? false);
2980
- if (!unchanged) {
2981
- await db
2982
- .update(properties)
2983
- .set(updateData)
2984
- .where(eq(properties.id, existingProperty.id));
2985
- const updatedFileId = updateData.schemaFileId ?? existingProperty.schemaFileId;
2986
- if (updatedFileId)
2987
- updatedPropertyFileIds.push(updatedFileId);
2988
- }
2989
- if (existingProperty.name !== propertyName) {
2990
- // Renamed (matched by id): its values move to the new name.
2991
- await moveMetadataToRenamedProperty(db, {
2992
- propertyId: existingProperty.id,
2993
- modelType: easName(modelName),
2994
- oldName: existingProperty.name,
2995
- newName: propertyName,
2996
- propertyDef: propertyValues,
2997
- });
2998
- }
2999
- }
3000
- else {
3001
- // Property doesn't exist, create it with schemaFileId
3002
- // Set isEdited = false when loading from schema file
3003
- const propertyDataWithIsEdited = {
3004
- ...propertyData,
3005
- isEdited: false,
3006
- };
3007
- // Global schemaFileId lookup (unique across all models) before insert — avoids
3008
- // UNIQUE races when the property already exists on another model row or from a prior pass.
3009
- if (propertyFileId) {
3010
- const existingByFileId = await db
3011
- .select()
3012
- .from(properties)
3013
- .where(eq(properties.schemaFileId, propertyFileId))
3014
- .limit(1);
3015
- if (existingByFileId.length > 0) {
3016
- const found = existingByFileId[0];
3017
- matchedDbPropertyIds.add(found.id);
3018
- logger$3(`Property ${modelName}:${propertyName} already exists by schemaFileId "${propertyFileId}" (id: ${found.id}), skipping insert`);
3019
- continue;
3020
- }
3021
- }
3022
- logger$3(`Creating new property ${modelName}:${propertyName} with schemaFileId: ${propertyData.schemaFileId}`);
3023
- // A conflict means the row already exists: the schema import's Model instances write their
3024
- // properties at the same time as this. DO NOTHING keeps sqlite-wasm from logging a failed insert.
3025
- const inserted = await db
3026
- .insert(properties)
3027
- .values(propertyDataWithIsEdited)
3028
- .onConflictDoNothing()
3029
- .returning({ id: properties.id });
3030
- if (inserted.length === 0) {
3031
- logger$3(`Property ${modelName}:${propertyName} already exists (UNIQUE constraint), treating as success`);
3032
- }
3033
- }
3034
- }
3035
- }
3036
- catch (error) {
3037
- const errorMessage = error?.message || String(error);
3038
- logger$3(`Error processing model "${modelName}": ${errorMessage}`);
3039
- modelProcessingErrors.push({ modelName, error: errorMessage });
3040
- // Continue processing other models even if one fails
3041
- }
3042
- }
3043
- if (modelProcessingErrors.length > 0) {
3044
- console.warn(`[addModelsToDb] Errors occurred while processing ${modelProcessingErrors.length} model(s):`, modelProcessingErrors.map(e => `${e.modelName}: ${e.error}`).join('; '));
3045
- logger$3(`Errors processing models: ${modelProcessingErrors.map(e => e.modelName).join(', ')}`);
3046
- }
3047
- // If schema was provided, create modelSchema join records to connect models to the schema
3048
- if (schemaRecord && schemaRecord.id) {
3049
- const modelIds = [...modelRecords.entries()]
3050
- .filter(([, r]) => r.id)
3051
- .map(([name, r]) => ({ modelName: name, modelId: r.id }));
3052
- if (modelIds.length === 0) {
3053
- logger$3('No model IDs to link, skipping schema linking');
3054
- }
3055
- else {
3056
- // Single batch query: get all existing joins for this schema
3057
- const existingJoins = await db
3058
- .select({ modelId: modelSchemas.modelId })
3059
- .from(modelSchemas)
3060
- .where(eq(modelSchemas.schemaId, schemaRecord.id));
3061
- const existingSet = new Set(existingJoins.map((r) => r.modelId));
3062
- const toInsert = modelIds.filter(({ modelId }) => !existingSet.has(modelId));
3063
- if (toInsert.length > 0) {
3064
- for (const { modelName, modelId } of toInsert) {
3065
- if (!(await linkModelToSchema(db, modelId, schemaRecord.id)))
3066
- continue;
3067
- logger$3(`Created join table entry for model ${modelName} (id: ${modelId}) to schema (id: ${schemaRecord.id})`);
3068
- }
3069
- }
3070
- }
3071
- }
3072
- else if (schemaRecord && !schemaRecord.id) {
3073
- logger$3(`Warning: schemaRecord provided but has no id, cannot create join table entries`);
3074
- }
3075
- // A cached instance is otherwise never re-read from its row (createById returns it as is)
3076
- await refreshCachedProperties(updatedPropertyFileIds);
3077
- };
3078
- /**
3079
- * Loads models from the database for a given schema by querying the model_schemas join table.
3080
- * This ensures that models added to the database (via model_schemas) are included even if
3081
- * they're not in the schemaData JSON.
3082
- * @param schemaId - The ID of the schema record in the database
3083
- * @returns A map of model names to model data (compatible with SchemaFileFormat.models)
3084
- */
3085
- const loadModelsFromDbForSchema = async (schemaId) => {
3086
- const db = BaseDb.getAppDb();
3087
- if (!db) {
3088
- logger$3('Database not found, cannot load models from DB');
3089
- return {};
3090
- }
3091
- try {
3092
- // Query model_schemas join table to find all models linked to this schema
3093
- const modelSchemaRecords = await db
3094
- .select({
3095
- modelId: modelSchemas.modelId,
3096
- modelName: models.name,
3097
- schemaFileId: models.schemaFileId,
3098
- })
3099
- .from(modelSchemas)
3100
- .innerJoin(models, eq(modelSchemas.modelId, models.id))
3101
- .where(eq(modelSchemas.schemaId, schemaId));
3102
- const models$1 = {};
3103
- // For each model, load its properties
3104
- for (const { modelId, modelName, schemaFileId } of modelSchemaRecords) {
3105
- if (!modelId || !modelName)
3106
- continue;
3107
- // Get all properties for this model
3108
- const propertyRecords = await db
3109
- .select()
3110
- .from(properties)
3111
- .where(eq(properties.modelId, modelId));
3112
- // Reconstruct properties object
3113
- const modelProperties = {};
3114
- for (const prop of propertyRecords) {
3115
- // Build a basic property structure from database fields
3116
- // Note: This is a simplified reconstruction - full property schemas
3117
- // (like Relation details, List configs, etc.) should come from schemaData
3118
- const propertyData = {
3119
- dataType: normalizeDataType(prop.dataType ?? undefined),
3120
- };
3121
- if (prop.schemaFileId) {
3122
- propertyData.schemaFileId = prop.schemaFileId;
3123
- }
3124
- // Add ref information if it's a relation
3125
- if (prop.refModelId) {
3126
- // Get the referenced model name
3127
- const refModelRecords = await db
3128
- .select({ name: models.name })
3129
- .from(models)
3130
- .where(eq(models.id, prop.refModelId))
3131
- .limit(1);
3132
- if (refModelRecords.length > 0) {
3133
- propertyData.ref = refModelRecords[0].name;
3134
- }
3135
- }
3136
- if (prop.refValueType) {
3137
- propertyData.refValueType = normalizeDataType(prop.refValueType ?? undefined);
3138
- }
3139
- if (prop.storageType)
3140
- propertyData.storageType = prop.storageType;
3141
- if (prop.localStorageDir)
3142
- propertyData.localStorageDir = prop.localStorageDir;
3143
- if (prop.filenameSuffix)
3144
- propertyData.filenameSuffix = prop.filenameSuffix;
3145
- modelProperties[prop.name] = propertyData;
3146
- }
3147
- // Create model structure. Include id (schemaFileId) so processSchemaFiles can populate
3148
- // modelFileIds and Model.create can skip duplicate-name logic, preventing model
3149
- // duplication on reload (Post 1 1, Post 2 1, etc.).
3150
- models$1[modelName] = {
3151
- properties: modelProperties,
3152
- ...(schemaFileId ? { id: schemaFileId } : {}),
3153
- // Note: description would need to be stored separately
3154
- // or reconstructed from schemaData if available
3155
- };
3156
- }
3157
- logger$3(`Loaded ${Object.keys(models$1).length} models from database for schema ${schemaId}`);
3158
- return models$1;
3159
- }
3160
- catch (error) {
3161
- logger$3(`Error loading models from database for schema ${schemaId}:`, error);
3162
- return {};
3163
- }
3164
- };
3165
- /**
3166
- * Merges the models {@link loadModelsFromDbForSchema} read from the database into a schema's
3167
- * models. The database decides which models and properties exist and their stored fields; the
3168
- * schema keeps what only it holds (nameMatch, description, validation, ...), found by id first so
3169
- * a property renamed in the database keeps them. A schema property whose id a database property
3170
- * has is dropped under its old name.
3171
- */
3172
- const mergeDbModelsIntoSchemaModels = (schemaModels, dbModels) => {
3173
- const merged = { ...(schemaModels ?? {}) };
3174
- for (const [modelName, dbModel] of Object.entries(dbModels)) {
3175
- const schemaModel = schemaModels?.[modelName];
3176
- if (!schemaModel) {
3177
- merged[modelName] = dbModel;
3178
- continue;
3179
- }
3180
- const schemaProps = schemaModel.properties ?? {};
3181
- const dbProps = dbModel.properties ?? {};
3182
- const schemaNameById = new Map();
3183
- for (const [name, prop] of Object.entries(schemaProps)) {
3184
- const id = prop?.id ?? prop?.schemaFileId;
3185
- if (id)
3186
- schemaNameById.set(id, name);
3187
- }
3188
- const properties = {};
3189
- const matched = new Set();
3190
- for (const [name, dbProp] of Object.entries(dbProps)) {
3191
- const byId = dbProp?.schemaFileId ? schemaNameById.get(dbProp.schemaFileId) : undefined;
3192
- const byName = schemaProps[name];
3193
- const byNameId = byName?.id ?? byName?.schemaFileId;
3194
- // By name only when the ids don't say it's another property.
3195
- const schemaName = byId ?? (byName && !(byNameId && dbProp?.schemaFileId && byNameId !== dbProp.schemaFileId) ? name : undefined);
3196
- const schemaProp = schemaName ? schemaProps[schemaName] : undefined;
3197
- if (schemaName && schemaProp)
3198
- matched.add(schemaName);
3199
- // The database's dataType and ref replace the file's type and model.
3200
- const { type, model, ...fromSchema } = schemaProp ?? {};
3201
- properties[name] = {
3202
- ...fromSchema,
3203
- ...(dbProp?.dataType === undefined && type !== undefined ? { type } : {}),
3204
- ...(dbProp?.ref === undefined && model !== undefined ? { model } : {}),
3205
- ...dbProp,
3206
- };
3207
- }
3208
- const unmatched = Object.fromEntries(Object.entries(schemaProps).filter(([name]) => !matched.has(name)));
3209
- merged[modelName] = { ...schemaModel, ...dbModel, properties: { ...unmatched, ...properties } };
3210
- }
3211
- return merged;
3212
- };
3213
- /**
3214
- * Returns model name by database model ID.
3215
- */
3216
- async function getModelNameByModelId(modelId) {
3217
- const db = BaseDb.getAppDb();
3218
- if (!db)
3219
- return undefined;
3220
- try {
3221
- const rows = await db
3222
- .select({ name: models.name })
3223
- .from(models)
3224
- .where(eq(models.id, modelId))
3225
- .limit(1);
3226
- return rows.length > 0 ? rows[0].name : undefined;
3227
- }
3228
- catch {
3229
- return undefined;
3230
- }
3231
- }
3232
- /**
3233
- * Resolves modelName and dataType for a property by its schemaFileId (e.g. context.id).
3234
- * Used when machine context lacks these (e.g. just-created property renamed before full context is set).
3235
- */
3236
- async function getPropertyModelNameAndDataType(schemaFileId) {
3237
- const db = BaseDb.getAppDb();
3238
- if (!db || !schemaFileId)
3239
- return undefined;
3240
- try {
3241
- const rows = await db
3242
- .select({
3243
- dataType: properties.dataType,
3244
- modelId: properties.modelId,
3245
- })
3246
- .from(properties)
3247
- .where(eq(properties.schemaFileId, schemaFileId))
3248
- .limit(1);
3249
- if (rows.length === 0)
3250
- return undefined;
3251
- const modelRows = await db
3252
- .select({ name: models.name })
3253
- .from(models)
3254
- .where(eq(models.id, rows[0].modelId))
3255
- .limit(1);
3256
- if (modelRows.length === 0)
3257
- return undefined;
3258
- return {
3259
- modelName: modelRows[0].name,
3260
- dataType: rows[0].dataType ?? '',
3261
- };
3262
- }
3263
- catch {
3264
- return undefined;
3265
- }
3266
- }
3267
- /**
3268
- * Saves a property's changes to the database without updating the JSON schema file.
3269
- * This is used when properties are edited but the schema hasn't been saved as a new version yet.
3270
- * @param property - The ModelPropertyMachineContext with updated values
3271
- */
3272
- const savePropertyToDb = async (property) => {
3273
- const db = BaseDb.getAppDb();
3274
- if (!db) {
3275
- throw new Error('Database not found');
3276
- }
3277
- if (!property.name) {
3278
- throw new Error('Property name is required');
3279
- }
3280
- // Find the model: prefer modelId (reliable), then modelName as name, then modelName as schemaFileId
3281
- // (context.modelName can be a model schemaFileId when schema uses that as identifier)
3282
- let modelRecord;
3283
- if (property.modelId != null && typeof property.modelId === 'number') {
3284
- const byId = await db
3285
- .select()
3286
- .from(models)
3287
- .where(eq(models.id, property.modelId))
3288
- .limit(1);
3289
- modelRecord = byId[0];
3290
- }
3291
- if (!modelRecord && property.modelName) {
3292
- // Model names are only unique per schema: scope by the property's schema when known
3293
- modelRecord = await resolveModelRecord(property.modelName, { schemaName: property._schemaName }, db);
3294
- }
3295
- if (!modelRecord && property.modelName) {
3296
- const bySchemaFileId = await db
3297
- .select()
3298
- .from(models)
3299
- .where(eq(models.schemaFileId, property.modelName))
3300
- .limit(1);
3301
- modelRecord = bySchemaFileId[0];
3302
- }
3303
- // Fallback: resolve model from existing property row (by schemaFileId) when modelName is wrong
3304
- const schemaFileId = property._propertyFileId || (typeof property.id === 'string' ? property.id : undefined);
3305
- if (!modelRecord && schemaFileId) {
3306
- const propRows = await db
3307
- .select({ modelId: properties.modelId })
3308
- .from(properties)
3309
- .where(eq(properties.schemaFileId, schemaFileId))
3310
- .limit(1);
3311
- if (propRows.length > 0 && propRows[0].modelId) {
3312
- const byPropModelId = await db
3313
- .select()
3314
- .from(models)
3315
- .where(eq(models.id, propRows[0].modelId))
3316
- .limit(1);
3317
- modelRecord = byPropModelId[0];
3318
- }
3319
- }
3320
- if (!modelRecord) {
3321
- throw new Error(`Model not found in database (modelId=${property.modelId}, modelName=${property.modelName})`);
3322
- }
3323
- // Find existing property - try multiple strategies to handle name changes
3324
- // 1. First try by schemaFileId (most reliable - doesn't change when name changes)
3325
- let existingProperties = [];
3326
- logger$3(`[savePropertyToDb] Looking for property ${property.modelName}:${property.name} (schemaFileId: ${schemaFileId}, originalName: ${property._originalValues?.name})`);
3327
- if (schemaFileId) {
3328
- existingProperties = await db
3329
- .select()
3330
- .from(properties)
3331
- .where(and(eq(properties.schemaFileId, schemaFileId), eq(properties.modelId, modelRecord.id)))
3332
- .limit(1);
3333
- logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by schemaFileId`);
3334
- }
3335
- // 2. If not found by schemaFileId, try by original name (if name was changed)
3336
- if (existingProperties.length === 0 && property._originalValues?.name && property._originalValues.name !== property.name) {
3337
- logger$3(`[savePropertyToDb] Trying to find by original name: ${property._originalValues.name}`);
3338
- existingProperties = await db
3339
- .select()
3340
- .from(properties)
3341
- .where(and(eq(properties.name, property._originalValues.name), eq(properties.modelId, modelRecord.id)))
3342
- .limit(1);
3343
- logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by original name`);
3344
- }
3345
- // 3. Fallback: try by current name
3346
- if (existingProperties.length === 0) {
3347
- logger$3(`[savePropertyToDb] Trying to find by current name: ${property.name}`);
3348
- existingProperties = await db
3349
- .select()
3350
- .from(properties)
3351
- .where(and(eq(properties.name, property.name), eq(properties.modelId, modelRecord.id)))
3352
- .limit(1);
3353
- logger$3(`[savePropertyToDb] Found ${existingProperties.length} properties by current name`);
3354
- }
3355
- // Prepare property data
3356
- const propertyData = {
3357
- name: property.name,
3358
- modelId: modelRecord.id,
3359
- dataType: normalizeDataType(property.dataType),
3360
- };
3361
- // Preserve schemaFileId if we have it
3362
- if (schemaFileId) {
3363
- propertyData.schemaFileId = schemaFileId;
3364
- }
3365
- // Handle ref property - create ref model if needed
3366
- if (property.refModelName) {
3367
- const refModel = await findOrCreateRefModelRecord(db, property.refModelName, modelRecord.id);
3368
- propertyData.refModelId = refModel.id;
3369
- }
3370
- else if (property.refModelId) {
3371
- propertyData.refModelId = property.refModelId;
3372
- }
3373
- else {
3374
- // If it's not a Relation type, ensure refModelId is null
3375
- propertyData.refModelId = null;
3376
- }
3377
- if (property.refValueType) {
3378
- propertyData.refValueType = normalizeDataType(property.refValueType);
3379
- }
3380
- else {
3381
- // If refValueType is not set, ensure it's null
3382
- propertyData.refValueType = null;
3383
- }
3384
- propertyData.required = property.required ?? false;
3385
- // Only when the context has storage settings: a context built without them (e.g. loaded before
3386
- // they were stored) must not clear the row's.
3387
- const storage = propertyStorageColumns(property);
3388
- if (hasStorageSettings(storage)) {
3389
- Object.assign(propertyData, storage);
3390
- }
3391
- if (existingProperties.length > 0) {
3392
- // Property exists, update it with new values (including new name)
3393
- // Set isEdited = true when property is edited
3394
- const existingProperty = existingProperties[0];
3395
- await db
3396
- .update(properties)
3397
- .set({
3398
- ...propertyData,
3399
- isEdited: true, // Mark as edited when saving changes
3400
- })
3401
- .where(eq(properties.id, existingProperty.id));
3402
- logger$3(`Updated property ${property.modelName}:${property._originalValues?.name || 'unknown'} -> ${property.name} in database`);
3403
- }
3404
- else {
3405
- // Property doesn't exist, create it
3406
- // Set isEdited = true for runtime-created properties
3407
- // A conflict means the row already exists (concurrent addModelsToDb or another write): treat as success
3408
- const inserted = await db
3409
- .insert(properties)
3410
- .values({
3411
- ...propertyData,
3412
- isEdited: true, // Runtime-created properties are edited
3413
- })
3414
- .onConflictDoNothing()
3415
- .returning({ id: properties.id });
3416
- if (inserted.length > 0) {
3417
- logger$3(`Created property ${property.modelName}:${property.name} in database`);
3418
- }
3419
- else {
3420
- logger$3(`Property ${property.modelName}:${property.name} already exists (UNIQUE constraint), treating as success`);
3421
- }
3422
- }
3423
- };
3424
- function parseAddressConfig(value) {
3425
- if (!value)
3426
- return null;
3427
- try {
3428
- const parsed = JSON.parse(value);
3429
- if (Array.isArray(parsed)) {
3430
- return normalizeAddressConfig(parsed);
3431
- }
3432
- if (parsed && typeof parsed === 'object' && Array.isArray(parsed.owned)) {
3433
- return normalizeAddressConfig({
3434
- owned: parsed.owned,
3435
- watched: Array.isArray(parsed.watched) ? parsed.watched : [],
3436
- });
3437
- }
3438
- return null;
3439
- }
3440
- catch {
3441
- return null;
3442
- }
3443
- }
3444
- const getOwnedAddressesFromDb = async () => {
3445
- const config = await getAddressConfigFromDb();
3446
- return config?.owned ?? [];
3447
- };
3448
- const getWatchedAddressesFromDb = async () => {
3449
- const config = await getAddressConfigFromDb();
3450
- return config?.watched ?? [];
3451
- };
3452
- /**
3453
- * Returns persisted owned or watched addresses for list filters.
3454
- * Does not include `getAdditionalSyncAddresses` (those are extra EAS indexers).
3455
- */
3456
- const getAddressesForItemsFilter = async (addressFilter) => {
3457
- if (addressFilter === 'owned') {
3458
- return getOwnedAddressesFromDb();
3459
- }
3460
- return getWatchedAddressesFromDb();
3461
- };
3462
- /**
3463
- * Returns owned + watched addresses. Use for EAS sync and file download.
3464
- */
3465
- const getAllAddressesFromDb = async () => {
3466
- const config = await getAddressConfigFromDb();
3467
- if (!config)
3468
- return [];
3469
- return [...config.owned, ...config.watched];
3470
- };
3471
- async function getAddressConfigFromDb() {
3472
- const appDb = BaseDb.getAppDb();
3473
- if (!appDb) {
3474
- return new Promise((resolve) => {
3475
- setTimeout(async () => {
3476
- resolve(await getAddressConfigFromDb());
3477
- }, 500);
3478
- });
3479
- }
3480
- const appStatesRecords = await appDb
3481
- .select()
3482
- .from(appState)
3483
- .where(eq(appState.key, 'addresses'))
3484
- .limit(1);
3485
- if (!appStatesRecords || appStatesRecords.length === 0) {
3486
- return null;
3487
- }
3488
- const addressArrayString = appStatesRecords[0].value;
3489
- return parseAddressConfig(addressArrayString);
3490
- }
3491
- /**
3492
- * Write model to database and create model_schemas join entry
3493
- * @param modelFileId - The model file ID (schema_file_id)
3494
- * @param data - Model data including modelName, schemaId, and optional properties
3495
- */
3496
- async function writeModelToDb(modelFileId, data) {
3497
- const db = BaseDb.getAppDb();
3498
- if (!db)
3499
- throw new Error('Database not available');
3500
- // Find or create model record
3501
- const modelRecords = await db
3502
- .select()
3503
- .from(models)
3504
- .where(eq(models.schemaFileId, modelFileId))
3505
- .limit(1);
3506
- let modelId;
3507
- if (modelRecords.length === 0) {
3508
- // Create new model record
3509
- // Check again right before insert to avoid race conditions
3510
- const doubleCheck = await db
3511
- .select()
3512
- .from(models)
3513
- .where(eq(models.schemaFileId, modelFileId))
3514
- .limit(1);
3515
- if (doubleCheck.length > 0) {
3516
- // Another process created it, use existing
3517
- modelId = doubleCheck[0].id;
3518
- logger$3(`Model with schemaFileId "${modelFileId}" was created by another process, using existing record (id: ${modelId})`);
3519
- }
3520
- else {
3521
- // DO NOTHING on conflict: addModelsToDb writes the same row during schema import, and a failed
3522
- // insert is logged by sqlite-wasm even when caught.
3523
- const newModel = await db
3524
- .insert(models)
3525
- .values({
3526
- name: data.modelName,
3527
- schemaFileId: modelFileId,
3528
- isEdited: true, // Runtime-created models are edited
3529
- })
3530
- .onConflictDoNothing()
3531
- .returning();
3532
- if (newModel.length > 0) {
3533
- modelId = newModel[0].id;
3534
- }
3535
- else {
3536
- const existing = await db
3537
- .select()
3538
- .from(models)
3539
- .where(eq(models.schemaFileId, modelFileId))
3540
- .limit(1);
3541
- if (existing.length === 0) {
3542
- throw new Error(`Failed to create or find model "${data.modelName}" with schemaFileId "${modelFileId}"`);
3543
- }
3544
- modelId = existing[0].id;
3545
- logger$3(`Found existing model with schemaFileId "${modelFileId}" (id: ${modelId}) after insert conflict`);
3546
- }
3547
- }
3548
- }
3549
- else {
3550
- // Update existing model record
3551
- modelId = modelRecords[0].id;
3552
- const updates = {};
3553
- if (data.modelName !== modelRecords[0].name) {
3554
- updates.name = data.modelName;
3555
- }
3556
- if (Object.keys(updates).length > 0) {
3557
- await db
3558
- .update(models)
3559
- .set(updates)
3560
- .where(eq(models.id, modelId));
3561
- }
3562
- }
3563
- // Validate that modelId and schemaId are valid before creating join entry
3564
- if (!modelId || !Number.isInteger(modelId) || modelId <= 0) {
3565
- throw new Error(`Invalid modelId: ${modelId}. Model record must be created successfully before creating join entry.`);
3566
- }
3567
- if (!data.schemaId || !Number.isInteger(data.schemaId) || data.schemaId <= 0) {
3568
- throw new Error(`Invalid schemaId: ${data.schemaId}. Schema record must exist before creating join entry.`);
3569
- }
3570
- // Create model_schemas join entry
3571
- // CRITICAL: This must happen AFTER the model record is fully saved and committed
3572
- // The modelId comes from .returning() which ensures the record is committed
3573
- const existingJoin = await db
3574
- .select()
3575
- .from(modelSchemas)
3576
- .where(and(eq(modelSchemas.modelId, modelId), eq(modelSchemas.schemaId, data.schemaId)))
3577
- .limit(1);
3578
- if (existingJoin.length === 0) {
3579
- // Only provide modelId and schemaId - id is auto-increment and should not be included
3580
- // Don't use type cast - let Drizzle infer the correct type without id
3581
- // Verify both modelId and schemaId exist before inserting join record
3582
- try {
3583
- // Verify modelId exists
3584
- const modelCheck = await db
3585
- .select({ id: models.id })
3586
- .from(models)
3587
- .where(eq(models.id, modelId))
3588
- .limit(1);
3589
- if (modelCheck.length === 0) {
3590
- throw new Error(`Model with id ${modelId} does not exist in database. Cannot create join record.`);
3591
- }
3592
- // Verify schemaId exists and get name/fileId for invalidation broadcast
3593
- const schemaSchemaMod = await Promise.resolve().then(function () { return SchemaSchema; });
3594
- const { schemas: schemasTable } = schemaSchemaMod;
3595
- const schemaCheck = await db
3596
- .select({
3597
- id: schemasTable.id,
3598
- name: schemasTable.name,
3599
- schemaFileId: schemasTable.schemaFileId,
3600
- })
3601
- .from(schemasTable)
3602
- .where(eq(schemasTable.id, data.schemaId))
3603
- .limit(1);
3604
- if (schemaCheck.length === 0) {
3605
- throw new Error(`Schema with id ${data.schemaId} does not exist in database. Cannot create join record.`);
3606
- }
3607
- logger$3(`Creating join record: modelId=${modelId}, schemaId=${data.schemaId} (both verified to exist)`);
3608
- await linkModelToSchema(db, modelId, data.schemaId);
3609
- // Notify React useModels so it can invalidate; live query over join often doesn't re-run when model_schemas is inserted.
3610
- // Yield so the insert is visible to the refetch that will run when the broadcast is received.
3611
- if (typeof BroadcastChannel !== 'undefined') {
3612
- await new Promise((r) => setTimeout(r, 10));
3613
- const row = schemaCheck[0];
3614
- try {
3615
- postBroadcast('seed-models-invalidate', {
3616
- schemaName: row.name ?? undefined,
3617
- schemaFileId: row.schemaFileId ?? undefined,
3618
- });
3619
- }
3620
- catch (_) { }
3621
- }
3622
- logger$3(`Successfully created join record for model ${data.modelName} (id: ${modelId}) to schema (id: ${data.schemaId})`);
3623
- }
3624
- catch (error) {
3625
- if (error?.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
3626
- logger$3(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}"`);
3627
- logger$3(`modelId: ${modelId}, schemaId: ${data.schemaId}`);
3628
- logger$3(`Error details:`, error);
3629
- // Re-throw with more context
3630
- throw new Error(`FOREIGN KEY constraint failed when creating join record for model "${data.modelName}" (modelId: ${modelId}, schemaId: ${data.schemaId}). ${error.message}`);
3631
- }
3632
- throw error;
3633
- }
3634
- }
3635
- // Write properties if provided
3636
- if (data.properties) {
3637
- for (const [propName, propData] of Object.entries(data.properties)) {
3638
- // Check if property already exists in database
3639
- const existingProps = await db
3640
- .select()
3641
- .from(properties)
3642
- .where(and(eq(properties.name, propName), eq(properties.modelId, modelId)))
3643
- .limit(1);
3644
- let propertyFileId;
3645
- if (existingProps.length > 0 && existingProps[0].schemaFileId) {
3646
- // Use existing property's schemaFileId
3647
- propertyFileId = existingProps[0].schemaFileId;
3648
- logger$3(`Using existing propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
3649
- }
3650
- else if (propData.id) {
3651
- // Use provided ID
3652
- propertyFileId = propData.id;
3653
- }
3654
- else {
3655
- // Generate random propertyFileId
3656
- // IDs should be generated in the import process before calling writeModelToDb
3657
- const { generateId } = await Promise.resolve().then(function () { return generateId$1; });
3658
- propertyFileId = generateId();
3659
- logger$3(`Generated propertyFileId "${propertyFileId}" for property "${data.modelName}:${propName}"`);
3660
- }
3661
- await writePropertyToDb(propertyFileId, {
3662
- modelId,
3663
- name: propName,
3664
- ...propData,
3665
- });
3666
- }
3667
- }
3668
- logger$3(`Wrote model ${data.modelName} (${modelFileId}) to database`);
3669
- }
3670
- /**
3671
- * Write property to database
3672
- * @param propertyFileId - The property file ID (schema_file_id)
3673
- * @param data - Property data including modelId, name, dataType, and other property fields
3674
- * @param isEdited - Optional flag indicating if property has been edited locally. Left unset, an update
3675
- * keeps the row's flag (a load or creation write must not clear an unsaved edit) and an insert stores false.
3676
- */
3677
- async function writePropertyToDb(propertyFileId, data, isEdited) {
3678
- const db = BaseDb.getAppDb();
3679
- if (!db)
3680
- throw new Error('Database not available');
3681
- // Find existing property by schemaFileId first (preferred for uniqueness)
3682
- let existingProperties = await db
3683
- .select()
3684
- .from(properties)
3685
- .where(eq(properties.schemaFileId, propertyFileId))
3686
- .limit(1);
3687
- // Fallback: find by modelId and name if not found by schemaFileId
3688
- if (existingProperties.length === 0) {
3689
- existingProperties = await db
3690
- .select()
3691
- .from(properties)
3692
- .where(and(eq(properties.name, data.name), eq(properties.modelId, data.modelId)))
3693
- .limit(1);
3694
- }
3695
- // Prepare property data
3696
- const propertyData = {
3697
- name: data.name,
3698
- modelId: data.modelId,
3699
- dataType: normalizeDataType(data.dataType),
3700
- schemaFileId: propertyFileId,
3701
- ...(isEdited !== undefined && { isEdited }),
3702
- };
3703
- // Handle ref property - create ref model if needed
3704
- // Check refModelName first, then ref (for backwards compatibility with schema files)
3705
- const refModelName = data.refModelName || data.ref;
3706
- if (refModelName) {
3707
- const refModel = await findOrCreateRefModelRecord(db, refModelName, data.modelId);
3708
- propertyData.refModelId = refModel.id;
3709
- }
3710
- else if (data.refModelId) {
3711
- propertyData.refModelId = data.refModelId;
3712
- }
3713
- else {
3714
- // If it's not a Relation type, ensure refModelId is null
3715
- propertyData.refModelId = null;
3716
- }
3717
- if (data.refValueType) {
3718
- propertyData.refValueType = normalizeDataType(data.refValueType);
3719
- }
3720
- else {
3721
- // If refValueType is not set, ensure it's null
3722
- propertyData.refValueType = null;
3723
- }
3724
- // Only overwrite `required` when the caller knows it. The creation write's payload can lack it, and
3725
- // defaulting to false here cleared the flag the schema import set (required relations became optional).
3726
- if (data.required !== undefined) {
3727
- propertyData.required = data.required;
3728
- }
3729
- // Storage settings, like `required`, only when the caller has them: the creation write's payload
3730
- // can lack them, and writing nulls would clear what the schema import stored.
3731
- const storage = propertyStorageColumns(data);
3732
- if (hasStorageSettings(storage)) {
3733
- Object.assign(propertyData, storage);
3734
- }
3735
- if (existingProperties.length > 0) {
3736
- // Property exists, update it with new values.
3737
- // Use existing row id for WHERE (not name+modelId) so renames work: the row
3738
- // may have the old name but we're updating to the new one.
3739
- const existing = existingProperties[0];
3740
- await db
3741
- .update(properties)
3742
- .set(propertyData)
3743
- .where(eq(properties.id, existing.id));
3744
- logger$3(`Updated property ${data.name} (${propertyFileId}) in database`);
3745
- }
3746
- else {
3747
- // Property doesn't exist, create it
3748
- // Double-check before insert to avoid race conditions
3749
- const doubleCheck = await db
3750
- .select()
3751
- .from(properties)
3752
- .where(eq(properties.schemaFileId, propertyFileId))
3753
- .limit(1);
3754
- if (doubleCheck.length > 0) {
3755
- // Another process created it, update it instead
3756
- await db
3757
- .update(properties)
3758
- .set(propertyData)
3759
- .where(eq(properties.id, doubleCheck[0].id));
3760
- logger$3(`Property with schemaFileId "${propertyFileId}" was created by another process, updated existing record`);
3761
- }
3762
- else {
3763
- // DO NOTHING on conflict: addModelsToDb writes the same row during schema import, and a failed
3764
- // insert is logged by sqlite-wasm even when caught.
3765
- const inserted = await db
3766
- .insert(properties)
3767
- .values({ required: false, isEdited: false, ...propertyData })
3768
- .onConflictDoNothing()
3769
- .returning({ id: properties.id });
3770
- if (inserted.length > 0) {
3771
- logger$3(`Created property ${data.name} (${propertyFileId}) in database`);
3772
- }
3773
- else {
3774
- const existing = await db
3775
- .select()
3776
- .from(properties)
3777
- .where(eq(properties.schemaFileId, propertyFileId))
3778
- .limit(1);
3779
- if (existing.length === 0) {
3780
- throw new Error(`Failed to create or find property "${data.name}" with schemaFileId "${propertyFileId}"`);
3781
- }
3782
- await db
3783
- .update(properties)
3784
- .set(propertyData)
3785
- .where(eq(properties.id, existing[0].id));
3786
- logger$3(`Found existing property with schemaFileId "${propertyFileId}" (id: ${existing[0].id}) after insert conflict, updated it`);
3787
- }
3788
- }
3789
- }
3790
- }
3791
- /**
3792
- * Get model database ID from model name or modelFileId
3793
- * @param modelNameOrFileId - Model name (string) or modelFileId (string)
3794
- * @param schemaNameOrId - Optional schema name or ID to narrow search
3795
- * @returns Model database ID
3796
- * @throws Error if model not found
3797
- */
3798
- async function getModelId(modelNameOrFileId, schemaNameOrId) {
3799
- const db = BaseDb.getAppDb();
3800
- if (!db) {
3801
- throw new Error('Database not available');
3802
- }
3803
- const modelSchemaMod = await Promise.resolve().then(function () { return ModelSchema; });
3804
- const { models: modelsTable } = modelSchemaMod;
3805
- const drizzleMod = await import('drizzle-orm');
3806
- const { eq, and } = drizzleMod;
3807
- // Try to find by modelFileId first (more reliable)
3808
- let records = await db
3809
- .select()
3810
- .from(modelsTable)
3811
- .where(eq(modelsTable.schemaFileId, modelNameOrFileId))
3812
- .limit(1);
3813
- // If not found by modelFileId, try by name
3814
- if (records.length === 0) {
3815
- // If schema is provided, narrow the search
3816
- if (schemaNameOrId !== undefined) {
3817
- const modelSchemaSchemaMod = await Promise.resolve().then(function () { return ModelSchemaSchema; });
3818
- const { modelSchemas } = modelSchemaSchemaMod;
3819
- const schemaSchemaMod = await Promise.resolve().then(function () { return SchemaSchema; });
3820
- const { schemas: schemasTable } = schemaSchemaMod;
3821
- if (typeof schemaNameOrId === 'number') {
3822
- // schemaNameOrId is schemaId
3823
- records = await db
3824
- .select({ id: modelsTable.id })
3825
- .from(modelsTable)
3826
- .innerJoin(modelSchemas, eq(modelsTable.id, modelSchemas.modelId))
3827
- .where(and(eq(modelsTable.name, modelNameOrFileId), eq(modelSchemas.schemaId, schemaNameOrId)))
3828
- .limit(1);
3829
- }
3830
- else {
3831
- // schemaNameOrId is schemaName
3832
- records = await db
3833
- .select({ id: modelsTable.id })
3834
- .from(modelsTable)
3835
- .innerJoin(modelSchemas, eq(modelsTable.id, modelSchemas.modelId))
3836
- .innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
3837
- .where(and(eq(modelsTable.name, modelNameOrFileId), eq(schemasTable.name, schemaNameOrId)))
3838
- .limit(1);
3839
- }
3840
- }
3841
- else {
3842
- // No schema filter: name-only (newest row, warns when the name is ambiguous)
3843
- const byName = await resolveModelRecord(modelNameOrFileId, {}, db);
3844
- records = byName ? [byName] : [];
3845
- }
3846
- }
3847
- if (records.length === 0) {
3848
- const schemaInfo = schemaNameOrId ? ` in schema "${schemaNameOrId}"` : '';
3849
- throw new Error(`Model "${modelNameOrFileId}"${schemaInfo} not found in database`);
3850
- }
3851
- return records[0].id;
3852
- }
3853
- /**
3854
- * Get model database ID from modelFileId
3855
- * @param modelFileId - The model file ID (schema_file_id)
3856
- * @returns Model database ID
3857
- * @throws Error if model not found
3858
- */
3859
- async function getModelIdByFileId(modelFileId) {
3860
- const db = BaseDb.getAppDb();
3861
- if (!db) {
3862
- throw new Error('Database not available');
3863
- }
3864
- const modelSchemaMod = await Promise.resolve().then(function () { return ModelSchema; });
3865
- const { models: modelsTable } = modelSchemaMod;
3866
- const drizzleMod = await import('drizzle-orm');
3867
- const { eq } = drizzleMod;
3868
- const records = await db
3869
- .select()
3870
- .from(modelsTable)
3871
- .where(eq(modelsTable.schemaFileId, modelFileId))
3872
- .limit(1);
3873
- if (records.length === 0) {
3874
- throw new Error(`Model with file ID "${modelFileId}" not found in database`);
3875
- }
3876
- return records[0].id;
3877
- }
3878
-
3879
- var db = /*#__PURE__*/Object.freeze({
3880
- __proto__: null,
3881
- addModelsToDb: addModelsToDb,
3882
- addSchemaToDb: addSchemaToDb,
3883
- getAddressesForItemsFilter: getAddressesForItemsFilter,
3884
- getAllAddressesFromDb: getAllAddressesFromDb,
3885
- getItemStoragePropertiesForModel: getItemStoragePropertiesForModel,
3886
- getModelId: getModelId,
3887
- getModelIdByFileId: getModelIdByFileId,
3888
- getModelNameByModelId: getModelNameByModelId,
3889
- getOwnedAddressesFromDb: getOwnedAddressesFromDb,
3890
- getPropertyIdForModelAndName: getPropertyIdForModelAndName,
3891
- getPropertyIdForSchemaFileId: getPropertyIdForSchemaFileId,
3892
- getPropertyModelNameAndDataType: getPropertyModelNameAndDataType,
3893
- getWatchedAddressesFromDb: getWatchedAddressesFromDb,
3894
- loadModelsFromDbForSchema: loadModelsFromDbForSchema,
3895
- mergeDbModelsIntoSchemaModels: mergeDbModelsIntoSchemaModels,
3896
- migrateMetadataForPropertyRename: migrateMetadataForPropertyRename,
3897
- moveMetadataToRenamedProperty: moveMetadataToRenamedProperty,
3898
- propertyStorageColumns: propertyStorageColumns,
3899
- renameModelInDb: renameModelInDb,
3900
- savePropertyToDb: savePropertyToDb,
3901
- writeModelToDb: writeModelToDb,
3902
- writePropertyToDb: writePropertyToDb
3903
- });
3904
-
3905
- const isNode = () => {
3906
- return (typeof process !== 'undefined' &&
3907
- process.versions != null &&
3908
- process.versions.node != null);
3909
- };
3910
- const isBrowser = () => {
3911
- return !isElectron() && typeof document !== 'undefined' && typeof window !== 'undefined';
3912
- };
3913
- const isElectron = () => {
3914
- return typeof process !== 'undefined' && process.versions != null && process.versions.electron != null;
3915
- };
3916
- /**
3917
- * Detects if code is running in an Electron renderer process.
3918
- *
3919
- * Uses multiple detection methods for robustness:
3920
- * 1. Checks process.type === 'renderer' (most reliable)
3921
- * 2. Falls back to checking if Electron is present and window/document exist
3922
- *
3923
- * @returns true if running in Electron renderer process, false otherwise
3924
- */
3925
- const isElectronRenderer = () => {
3926
- // Method 1: Check process.type (most reliable - official Electron API)
3927
- if (typeof process !== 'undefined' &&
3928
- process.type === 'renderer') {
3929
- return true;
3930
- }
3931
- // Method 2: Fallback - Electron + browser-like environment
3932
- // Renderer processes have window/document, main process doesn't (in modern Electron)
3933
- if (isElectron() &&
3934
- typeof window !== 'undefined' &&
3935
- typeof document !== 'undefined') {
3936
- return true;
3937
- }
3938
- return false;
3939
- };
3940
- /**
3941
- * True where OPFS-backed Arweave file downloads should run: normal browsers and
3942
- * Electron renderer. {@link isBrowser} is false for every Electron build (including renderer).
3943
- */
3944
- const supportsOpfsFileDownloads = () => {
3945
- return isBrowser() || isElectronRenderer();
3946
- };
3947
- /**
3948
- * NODE_ENV is 'test' or IS_SEED_DEV is set. Bundlers compile `process.env.NODE_ENV` in, but
3949
- * not IS_SEED_DEV, so that is only read where a `process` global exists: browsers have none.
3950
- */
3951
- const isTestOrSeedDev = () => process.env.NODE_ENV === 'test' || (typeof process !== 'undefined' && !!process.env?.IS_SEED_DEV);
3952
-
3953
- /**
3954
- * Tag every Seed upload carries with the hex digest of its data. Producers: the SDK's
3955
- * `getPublishUploadData` / `getPublishUploads` (and through them both the L1 transaction path and
3956
- * the bundler / DataItem path in `@seedprotocol/publish`). Consumer: the files download
3957
- * (`events/files/download.ts` and the download worker), which verifies downloaded bytes against it.
3958
- */
3959
- const CONTENT_SHA256_TAG = 'Content-SHA-256';
3960
- const CONTENT_TYPE_TAG = 'Content-Type';
3961
- /**
3962
- * Lowercase hex SHA-256 of `data`: the value of the {@link CONTENT_SHA256_TAG} tag.
3963
- *
3964
- * Uses @noble/hashes rather than WebCrypto so it also works outside secure contexts (WebCrypto's
3965
- * `crypto.subtle` is missing on plain-http origins); the digest is the same.
3966
- *
3967
- * In SDK 0.7.2 and earlier the L1 transaction path (`getPublishUploads`) wrote SHA3-256 under this tag name
3968
- * while the bundler path wrote SHA-256. Uploads from that path still carry SHA3-256 digests; use
3969
- * {@link contentSha256TagMatches} to check a tag value against bytes.
3970
- */
3971
- const getContentSha256 = (data) => bytesToHex(sha256(data));
3972
- /**
3973
- * True when `tagValue` (a {@link CONTENT_SHA256_TAG} value read from Arweave) is the digest of
3974
- * `data`. Accepts SHA-256 and, for uploads made by SDK 0.7.2 and earlier through the L1 transaction path,
3975
- * SHA3-256.
3976
- */
3977
- const contentSha256TagMatches = (data, tagValue) => {
3978
- const expected = tagValue.trim().toLowerCase();
3979
- if (!expected)
3980
- return false;
3981
- return getContentSha256(data) === expected || bytesToHex(sha3_256(data)) === expected;
3982
- };
3983
- /**
3984
- * Tags for one upload, in the order every upload uses: content hash, content type, then any
3985
- * configured extra tags (e.g. App-Name from `arweaveUploadTags`).
3986
- */
3987
- const buildArweaveContentTags = (contentHash, contentType, extra) => {
3988
- const tags = [];
3989
- if (contentHash)
3990
- tags.push({ name: CONTENT_SHA256_TAG, value: contentHash });
3991
- if (contentType)
3992
- tags.push({ name: CONTENT_TYPE_TAG, value: contentType });
3993
- if (extra?.length)
3994
- tags.push(...extra);
3995
- return tags;
3996
- };
3997
-
3998
- /** Subdirectory of the files root that holds a storage seed's file, by its data type. */
3999
- const getStorageSubdirForDataType = (dataType) => {
4000
- switch (normalizeDataType(dataType ?? undefined)) {
4001
- case 'Image':
4002
- return 'images';
4003
- case 'Html':
4004
- return 'html';
4005
- case 'Json':
4006
- return 'json';
4007
- case 'File':
4008
- default:
4009
- // Unknown types are generic files; `images/` is only for what the SDK knows is an image.
4010
- return 'files';
4011
- }
4012
- };
4013
- /** Absolute directory for a storage seed's file (`images/`, `files/`, `html/` or `json/` under the files root). */
4014
- const getStorageDirForDataType = (dataType) => BaseFileManager.getFilesPath(getStorageSubdirForDataType(dataType));
4015
- /**
4016
- * Subdirectory (relative to the files root, no leading slash) for an ItemStorage property's file:
4017
- * its model's `storage.path` (`localStorageDir`), else `files`.
4018
- */
4019
- const getItemStorageSubdir = (localStorageDir) => (localStorageDir ?? '').replace(/^\/+/, '').replace(/\/+$/, '') || 'files';
4020
- /**
4021
- * File name a local save gives an ItemStorage property: `<seedUid or seedLocalId><extension>`
4022
- * (see saveItemStorage). Downloaded content is named after its transaction instead
4023
- * (`<txId><extension>`, see syncDbWithEas), which is what the metadata row's refResolvedValue says.
4024
- */
4025
- const getItemStorageLocalFileName = (seedId, filenameSuffix) => `${seedId}${filenameSuffix ?? ''}`;
4026
-
4027
- /** Rows per query: well under SQLite's bound-parameter limit. */
4028
- const CHUNK_SIZE = 400;
4029
- /**
4030
- * Where the download worker saves each child of a composite upload, by transaction id then child
4031
- * (property) name: a path relative to the files root.
4032
- *
4033
- * The paths come from the ItemStorage rows sync derives from a `storage_transaction_id`
4034
- * attestation (see `createMetadataRecordsForStorageTransactionId`): property value = the
4035
- * transaction id, `refResolvedValue` = `<txId><extension>`, `localStorageDir` = the model's storage
4036
- * directory. Those rows are what hydration reads, so a child saved here is found as that property's
4037
- * file. Transactions with no such rows are left out; the worker then falls back to the legacy
4038
- * html / json layout.
4039
- */
4040
- const getCompositeChildPaths = async (transactionIds) => {
4041
- const out = {};
4042
- const appDb = BaseDb.getAppDb();
4043
- if (!appDb || transactionIds.length === 0)
4044
- return out;
4045
- for (let i = 0; i < transactionIds.length; i += CHUNK_SIZE) {
4046
- const chunk = transactionIds.slice(i, i + CHUNK_SIZE);
4047
- const rows = await appDb
4048
- .select({
4049
- transactionId: metadata.propertyValue,
4050
- propertyName: metadata.propertyName,
4051
- refResolvedValue: metadata.refResolvedValue,
4052
- localStorageDir: metadata.localStorageDir,
4053
- })
4054
- .from(metadata)
4055
- .where(and(isNotNull(metadata.derivedFromUid), isNotNull(metadata.refResolvedValue), inArray(metadata.propertyValue, chunk)));
4056
- for (const row of rows) {
4057
- const { transactionId, propertyName, refResolvedValue } = row;
4058
- if (!transactionId || !propertyName || !refResolvedValue)
4059
- continue;
4060
- // A file name, never a path: anything else is not where the SDK would look.
4061
- if (refResolvedValue.includes('/') || refResolvedValue.includes('\\'))
4062
- continue;
4063
- const byName = (out[transactionId] ?? (out[transactionId] = {}));
4064
- byName[propertyName] ?? (byName[propertyName] = `${getItemStorageSubdir(row.localStorageDir)}/${refResolvedValue}`);
4065
- }
4066
- }
4067
- return out;
4068
- };
4069
-
4070
- const logger$2 = debug('seedSdk:files:download');
4071
- const AUTO_BULK_DOWNLOAD_THROTTLE_MS = 10000;
4072
- /** Avoid refetch-on-every-call: transient empty indexer responses were overwriting good cache (see trailing throttle ~10s after success). */
4073
- const GET_FILES_METADATA_STALE_MS = 120000;
4074
- let bulkDownloadInFlight = null;
4075
- const lazyDownloadInFlightByTransactionId = new Map();
4076
- const getAppDbReady = async () => {
4077
- if (!BaseDb.isAppDbReady()) {
4078
- // Wait for ClientManager to be ready (DB_INIT state or later)
4079
- const _mod_0 = await import('./ClientManager-GZfXErDX.js');
4080
- const _ns_0 = _mod_0.W;
4081
- const clientManagerMod = _ns_0;
4082
- const { getClient } = clientManagerMod;
4083
- const clientManager = getClient();
4084
- const clientService = clientManager.getService();
4085
- await waitFor(clientService, (snapshot) => {
4086
- const state = snapshot.value;
4087
- return state === 'dbInit' ||
4088
- state === 'saveConfig' ||
4089
- state === 'processSchemaFiles' ||
4090
- state === 'addModelsToStore' ||
4091
- state === 'addModelsToDb' ||
4092
- state === 'idle';
4093
- }, { timeout: 30000 });
4094
- }
4095
- return BaseDb.getAppDb();
4096
- };
4097
- const getExcludedTransactions = async () => {
4098
- const appDb = await getAppDbReady();
4099
- if (!appDb) {
4100
- return new Set();
4101
- }
4102
- const excludedTransactionsQuery = await appDb
4103
- .select()
4104
- .from(appState)
4105
- .where(eq(appState.key, 'excludedTransactions'));
4106
- const excludedTransactions = new Set();
4107
- if (excludedTransactionsQuery && excludedTransactionsQuery.length === 1) {
4108
- const valueString = excludedTransactionsQuery[0].value;
4109
- if (valueString) {
4110
- const excludedTransactionsArray = JSON.parse(valueString);
4111
- for (const txId of excludedTransactionsArray) {
4112
- excludedTransactions.add(txId);
4113
- }
4114
- }
4115
- }
4116
- return excludedTransactions;
4117
- };
4118
- /** Arweave transaction ids are 43 chars in the URL-safe base64 alphabet. */
4119
- const ARWEAVE_TX_ID_RE$1 = /^[a-z0-9_-]{43}$/i;
4120
- const normalizeArweaveTxId = (raw) => {
4121
- if (raw == null || raw === '')
4122
- return undefined;
4123
- const t = String(raw).trim();
4124
- if (ARWEAVE_TX_ID_RE$1.test(t))
4125
- return t;
4126
- const m = t.match(/^([a-z0-9_-]{43})(?:\.[^/]+)?$/i);
4127
- if (m)
4128
- return m[1];
4129
- // Values may be full gateway URLs or paths; scan for an embedded tx id. It must be 43 characters
4130
- // on its own, not the start of a longer run: the first 43 of a bytes32 value (`0x0000…0020`,
4131
- // an EAS UID) are in the alphabet too.
4132
- const embedded = t.match(/(?<![a-z0-9_-])[a-z0-9_-]{43}(?![a-z0-9_-])/gi);
4133
- return embedded?.[0];
4134
- };
4135
- const mergeAddressLists = (a, b) => {
4136
- const seen = new Set();
4137
- const out = [];
4138
- for (const addr of [...(a ?? []), ...(b ?? [])]) {
4139
- if (!addr || !addr.trim())
4140
- continue;
4141
- const k = addr.toLowerCase();
4142
- if (seen.has(k))
4143
- continue;
4144
- seen.add(k);
4145
- out.push(addr);
4146
- }
4147
- return out;
4148
- };
4149
- const collectTransactionIdsFromLocalMetadata = async () => {
4150
- const appDb = await getAppDbReady();
4151
- if (!appDb) {
4152
- return [];
4153
- }
4154
- const nameOr = or(eq(metadata.propertyName, 'storageTransactionId'), eq(metadata.propertyName, 'storage_transaction_id'), eq(metadata.propertyName, 'transactionId'));
4155
- const propRows = await appDb
4156
- .select({ propertyValue: metadata.propertyValue })
4157
- .from(metadata)
4158
- .where(and(nameOr, isNotNull(metadata.propertyValue)));
4159
- const refRows = await appDb
4160
- .select({ refResolvedValue: metadata.refResolvedValue })
4161
- .from(metadata)
4162
- .where(isNotNull(metadata.refResolvedValue));
4163
- const ids = new Set();
4164
- for (const row of propRows) {
4165
- const id = normalizeArweaveTxId(row.propertyValue ?? undefined);
4166
- if (id)
4167
- ids.add(id);
4168
- }
4169
- for (const row of refRows) {
4170
- const id = normalizeArweaveTxId(row.refResolvedValue ?? undefined);
4171
- if (id)
4172
- ids.add(id);
4173
- }
4174
- return Array.from(ids);
4175
- };
4176
- const parseTransactionIdsFromMetadata = (filesMetadata, excludedTransactions) => {
4177
- const transactionIds = [];
4178
- for (const fileMetadata of filesMetadata) {
4179
- // Validate and parse decodedDataJson
4180
- if (!fileMetadata.decodedDataJson || fileMetadata.decodedDataJson.trim() === '') {
4181
- console.warn('[events/files] [download] empty decodedDataJson for fileMetadata: ', fileMetadata.id);
4182
- continue;
4183
- }
4184
- let json;
4185
- try {
4186
- json = JSON.parse(fileMetadata.decodedDataJson);
4187
- }
4188
- catch (error) {
4189
- console.warn('[events/files] [download] failed to parse decodedDataJson for fileMetadata: ', fileMetadata.id, error);
4190
- continue;
4191
- }
4192
- if (!Array.isArray(json) || json.length === 0 || !json[0]?.value?.value) {
4193
- console.warn('[events/files] [download] invalid decodedDataJson structure for fileMetadata: ', fileMetadata.id);
4194
- continue;
4195
- }
4196
- const rawTx = json[0].value.value;
4197
- if (typeof rawTx !== 'string' || rawTx.trim().length === 0) {
4198
- continue;
4199
- }
4200
- const transactionId = normalizeArweaveTxId(rawTx.trim());
4201
- if (!transactionId) {
4202
- continue;
4203
- }
4204
- if (excludedTransactions.has(transactionId) || excludedTransactions.has(rawTx.trim())) {
4205
- continue;
4206
- }
4207
- transactionIds.push(transactionId);
4208
- }
4209
- return transactionIds;
4210
- };
4211
- /** Content-SHA-256 tag values for the transactions that have one, from one batched tag lookup (cached). */
4212
- const getExpectedContentHashes = async (transactionIds) => {
4213
- const queryClient = BaseQueryClient.getQueryClient();
4214
- const tagsById = new Map();
4215
- const toFetch = [];
4216
- for (const transactionId of transactionIds) {
4217
- // Tags never change once a transaction is indexed, so a cached lookup stays good.
4218
- const cached = queryClient.getQueryData?.(['getTransactionTags', transactionId]);
4219
- if (cached)
4220
- tagsById.set(transactionId, cached);
4221
- else
4222
- toFetch.push(transactionId);
4223
- }
4224
- if (toFetch.length > 0) {
4225
- try {
4226
- const fetched = await getTransactionTagsByIds(toFetch, {
4227
- client: BaseArweaveClient.getArweaveClient(),
4228
- onError: (error) => logger$2('[download] transaction tags lookup failed', error),
4229
- });
4230
- for (const [transactionId, tags] of fetched) {
4231
- tagsById.set(transactionId, tags);
4232
- // Seed the per-transaction cache entry (the client interface has no setQueryData).
4233
- void queryClient
4234
- .fetchQuery({ queryKey: ['getTransactionTags', transactionId], queryFn: async () => tags })
4235
- .catch(() => { });
4236
- }
4237
- }
4238
- catch (error) {
4239
- logger$2('[download] transaction tags lookup failed', error);
4240
- }
4241
- }
4242
- const out = {};
4243
- for (const [transactionId, tags] of tagsById) {
4244
- const value = tags.find((tag) => tag.name === CONTENT_SHA256_TAG)?.value?.trim();
4245
- if (value)
4246
- out[transactionId] = value;
4247
- }
4248
- return out;
4249
- };
4250
- const downloadTransactionIds = async (rawTransactionIds, options) => {
4251
- if (!supportsOpfsFileDownloads()) {
4252
- return false;
4253
- }
4254
- const transactionIds = Array.from(new Set(rawTransactionIds.filter((tx) => !!tx && tx.trim().length > 0)));
4255
- if (transactionIds.length === 0) {
4256
- return false;
4257
- }
4258
- const excludedTransactions = await getExcludedTransactions();
4259
- await ensureReadGatewaySelected();
4260
- // Browser: do not call getTransactionStatus() — many gateways (e.g. g8way.io) redirect or omit
4261
- // CORS headers, so fetch() fails and we would wrongly exclude txs. The OPFS worker fetch decides success.
4262
- const transactionIdsToDownload = transactionIds.filter((id) => !excludedTransactions.has(id));
4263
- if (transactionIdsToDownload.length === 0) {
4264
- return false;
4265
- }
4266
- // One batched lookup for every upload's Content-SHA-256 tag; downloads are verified against it.
4267
- // Missing tags (old uploads, or a just-published one not indexed yet) just mean no verification.
4268
- const expectedContentHashes = await getExpectedContentHashes(transactionIdsToDownload);
4269
- // Preferred gateways first: a just-published item is only on the gateway it was uploaded through.
4270
- const arweaveBaseUrls = getArweaveReadBaseUrls();
4271
- // Where each ItemStorage child of a composite goes (the worker cannot read the DB or the schema).
4272
- const compositeChildPaths = await getCompositeChildPaths(transactionIdsToDownload).catch((error) => {
4273
- logger$2('[download] composite child paths lookup failed', error);
4274
- return {};
4275
- });
4276
- let result;
4277
- if (transactionIdsToDownload.length === 1) {
4278
- const transactionId = transactionIdsToDownload[0];
4279
- result = await BaseFileManager.downloadFileByTransactionId({
4280
- transactionId,
4281
- arweaveHost: BaseArweaveClient.getBaseUrl(),
4282
- arweaveBaseUrls,
4283
- excludedTransactions,
4284
- expectedContentHash: expectedContentHashes[transactionId],
4285
- compositeChildPaths: compositeChildPaths[transactionId],
4286
- skipExisting: options?.skipExisting,
4287
- });
4288
- }
4289
- else {
4290
- result = await BaseFileManager.downloadAllFiles({
4291
- transactionIds: transactionIdsToDownload,
4292
- arweaveHost: BaseArweaveClient.getBaseUrl(),
4293
- arweaveBaseUrls,
4294
- excludedTransactions,
4295
- expectedContentHashes,
4296
- compositeChildPaths,
4297
- skipExisting: options?.skipExisting,
4298
- });
4299
- }
4300
- if (result) {
4301
- for (const transactionId of result.hashMismatchTransactionIds) {
4302
- // Not saved by the worker; left for a later run (a gateway may serve the right bytes then).
4303
- logger$2(`[download] ${transactionId}: downloaded bytes do not match Content-SHA-256; skipped`);
4304
- }
4305
- }
4306
- return true;
4307
- };
4308
- const runBulkDownloadWithDedupe = async (transactionIds) => {
4309
- if (bulkDownloadInFlight) {
4310
- return bulkDownloadInFlight;
4311
- }
4312
- bulkDownloadInFlight = (async () => {
4313
- try {
4314
- await downloadTransactionIds(transactionIds, { skipExisting: true });
4315
- }
4316
- finally {
4317
- bulkDownloadInFlight = null;
4318
- }
4319
- })();
4320
- return bulkDownloadInFlight;
4321
- };
4322
- const downloadAllFilesBinaryRequestHandler = async (addressesHint) => {
4323
- if (!supportsOpfsFileDownloads()) {
4324
- return;
4325
- }
4326
- let fromDb = [];
4327
- if (BaseDb.isAppDbReady()) {
4328
- fromDb = await getAllAddressesFromDb();
4329
- }
4330
- if (!BaseDb.isAppDbReady()) {
4331
- await getAppDbReady();
4332
- fromDb = await getAllAddressesFromDb();
4333
- }
4334
- const addresses = mergeAddressLists(addressesHint, fromDb);
4335
- if (!addresses || addresses.length === 0) {
4336
- return;
4337
- }
4338
- const queryClient = BaseQueryClient.getQueryClient();
4339
- const easClient = BaseEasClient.getEasClient();
4340
- const filesMetadataQueryKey = ['getFilesMetadata', ...addresses];
4341
- const fetchFilesMetadata = () => easClient.request(GET_FILES_METADATA, {
4342
- where: {
4343
- attester: {
4344
- in: addresses,
4345
- },
4346
- schema: {
4347
- is: {
4348
- id: {
4349
- equals: '0x55fdefb36fcbbaebeb7d6b41dc3a1a9666e4e42154267c889de064faa7ede517',
4350
- },
4351
- },
4352
- },
4353
- },
4354
- });
4355
- let { filesMetadata } = await queryClient.fetchQuery({
4356
- queryKey: filesMetadataQueryKey,
4357
- queryFn: fetchFilesMetadata,
4358
- staleTime: GET_FILES_METADATA_STALE_MS,
4359
- });
4360
- if ((!Array.isArray(filesMetadata) || filesMetadata.length === 0) && addresses.length > 0) {
4361
- await queryClient.removeQueries({ queryKey: filesMetadataQueryKey });
4362
- ({ filesMetadata } = await queryClient.fetchQuery({
4363
- queryKey: filesMetadataQueryKey,
4364
- queryFn: fetchFilesMetadata,
4365
- staleTime: GET_FILES_METADATA_STALE_MS,
4366
- }));
4367
- }
4368
- const filesRoot = BaseFileManager.getWorkingDir();
4369
- await BaseFileManager.createDirIfNotExists(filesRoot);
4370
- await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('html'));
4371
- await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('json'));
4372
- await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('images'));
4373
- await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('files'));
4374
- const excludedTransactions = await getExcludedTransactions();
4375
- const fromEas = parseTransactionIdsFromMetadata(filesMetadata ?? [], excludedTransactions);
4376
- const fromLocal = await collectTransactionIdsFromLocalMetadata();
4377
- const transactionIds = Array.from(new Set([...fromEas, ...fromLocal]));
4378
- await runBulkDownloadWithDedupe(transactionIds);
4379
- };
4380
- const downloadTransactionIdWithDedupe = async (transactionId) => {
4381
- if (!supportsOpfsFileDownloads() || !transactionId || transactionId.trim().length === 0) {
4382
- return false;
4383
- }
4384
- const normalizedTxId = normalizeArweaveTxId(transactionId);
4385
- if (!normalizedTxId) {
4386
- logger$2(`[download] not an Arweave transaction id: ${transactionId}`);
4387
- return false;
4388
- }
4389
- const existing = lazyDownloadInFlightByTransactionId.get(normalizedTxId);
4390
- if (existing) {
4391
- return existing;
4392
- }
4393
- const inFlight = (async () => {
4394
- try {
4395
- // No skipExisting: the caller found no local file, so a file on disk it could not see is
4396
- // written again and announced.
4397
- const ok = await downloadTransactionIds([normalizedTxId]);
4398
- return ok;
4399
- }
4400
- finally {
4401
- lazyDownloadInFlightByTransactionId.delete(normalizedTxId);
4402
- }
4403
- })();
4404
- lazyDownloadInFlightByTransactionId.set(normalizedTxId, inFlight);
4405
- return inFlight;
4406
- };
4407
- const scheduleBulkFilesDownloadFromEasSyncThrottled = throttle((addressesHint) => {
4408
- void downloadAllFilesBinaryRequestHandler(addressesHint).catch((error) => {
4409
- logger$2('[scheduleBulkFilesDownloadFromEasSync] failed', error);
4410
- });
4411
- }, AUTO_BULK_DOWNLOAD_THROTTLE_MS, { leading: true, trailing: false });
4412
- const scheduleBulkFilesDownloadFromEasSync = (addressesHint) => {
4413
- if (!supportsOpfsFileDownloads()) {
4414
- return;
4415
- }
4416
- scheduleBulkFilesDownloadFromEasSyncThrottled(addressesHint);
4417
- };
4418
-
4419
- var download = /*#__PURE__*/Object.freeze({
4420
- __proto__: null,
4421
- downloadAllFilesBinaryRequestHandler: downloadAllFilesBinaryRequestHandler,
4422
- downloadTransactionIdWithDedupe: downloadTransactionIdWithDedupe,
4423
- scheduleBulkFilesDownloadFromEasSync: scheduleBulkFilesDownloadFromEasSync
4424
- });
4425
-
4426
- const logger$1 = debug('seedSdk:helpers:ensureImageLocal');
4427
- /** Arweave transaction ids are 43 chars in the URL-safe base64 alphabet. */
4428
- const ARWEAVE_TX_ID_RE = /^[a-z0-9_-]{43}$/i;
4429
- const IMAGE_SIZES = [
4430
- ImageSize.EXTRA_SMALL,
4431
- ImageSize.SMALL,
4432
- ImageSize.MEDIUM,
4433
- ImageSize.LARGE,
4434
- ImageSize.EXTRA_LARGE,
4435
- ];
4436
- const inFlightByKey = new Map();
4437
- const normalizeArweaveTxIdForEnsure = (raw) => {
4438
- if (raw == null || raw === '')
4439
- return undefined;
4440
- const t = String(raw).trim();
4441
- if (ARWEAVE_TX_ID_RE.test(t))
4442
- return t;
4443
- const m = t.match(/^([a-z0-9_-]{43})(?:\.[^/]+)?$/i);
4444
- if (m)
4445
- return m[1];
4446
- const embedded = t.match(/[a-z0-9_-]{43}/gi);
4447
- if (embedded) {
4448
- for (const candidate of embedded) {
4449
- if (ARWEAVE_TX_ID_RE.test(candidate))
4450
- return candidate;
4451
- }
4452
- }
4453
- return undefined;
4454
- };
4455
- const closestImageSize = (width) => {
4456
- let best = ImageSize.EXTRA_SMALL;
4457
- let bestDist = Math.abs(best - width);
4458
- for (const size of IMAGE_SIZES) {
4459
- const dist = Math.abs(size - width);
4460
- if (dist < bestDist) {
4461
- best = size;
4462
- bestDist = dist;
4463
- }
4464
- }
4465
- return best;
4466
- };
4467
- const mapRequestedWidthsToImageSizes = (widths) => {
4468
- const requested = widths && widths.length > 0 ? widths : [ImageSize.EXTRA_SMALL];
4469
- const unique = new Set();
4470
- for (const w of requested) {
4471
- if (typeof w !== 'number' || !Number.isFinite(w) || w <= 0)
4472
- continue;
4473
- unique.add(closestImageSize(w));
4474
- }
4475
- if (unique.size === 0) {
4476
- unique.add(ImageSize.EXTRA_SMALL);
4477
- }
4478
- return Array.from(unique).sort((a, b) => a - b);
4479
- };
4480
- const getBasename = (filePath) => {
4481
- const path = BaseFileManager.getPathModule();
4482
- return path.basename(filePath);
4483
- };
4484
- const getBasenameWithoutExtension = (fileName) => {
4485
- const match = fileName.match(/^(.*[\/\\])?([^\/\\]+?)(\.[^.\/\\]*)?$/);
4486
- return match?.[2] ?? fileName;
4487
- };
4488
- const resolveMatchingImagePath = async (needle) => {
4489
- const dirPath = BaseFileManager.getFilesPath('images');
4490
- const dirExists = await BaseFileManager.pathExists(dirPath);
4491
- if (!dirExists)
4492
- return undefined;
4493
- const fs = await BaseFileManager.getFs();
4494
- const path = BaseFileManager.getPathModule();
4495
- const files = await fs.promises.readdir(dirPath);
4496
- // Prefer exact basename match (files only); skip size dirs.
4497
- const exact = files.find((file) => path.basename(file) === needle);
4498
- if (exact) {
4499
- const candidate = BaseFileManager.getFilesPath('images', exact);
4500
- try {
4501
- const zenfs = await BaseFileManager.getFs();
4502
- const stat = await zenfs.promises.stat(candidate);
4503
- if (stat.isFile())
4504
- return candidate;
4505
- }
4506
- catch {
4507
- // fall through
4508
- }
4509
- }
4510
- const matchingFiles = files.filter((file) => {
4511
- const base = path.basename(file);
4512
- return base.includes(needle);
4513
- });
4514
- for (const file of matchingFiles) {
4515
- const candidate = BaseFileManager.getFilesPath('images', file);
4516
- try {
4517
- const zenfs = await BaseFileManager.getFs();
4518
- const stat = await zenfs.promises.stat(candidate);
4519
- if (stat.isFile())
4520
- return candidate;
4521
- }
4522
- catch {
4523
- continue;
4524
- }
4525
- }
4526
- return undefined;
4527
- };
4528
- const sizedVariantPath = (originalFilePath, size) => {
4529
- const path = BaseFileManager.getPathModule();
4530
- const dir = path.dirname(originalFilePath);
4531
- const base = getBasenameWithoutExtension(getBasename(originalFilePath));
4532
- return path.join(dir, String(size), `${base}.webp`);
4533
- };
4534
- const ensureKey = (txId, fileName, sizes) => {
4535
- const idPart = txId ?? fileName ?? '';
4536
- return `${idPart}|${sizes.join(',')}`;
4537
- };
4538
- const runEnsureImageLocal = async (params) => {
4539
- if (!supportsOpfsFileDownloads()) {
4540
- return { status: 'unsupported' };
4541
- }
4542
- const fileName = params.fileName?.trim() || undefined;
4543
- const txId = normalizeArweaveTxIdForEnsure(params.transactionId) ??
4544
- normalizeArweaveTxIdForEnsure(fileName);
4545
- if (!txId && !fileName) {
4546
- return { status: 'missing-id' };
4547
- }
4548
- const targetSizes = mapRequestedWidthsToImageSizes(params.widths);
4549
- const needles = [fileName, txId].filter((n) => typeof n === 'string' && n.length > 0);
4550
- try {
4551
- let filePath;
4552
- for (const needle of needles) {
4553
- filePath = await resolveMatchingImagePath(needle);
4554
- if (filePath)
4555
- break;
4556
- }
4557
- if (!filePath && txId) {
4558
- await downloadTransactionIdWithDedupe(txId);
4559
- for (const needle of needles) {
4560
- filePath = await resolveMatchingImagePath(needle);
4561
- if (filePath)
4562
- break;
4563
- }
4564
- // Downloads store as bare tx id
4565
- if (!filePath) {
4566
- const direct = BaseFileManager.getFilesPath('images', txId);
4567
- if (await BaseFileManager.pathExists(direct)) {
4568
- filePath = direct;
4569
- }
4570
- }
4571
- }
4572
- if (!filePath) {
4573
- return { status: 'failed' };
4574
- }
4575
- const createdWidths = [];
4576
- for (const size of targetSizes) {
4577
- const variantPath = sizedVariantPath(filePath, size);
4578
- const exists = await BaseFileManager.pathExists(variantPath);
4579
- if (exists)
4580
- continue;
4581
- await BaseFileManager.resizeImage({
4582
- filePath,
4583
- width: size,
4584
- height: size,
4585
- });
4586
- createdWidths.push(size);
4587
- }
4588
- return {
4589
- status: 'ready',
4590
- filePath,
4591
- createdWidths,
4592
- };
4593
- }
4594
- catch (error) {
4595
- logger$1('ensureImageLocal failed', error);
4596
- return { status: 'failed' };
4597
- }
4598
- };
4599
- /**
4600
- * Ensure an image original exists in OPFS (downloading from Arweave if needed)
4601
- * and create only the requested ImageSize variant(s) for that one file.
4602
- */
4603
- const ensureImageLocal = async (params) => {
4604
- if (!supportsOpfsFileDownloads()) {
4605
- return { status: 'unsupported' };
4606
- }
4607
- const fileName = params.fileName?.trim() || undefined;
4608
- const txId = normalizeArweaveTxIdForEnsure(params.transactionId) ??
4609
- normalizeArweaveTxIdForEnsure(fileName);
4610
- if (!txId && !fileName) {
4611
- return { status: 'missing-id' };
4612
- }
4613
- const targetSizes = mapRequestedWidthsToImageSizes(params.widths);
4614
- const key = ensureKey(txId, fileName, targetSizes);
4615
- const existing = inFlightByKey.get(key);
4616
- if (existing) {
4617
- return existing;
4618
- }
4619
- const inFlight = (async () => {
4620
- try {
4621
- return await runEnsureImageLocal({
4622
- transactionId: txId,
4623
- fileName,
4624
- widths: targetSizes,
4625
- });
4626
- }
4627
- finally {
4628
- inFlightByKey.delete(key);
4629
- }
4630
- })();
4631
- inFlightByKey.set(key, inFlight);
4632
- return inFlight;
4633
- };
4634
-
4635
- const randomId = customAlphabet(alphanumeric, 10);
4636
- /**
4637
- * A 10-character local id. Never starts with "0x" (about 1 in 3,844 random ids would), so code that
4638
- * tells local ids from EAS uids by that prefix can't mistake one for the other.
4639
- */
4640
- const generateId = () => {
4641
- let id = randomId();
4642
- while (id.startsWith('0x'))
4643
- id = randomId();
4644
- return id;
4645
- };
4646
-
4647
- var generateId$1 = /*#__PURE__*/Object.freeze({
4648
- __proto__: null,
4649
- generateId: generateId
4650
- });
4651
-
4652
- const logger = debug('seedSdk:shared:helpers');
4653
- const getMimeType = (base64) => {
4654
- if (!base64) {
4655
- return null;
4656
- }
4657
- const result = base64.match(/^data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,/);
4658
- if (result && result.length > 1) {
4659
- return result[1];
4660
- }
4661
- else {
4662
- return null; // MIME type could not be determined
4663
- }
4664
- };
4665
- const getCorrectId = (localIdOrUid) => {
4666
- const id = {
4667
- localId: undefined,
4668
- uid: undefined,
4669
- };
4670
- if (!localIdOrUid) {
4671
- return id;
4672
- }
4673
- if (localIdOrUid.length === 10) {
4674
- id.localId = localIdOrUid;
4675
- }
4676
- if (localIdOrUid.startsWith('0x') && localIdOrUid.length === 66) {
4677
- id.uid = localIdOrUid;
4678
- }
4679
- return id;
4680
- };
4681
- const getDataTypeFromString = (data) => {
4682
- const nonImageBase64Regex = /^(?!data:image\/(?:jpeg|png|gif|bmp|webp);base64,)[A-Za-z0-9+/=]+$/;
4683
- if (nonImageBase64Regex.test(data)) {
4684
- return 'base64';
4685
- }
4686
- // Regular expression for base64 (simple version, checking for base64 format)
4687
- const imageBase64Regex = /^data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/]+={0,2}$/;
4688
- if (imageBase64Regex.test(data)) {
4689
- return 'imageBase64';
4690
- }
4691
- // Blob URLs are fetchable but session-scoped; recognize them as url type
4692
- if (data.startsWith('blob:')) {
4693
- return 'url';
4694
- }
4695
- // Regular expression for URL (simple version, checking for common URL format)
4696
- const urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/;
4697
- if (urlRegex.test(data)) {
4698
- return 'url';
4699
- }
4700
- return null;
4701
- };
4702
- const convertTxIdToImage = async (txId) => {
4703
- const imageFilePath = BaseFileManager.getFilesPath('images', txId);
4704
- const fileExists = await BaseFileManager.pathExists(imageFilePath);
4705
- if (!fileExists) {
4706
- logger(`[ItemView] [updateImage] ${imageFilePath} does not exist`);
4707
- return;
4708
- }
4709
- const buffer = await BaseFileManager.readFileAsBuffer(imageFilePath);
4710
- // Convert Blob to ArrayBuffer if needed, or use Buffer directly
4711
- let arrayBuffer;
4712
- if (buffer instanceof Blob) {
4713
- arrayBuffer = await buffer.arrayBuffer();
4714
- }
4715
- else if (buffer instanceof ArrayBuffer) {
4716
- arrayBuffer = buffer;
4717
- }
4718
- else {
4719
- // Node.js Buffer
4720
- const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
4721
- // Handle SharedArrayBuffer case by creating a new ArrayBuffer
4722
- if (sliced instanceof SharedArrayBuffer) {
4723
- const uint8Array = new Uint8Array(sliced);
4724
- arrayBuffer = uint8Array.buffer.slice(0);
4725
- }
4726
- else {
4727
- arrayBuffer = sliced;
4728
- }
4729
- }
4730
- const uint = new Uint8Array(arrayBuffer);
4731
- const imageBlob = new Blob([uint]);
4732
- return URL.createObjectURL(imageBlob);
4733
- };
4734
- /**
4735
- * Constructs an Arweave URL for a storage transaction ID
4736
- * @param storageTransactionId - The Arweave transaction ID
4737
- * @returns The full URL to access the transaction data on Arweave (e.g., https://arweave.net/raw/{transactionId})
4738
- * @deprecated Use BaseArweaveClient.getRawUrl() instead for better consistency and testability
4739
- */
4740
- const getArweaveUrlForTransaction = (storageTransactionId) => {
4741
- return BaseArweaveClient.getRawUrl(storageTransactionId);
4742
- };
4743
-
4744
- export { isNode as $, AmbiguousModelError as A, modelsRelations as B, pendingWrites as C, DEFAULT_TEXT_MAX_LENGTH as D, properties as E, propertiesRelations as F, propertyUidRelations as G, propertyUids as H, publishProcesses as I, schemas as J, uploadProcesses as K, BaseDb as L, ModelPropertyDataTypes as M, resolveStorageNameToSchemaName as N, nameMatchLevels as O, Property as P, BaseFileManager as Q, resolveModelRecord as R, getPropertyIdForModelAndName as S, THtmlEmbeddedDataUriPolicy as T, resolveItemModelFileId as U, INTERNAL_STORAGE_MODEL_FILE_IDS as V, getStorageDirForDataType as W, getContentSha256 as X, buildArweaveContentTags as Y, getItemStorageSubdir as Z, getItemStorageLocalFileName as _, TProperty as a, convertTxIdToImage as a$, ZERO_BYTES32 as a0, getItemModelScope as a1, INTERNAL_PROPERTY_NAMES as a2, isStorageSeedRef as a3, defaultAttestationData as a4, INTERNAL_DATA_TYPES as a5, listRelationEasPropertyName as a6, easPropertyNameForPublish as a7, normalizePublisher as a8, getAllAddressesFromDb as a9, normalizeAddressConfig as aA, normalizeAddressList as aB, normalizeArweaveTxIdForEnsure as aC, normalizeFeedItemFields as aD, resolveMediaRef as aE, isInternalSchema as aF, writeModelToDb as aG, writePropertyToDb as aH, renameModelInDb as aI, currentNameMatchLevels as aJ, withSqliteBusyRetry as aK, loadModelsFromDbForSchema as aL, mergeDbModelsIntoSchemaModels as aM, addSchemaToDb as aN, addModelsToDb as aO, clearNameMatchLevels as aP, isTestOrSeedDev as aQ, warnOnInvalidNameMatch as aR, INTERNAL_SCHEMA_IDS as aS, linkModelToSchema as aT, getMetadataPropertyNamesForQuery as aU, getWatchedAddressesFromDb as aV, getOwnedAddressesFromDb as aW, CLIENT_NOT_INITIALIZED as aX, toSchemaPropertyName as aY, easSchemaDefinitionForProperty as aZ, getStorageTransactionIdForSeedUid as a_, loadNameMatchLevels as aa, scheduleBulkFilesDownloadFromEasSync as ab, EAS_SEED_DATA_SYNCED_TO_DB_EVENT as ac, generateId as ad, normalizeHexAddress as ae, toMetadataPropertyName as af, getItemStoragePropertiesForModel as ag, getModelIdByFileId as ah, getModelId as ai, setCachedPropertyRefresher as aj, isSqliteBusyError as ak, SQLITE_BUSY_RETRY_DELAYS_MS as al, isBrowser as am, CONTENT_SHA256_TAG as an, CONTENT_TYPE_TAG as ao, ImageSize as ap, SEED_PROTOCOL_SCHEMA_NAME as aq, SeedModels as ar, VERSION_SCHEMA_UID_OPTIMISM_SEPOLIA as as, classifyMediaRef as at, closestImageSize as au, contentSha256TagMatches as av, ensureImageLocal as aw, getAddressesForItemsFilter as ax, getArweaveUrlForTransaction as ay, getFeedItemStringField as az, TPropertyConstructor as b, getPropertyIdForSchemaFileId as b0, downloadTransactionIdWithDedupe as b1, resolveMetadataRecord as b2, getDataTypeFromString as b3, getMimeType as b4, listRelationStoragePropertyName as b5, PROPERTY_NAMES_EXEMPT_FROM_ID_SUFFIX_STRIP as b6, toItemPropertyKey as b7, getAlternatePropertyNameForInstanceLookup as b8, BaseDb$1 as b9, ModelSchema as ba, BaseFileManager$1 as bb, SchemaSchema as bc, ModelSchemaSchema as bd, resolveModelRecord$1 as be, db as bf, download as bg, generateId$1 as bh, TPropertyDataType as c, TPropertyDefs as d, TStorageType as e, TValidationRules as f, normalizePropertyRecordSchema as g, appState as h, isDataType as i, config as j, getCorrectId as k, isPublishedSeedRef as l, metadata as m, normalizeDataType as n, modelUids as o, models as p, normalizeRelationPropertyValue as q, resolveSeedIdsFromRefString as r, seeds as s, arweaveL1FinalizeJobs as t, easSyncProcesses as u, versions as v, entityChanges as w, htmlEmbeddedImageCoPublish as x, modelRelations as y, modelSchemas as z };
4745
- //# sourceMappingURL=index-Dx_GTdyQ.js.map