@seedprotocol/sdk 0.7.10 → 0.7.11-next.1396.g883ef47

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 (174) hide show
  1. package/README.md +1 -1
  2. package/dist/{BaseFileManager-DLou1TZS.js → BaseFileManager-D-pLXQAp.js} +2 -14
  3. package/dist/BaseFileManager-D-pLXQAp.js.map +1 -0
  4. package/dist/{Item-oPnmIehk.js → Item-BU9wIT7l.js} +2053 -412
  5. package/dist/Item-BU9wIT7l.js.map +1 -0
  6. package/dist/{ModelProperty-D2vJozmW.js → ModelProperty-Bv7KzP97.js} +40 -38
  7. package/dist/ModelProperty-Bv7KzP97.js.map +1 -0
  8. package/dist/{Schema-BMXRF4j9.js → Schema-C_4wv0RP.js} +22 -22
  9. package/dist/{Schema-BMXRF4j9.js.map → Schema-C_4wv0RP.js.map} +1 -1
  10. package/dist/{SchemaValidationService-CieGrj5c.js → SchemaValidationService-Dc-lpFQX.js} +9 -7
  11. package/dist/{SchemaValidationService-CieGrj5c.js.map → SchemaValidationService-Dc-lpFQX.js.map} +1 -1
  12. package/dist/arweaveContentTags-iW_HcWJA.js +1456 -0
  13. package/dist/arweaveContentTags-iW_HcWJA.js.map +1 -0
  14. package/dist/{configurePlatform-WkmvqeTO.js → configurePlatform-BH1YOZpg.js} +2 -2
  15. package/dist/{configurePlatform-WkmvqeTO.js.map → configurePlatform-BH1YOZpg.js.map} +1 -1
  16. package/dist/db/drizzle/0030_drop_property_uids.sql +1 -0
  17. package/dist/db/drizzle/meta/0030_snapshot.json +1638 -0
  18. package/dist/db/drizzle/meta/_journal.json +8 -1
  19. package/dist/{detachRowsBeforeDelete-BZr_0iZU.js → detachRowsBeforeDelete-Cie55SoG.js} +4 -6
  20. package/dist/detachRowsBeforeDelete-Cie55SoG.js.map +1 -0
  21. package/dist/dist/{BaseFileManager-DLou1TZS.d.ts → BaseFileManager-D-pLXQAp.d.ts} +432 -495
  22. package/dist/dist/BaseFileManager-D-pLXQAp.d.ts.map +1 -0
  23. package/dist/dist/{configurePlatform-WkmvqeTO.d.ts → configurePlatform-BH1YOZpg.d.ts} +1 -1
  24. package/dist/dist/configurePlatform-BH1YOZpg.d.ts.map +1 -0
  25. package/dist/dist/{index-BjWk0x4-.d.ts → index-2PaIhvOU.d.ts} +1 -64
  26. package/dist/dist/{index-BjWk0x4-.d.ts.map → index-2PaIhvOU.d.ts.map} +1 -1
  27. package/dist/dist/{index.node-BfiBriEq.d.ts → index.node-C3rI1upv.d.ts} +25 -281
  28. package/dist/dist/{index.node-BfiBriEq.d.ts.map → index.node-C3rI1upv.d.ts.map} +1 -1
  29. package/dist/dist/minimumUpgradeVersion-Dg2MguFf.d.ts +20 -0
  30. package/dist/dist/minimumUpgradeVersion-Dg2MguFf.d.ts.map +1 -0
  31. package/dist/dist/platform.node.d.ts +2 -2
  32. package/dist/download-DHWUPLlK.js +393 -0
  33. package/dist/download-DHWUPLlK.js.map +1 -0
  34. package/dist/{eas-FZPPqxqe.js → eas-MXEdKKVc.js} +12 -10
  35. package/dist/{eas-FZPPqxqe.js.map → eas-MXEdKKVc.js.map} +1 -1
  36. package/dist/ensureImageLocal-B6suz6FC.js +175 -0
  37. package/dist/ensureImageLocal-B6suz6FC.js.map +1 -0
  38. package/dist/{getPublishPayload-DKuEaq96.js → getPublishPayload-Dd3HCYeI.js} +8 -49
  39. package/dist/getPublishPayload-Dd3HCYeI.js.map +1 -0
  40. package/dist/{getPublishPendingDiff-CH5xLMjU.js → getPublishPendingDiff-kcTP52Kj.js} +13 -12
  41. package/dist/{getPublishPendingDiff-CH5xLMjU.js.map → getPublishPendingDiff-kcTP52Kj.js.map} +1 -1
  42. package/dist/{index-BjWk0x4-.js → index-2PaIhvOU.js} +2 -4
  43. package/dist/index-2PaIhvOU.js.map +1 -0
  44. package/dist/index-BXTyvnKb.js +28 -0
  45. package/dist/index-BXTyvnKb.js.map +1 -0
  46. package/dist/{index-DXRhFg1N.js → index-CmNqUKQN.js} +10 -8
  47. package/dist/index-CmNqUKQN.js.map +1 -0
  48. package/dist/{index-Gh42_47r.js → index-Xs_EDi-_.js} +18 -401
  49. package/dist/index-Xs_EDi-_.js.map +1 -0
  50. package/dist/{index.browser-DQstVvRV.js → index.browser-BkcOQGhw.js} +59 -102
  51. package/dist/index.browser-BkcOQGhw.js.map +1 -0
  52. package/dist/{index.node-BfiBriEq.js → index.node-C3rI1upv.js} +14 -36
  53. package/dist/index.node-C3rI1upv.js.map +1 -0
  54. package/dist/main.js +19 -19
  55. package/dist/{minimumUpgradeVersion-BDhHvHtr.js → minimumUpgradeVersion-Dg2MguFf.js} +38 -9
  56. package/dist/minimumUpgradeVersion-Dg2MguFf.js.map +1 -0
  57. package/dist/{modelPropertyRecordSchemas-DEe-lYhn.js → modelPropertyRecordSchemas-CEac3FB8.js} +10 -10
  58. package/dist/{modelPropertyRecordSchemas-DEe-lYhn.js.map → modelPropertyRecordSchemas-CEac3FB8.js.map} +1 -1
  59. package/dist/node.js +14 -12
  60. package/dist/node.js.map +1 -1
  61. package/dist/platform.browser.js +10 -12
  62. package/dist/platform.browser.js.map +1 -1
  63. package/dist/platform.node.js +5 -5
  64. package/dist/{property-iNW1N3vm.js → property-4jSqHieb.js} +9 -9
  65. package/dist/{property-iNW1N3vm.js.map → property-4jSqHieb.js.map} +1 -1
  66. package/dist/{errors-CnT0fSMK.js → publishErrors-DZXDX3B6.js} +1830 -245
  67. package/dist/publishErrors-DZXDX3B6.js.map +1 -0
  68. package/dist/{readHtmlBodyForStorageSeed-B7HgPmq6.js → readHtmlBodyForStorageSeed-Cva1DPzE.js} +10 -12
  69. package/dist/{readHtmlBodyForStorageSeed-B7HgPmq6.js.map → readHtmlBodyForStorageSeed-Cva1DPzE.js.map} +1 -1
  70. package/dist/seedSchema/index.ts +0 -1
  71. package/dist/src/Item/Item.d.ts.map +1 -1
  72. package/dist/src/ItemProperty/ItemProperty.d.ts +1 -0
  73. package/dist/src/ItemProperty/ItemProperty.d.ts.map +1 -1
  74. package/dist/src/ItemProperty/service/actors/initialize.d.ts.map +1 -1
  75. package/dist/src/Model/index.d.ts +0 -2
  76. package/dist/src/Model/index.d.ts.map +1 -1
  77. package/dist/src/browser/db/Db.d.ts.map +1 -1
  78. package/dist/src/browser/db/drizzleFiles.d.ts +3 -2
  79. package/dist/src/browser/db/drizzleFiles.d.ts.map +1 -1
  80. package/dist/src/browser/helpers/FileManager.d.ts +1 -3
  81. package/dist/src/browser/helpers/FileManager.d.ts.map +1 -1
  82. package/dist/src/browser/workers/FileDownloader.d.ts +1 -0
  83. package/dist/src/browser/workers/FileDownloader.d.ts.map +1 -1
  84. package/dist/src/browser/workers/ImageResizer.d.ts +1 -0
  85. package/dist/src/browser/workers/ImageResizer.d.ts.map +1 -1
  86. package/dist/src/client/actors/addModelsToDb.d.ts.map +1 -1
  87. package/dist/src/client/actors/saveAppState.d.ts.map +1 -1
  88. package/dist/src/client/actors/saveConfig.d.ts.map +1 -1
  89. package/dist/src/db/minimumUpgradeVersion.d.ts +20 -5
  90. package/dist/src/db/minimumUpgradeVersion.d.ts.map +1 -1
  91. package/dist/src/db/read/metadataProperty.d.ts +1 -1
  92. package/dist/src/db/read/metadataProperty.d.ts.map +1 -1
  93. package/dist/src/db/read/resolveModelRecord.d.ts +0 -20
  94. package/dist/src/db/read/resolveModelRecord.d.ts.map +1 -1
  95. package/dist/src/db/write/createMetadata.d.ts +1 -8
  96. package/dist/src/db/write/createMetadata.d.ts.map +1 -1
  97. package/dist/src/db/write/removeLocalCopiesForAddresses.d.ts +0 -5
  98. package/dist/src/db/write/removeLocalCopiesForAddresses.d.ts.map +1 -1
  99. package/dist/src/db/write/updateItemPropertyValue.d.ts.map +1 -1
  100. package/dist/src/events/files/download.d.ts.map +1 -1
  101. package/dist/src/events/item/syncDbWithEas.d.ts.map +1 -1
  102. package/dist/src/helpers/easPropertyCanonical.d.ts +1 -1
  103. package/dist/src/helpers/easPropertyCanonical.d.ts.map +1 -1
  104. package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts +2 -3
  105. package/dist/src/helpers/entity/detachRowsBeforeDelete.d.ts.map +1 -1
  106. package/dist/src/helpers/entity/entityLifecycle.d.ts +0 -2
  107. package/dist/src/helpers/entity/entityLifecycle.d.ts.map +1 -1
  108. package/dist/src/helpers/environment.d.ts +0 -13
  109. package/dist/src/helpers/environment.d.ts.map +1 -1
  110. package/dist/src/helpers/index.d.ts +0 -4
  111. package/dist/src/helpers/index.d.ts.map +1 -1
  112. package/dist/src/helpers/itemStorageFile.d.ts +0 -11
  113. package/dist/src/helpers/itemStorageFile.d.ts.map +1 -1
  114. package/dist/src/helpers/propertySchemaUid.d.ts +7 -5
  115. package/dist/src/helpers/propertySchemaUid.d.ts.map +1 -1
  116. package/dist/src/helpers/storageDir.d.ts +0 -2
  117. package/dist/src/helpers/storageDir.d.ts.map +1 -1
  118. package/dist/src/index.d.ts +4 -1
  119. package/dist/src/index.d.ts.map +1 -1
  120. package/dist/src/node/db/Db.d.ts +0 -2
  121. package/dist/src/node/db/Db.d.ts.map +1 -1
  122. package/dist/src/node/helpers/FileManager.d.ts +0 -2
  123. package/dist/src/node/helpers/FileManager.d.ts.map +1 -1
  124. package/dist/src/node/index.d.ts +11 -3
  125. package/dist/src/node/index.d.ts.map +1 -1
  126. package/dist/src/outbox/index.d.ts +5 -5
  127. package/dist/src/outbox/index.d.ts.map +1 -1
  128. package/dist/src/seedSchema/index.d.ts +0 -1
  129. package/dist/src/seedSchema/index.d.ts.map +1 -1
  130. package/dist/{syncDbWithEas-FuyqoEKk.js → syncDbWithEas-V_9gIyHp.js} +25 -23
  131. package/dist/syncDbWithEas-V_9gIyHp.js.map +1 -0
  132. package/dist/{tolerantWebAccess-zEseJ2zT.js → tolerantWebAccess-D6hFxREe.js} +11 -13
  133. package/dist/{tolerantWebAccess-zEseJ2zT.js.map → tolerantWebAccess-D6hFxREe.js.map} +1 -1
  134. package/dist/{updateSeedRevokedAt-B9JRwr5T.js → updateSeedRevokedAt-CsAoUEEt.js} +4 -3
  135. package/dist/{updateSeedRevokedAt-B9JRwr5T.js.map → updateSeedRevokedAt-CsAoUEEt.js.map} +1 -1
  136. package/package.json +7 -12
  137. package/dist/BaseFileManager-DLou1TZS.js.map +0 -1
  138. package/dist/Item-oPnmIehk.js.map +0 -1
  139. package/dist/ModelProperty-D2vJozmW.js.map +0 -1
  140. package/dist/detachRowsBeforeDelete-BZr_0iZU.js.map +0 -1
  141. package/dist/dist/BaseFileManager-DLou1TZS.d.ts.map +0 -1
  142. package/dist/dist/configurePlatform-WkmvqeTO.d.ts.map +0 -1
  143. package/dist/dist/minimumUpgradeVersion-BDhHvHtr.d.ts +0 -9
  144. package/dist/dist/minimumUpgradeVersion-BDhHvHtr.d.ts.map +0 -1
  145. package/dist/errors-CnT0fSMK.js.map +0 -1
  146. package/dist/getPublishPayload-DKuEaq96.js.map +0 -1
  147. package/dist/getPublishUploads-DivYpgNw.js +0 -783
  148. package/dist/getPublishUploads-DivYpgNw.js.map +0 -1
  149. package/dist/index-BjWk0x4-.js.map +0 -1
  150. package/dist/index-CZMWKXLM.js +0 -27
  151. package/dist/index-CZMWKXLM.js.map +0 -1
  152. package/dist/index-DXRhFg1N.js.map +0 -1
  153. package/dist/index-Gh42_47r.js.map +0 -1
  154. package/dist/index.browser-DQstVvRV.js.map +0 -1
  155. package/dist/index.node-BfiBriEq.js.map +0 -1
  156. package/dist/minimumUpgradeVersion-BDhHvHtr.js.map +0 -1
  157. package/dist/publishGraph-CRn_N3NT.js +0 -311
  158. package/dist/publishGraph-CRn_N3NT.js.map +0 -1
  159. package/dist/seedSchema/PropertyUidSchema.ts +0 -16
  160. package/dist/src/db/read/getItemProperty.d.ts +0 -6
  161. package/dist/src/db/read/getItemProperty.d.ts.map +0 -1
  162. package/dist/src/db/read/isSeedRevoked.d.ts +0 -7
  163. package/dist/src/db/read/isSeedRevoked.d.ts.map +0 -1
  164. package/dist/src/db/write/saveMetadata.d.ts +0 -3
  165. package/dist/src/db/write/saveMetadata.d.ts.map +0 -1
  166. package/dist/src/db/write/updateMetadata.d.ts +0 -13
  167. package/dist/src/db/write/updateMetadata.d.ts.map +0 -1
  168. package/dist/src/seedSchema/PropertyUidSchema.d.ts +0 -64
  169. package/dist/src/seedSchema/PropertyUidSchema.d.ts.map +0 -1
  170. package/dist/src/types/fileManager.d.ts +0 -13
  171. package/dist/src/types/fileManager.d.ts.map +0 -1
  172. package/dist/syncDbWithEas-FuyqoEKk.js.map +0 -1
  173. package/dist/tabEvents-DT5ez6io.js +0 -3830
  174. package/dist/tabEvents-DT5ez6io.js.map +0 -1
@@ -1,19 +1,21 @@
1
1
  import { fromCallback, setup, assign, createActor, waitFor, fromPromise } from 'xstate';
2
- import { ak as isActorStopped, T as applyContextUpdate, U as isInternalUpdate, al as differsFromOriginals, X as isEntityTornDown, am as onEntityTeardown, V as evictionLog, Y as sameEntity, Z as DEFAULT_READY_TIMEOUT_MS, _ as createReactiveProxy, $ as runEntityLoad, a0 as waitForEntityReady, a2 as EntityUnloadedError, a3 as entityStatus, I as Item, a1 as evictEntities, a4 as releaseEntity, a5 as destroyEntity, a8 as modelPropertiesToObject, a6 as EntityCache, aa as easPropertyNameForPublish, af as matchMetadataProperty, aT as getSeedPublisher, ag as metadataRowsOfProperty, ah as METADATA_LATEST_FIRST_ORDER_SQL, aU as deleteStagedFile, aV as storageFileName, aW as STORAGE_SUBDIR, aX as resolveNewSeedFields, aY as lookupPropertySchemaUid, aZ as stageStorageContent, a_ as requireStagedFile, a$ as createSeed, b0 as createMetadata, H as compareMetadataRowsLatestFirst, b1 as storageValueRowOf, t as registerOutboxApplier, r as outboxOpValue, b2 as findSetConflict, b3 as recordPropertyEdit, h as OutboxOpDeferredError, u as startOutbox, i as OutboxOpFailedError, y as OwnershipError, B as isItemOwned, R as getLatestVersionRow, S as parseListPropertyValueFromStorage, au as resolvePublisherFromSeedRow, aE as isLocalUnsealedDraft, b4 as setEntityUnloadGraceMs, b5 as stopOutbox, b6 as evictAllEntities } from './Item-oPnmIehk.js';
2
+ import { as as isActorStopped, N as applyContextUpdate, P as isInternalUpdate, at as differsFromOriginals, bd as writeModelToDb, be as writePropertyToDb, U as isEntityTornDown, ax as onEntityTeardown, Q as evictionLog, V as sameEntity, W as DEFAULT_READY_TIMEOUT_MS, X as createReactiveProxy, Y as runEntityLoad, Z as waitForEntityReady, A as AmbiguousModelError, E as EntityUnloadedError, $ as entityStatus, I as Item, _ as evictEntities, a1 as releaseEntity, a2 as destroyEntity, bf as currentNameMatchLevels, bg as renameModelInDb, ag as modelPropertiesToObject, a6 as EntityCache, a3 as isClientReady, aM as MachineIds, a$ as normalizeAddressConfig, af as nameMatchLevels, aa as loadNameMatchLevels, a0 as inDefinitionsTransaction, bh as insertModelStubs, b0 as normalizeAddressList, bi as warnOnInvalidNameMatch, a4 as upsertSchemaRow, bj as refreshCachedProperties, ai as easPropertyNameForPublish, aJ as findSeedRow, a7 as normalizePublisher, aK as resolvePublisherFromSeedRow, an as matchMetadataProperty, ao as metadataRowsOfProperty, ap as METADATA_LATEST_FIRST_ORDER_SQL, bk as resolveMetadataProperty, aj as metadataStorageName, bl as lookupPropertySchemaUid, K as getItemStorageSubdir, bm as getItemStorageLocalFileName, bn as deleteStagedFile, bo as storageFileName, bp as STORAGE_SUBDIR, bq as resolveNewSeedFields, br as stageStorageContent, bs as requireStagedFile, r as registerOutboxApplier, k as outboxOpValue, bt as findSetConflict, bu as recordPropertyEdit, d as OutboxOpDeferredError, bv as startOutbox, e as OutboxOpFailedError, q as OwnershipError, u as isItemOwned, D as compareMetadataRowsLatestFirst, bw as createSeed, bx as storageValueRowOf, by as getLatestVersionRow, aI as parseListPropertyValueFromStorage, aL as ClientManagerState, bz as ClientManagerEvents, aY as isLocalUnsealedDraft, J as writeInBatches, ae as selectInBatches, bA as getWatchedAddressesFromDb, bB as getOwnedAddressesFromDb, bC as setEntityUnloadGraceMs, bD as stopOutbox, bE as evictAllEntities, bF as registerClientActor, aF as isVersionRevoked, bG as getPropertyDefinitions, aH as readModelMetadataProperties, bH as metadataPropertyNameForms, bI as currentPropertyRow, H as getLatestPublishedVersionRow, au as resolveModelRecord, bJ as resolveModelRecordByNameOrType, v as isPublishedSeedRef, bK as storageSeedTransactionId, bL as storageUploadPath, bM as storedValueKind, bN as resolveStorageDisplay } from './Item-BU9wIT7l.js';
3
3
  import debug from 'debug';
4
- import { B as BaseDb, l as properties, d as models$1, j as modelSchemas, t as schemas, f as easSyncProcesses, a as appState, w as BaseFileManager, b as modelUids, s as seeds, v as versions, m as metadata$1, p as pendingWrites, h as htmlEmbeddedImageCoPublish, e as arweaveL1FinalizeJobs, r as publishProcesses } from './BaseFileManager-DLou1TZS.js';
5
- import { aI as writeModelToDb, aJ as writePropertyToDb, m as generateId, p as isInternalSchema, A as AmbiguousModelError, aK as currentNameMatchLevels, aL as renameModelInDb, s as isClientReady, a as TProperty, ay as withDbTabLock, af as MachineIds, ap as isLeaderTab, aM as postTabMessage, aN as onTabMessage, ax as whenLeaderTab, aO as listenForOtherTabsEvents, F as isBrowser, $ as isNode, aP as startTabCoordination, aq as normalizeAddressConfig, aQ as isTestOrSeedDev, L as nameMatchLevels, C as loadNameMatchLevels, aD as withSeedDbLock, q as inDefinitionsTransaction, aR as insertModelStubs, G as emitAcrossTabs, a8 as ADDRESSES_PERSISTED_EVENT, ar as normalizeAddressList, aS as warnOnInvalidNameMatch, u as upsertSchemaRow, aT as refreshCachedProperties, g as normalizePropertyRecordSchema, M as ModelPropertyDataTypes, j as findSeedRow, aU as tryWithTabLock, Y as getItemStorageSubdir, _ as getItemStorageLocalFileName, aV as notifyFileSaved, ad as ImageSize, n as normalizeDataType, ab as ClientManagerState, aW as ClientManagerEvents, at as parseAddressesPersistedPayload, x as normalizePublisher, w as writeInBatches, K as selectInBatches, ae as LOCAL_COPIES_REMOVED_EVENT, aX as getWatchedAddressesFromDb, aY as getOwnedAddressesFromDb, aZ as CLIENT_NOT_INITIALIZED, a_ as stopTabCoordination, a$ as registerClientActor } from './tabEvents-DT5ez6io.js';
6
- import { seedGatewayConfigFromSeedConfig, setPreferredArweaveReadBaseUrls, resolveSeedGatewayEndpoints, setResolvedSeedGatewayEndpoints, BaseArweaveClient, ensureReadGatewaySelected, DEFAULT_ARWEAVE_HOST } from '@seedprotocol/arweave';
4
+ import { B as BaseDb, l as properties, d as models$1, j as modelSchemas, q as schemas, f as easSyncProcesses, a as appState, r as BaseFileManager, b as modelUids, s as seeds, v as versions, m as metadata$1, p as pendingWrites, h as htmlEmbeddedImageCoPublish, e as arweaveL1FinalizeJobs, o as publishProcesses } from './BaseFileManager-D-pLXQAp.js';
5
+ import { seedGatewayConfigFromSeedConfig, setPreferredArweaveReadBaseUrls, resolveSeedGatewayEndpoints, setResolvedSeedGatewayEndpoints, BaseArweaveClient, ensureReadGatewaySelected, DEFAULT_ARWEAVE_HOST, normalizeRelationPropertyValue, resolveSeedIdsFromRefString, getPublicArweaveRawUrl } from '@seedprotocol/arweave';
7
6
  import 'pluralize';
8
- import { localNamesInBestMatchTier, configureEasReadChain, matchEasModelSchemas, whenEasReadChainSettled, getEasReadChainId, DEFAULT_EAS_CHAIN_ID, isEasReadChainConfigured, easName, isValidEasAttestationUid } from '@seedprotocol/eas';
7
+ import { localNamesInBestMatchTier, configureEasReadChain, matchEasModelSchemas, whenEasReadChainSettled, getEasReadChainId, DEFAULT_EAS_CHAIN_ID, isEasReadChainConfigured, easName, isValidEasAttestationUid, getEasChainDeployment, getEasAttestationExplorerUrl } from '@seedprotocol/eas';
8
+ import { k as generateId, l as isInternalSchema, a as TProperty, Q as withDbTabLock, H as isLeaderTab, a2 as postTabMessage, a3 as onTabMessage, O as whenLeaderTab, a4 as listenForOtherTabsEvents, o as isBrowser, q as isNode, a5 as startTabCoordination, Y as withSeedDbLock, p as emitAcrossTabs, A as ADDRESSES_PERSISTED_EVENT, g as normalizePropertyRecordSchema, M as ModelPropertyDataTypes, n as normalizeDataType, a6 as tryWithTabLock, a7 as notifyFileSaved, I as ImageSize, J as parseAddressesPersistedPayload, L as LOCAL_COPIES_REMOVED_EVENT, a8 as CLIENT_NOT_INITIALIZED, a9 as stopTabCoordination, Z as ZERO_BYTES32, aa as getExtensionForMimeType, ab as INTERNAL_STORAGE_MODEL_FILE_IDS, i as isDataType, ac as resolveUploadMimeType, B as getContentSha256, y as buildArweaveContentTags } from './arweaveContentTags-iW_HcWJA.js';
9
9
  import { eq, and, isNull, or, inArray, isNotNull, ne, desc, count, sql, like } from 'drizzle-orm';
10
10
  import { Type } from '@sinclair/typebox';
11
11
  import { isEqual } from 'lodash-es';
12
+ import { getSeed, registerLocalQuerySource, clearLocalQuerySource } from '@seedprotocol/query';
13
+ import { rewriteHtmlUrls, parseArchiveDocument, dublinCoreXml, premisXml, digestHex, createBag } from '@seedprotocol/archive';
14
+ import { parseFragment } from 'parse5';
12
15
  import { sha3_256 } from '@noble/hashes/sha3';
13
16
  import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
14
- import { getSeed, registerLocalQuerySource, clearLocalQuerySource } from '@seedprotocol/query';
15
17
 
16
- const logger$g = debug('seedSdk:model:actors:validateModel');
18
+ const logger$h = debug('seedSdk:model:actors:validateModel');
17
19
  // Validation timeout in milliseconds (10 seconds)
18
20
  const VALIDATION_TIMEOUT_MS = 10000;
19
21
  const validateModel = fromCallback(({ sendBack, input: { context }, self }) => {
@@ -36,10 +38,10 @@ const validateModel = fromCallback(({ sendBack, input: { context }, self }) => {
36
38
  const _validateModel = async () => {
37
39
  try {
38
40
  // Use dynamic imports to break circular dependencies
39
- const validationServiceMod = await import('./SchemaValidationService-CieGrj5c.js');
41
+ const validationServiceMod = await import('./SchemaValidationService-Dc-lpFQX.js');
40
42
  const { SchemaValidationService } = validationServiceMod;
41
43
  const validationService = new SchemaValidationService();
42
- const _mod_0 = await import('./Schema-BMXRF4j9.js');
44
+ const _mod_0 = await import('./Schema-C_4wv0RP.js');
43
45
  const _ns_0 = _mod_0.a;
44
46
  const schemaMod = _ns_0;
45
47
  const { Schema } = schemaMod;
@@ -85,12 +87,12 @@ const validateModel = fromCallback(({ sendBack, input: { context }, self }) => {
85
87
  }
86
88
  }
87
89
  else {
88
- logger$g(`Schema is in ${schemaStatus} state, skipping schema validation`);
90
+ logger$h(`Schema is in ${schemaStatus} state, skipping schema validation`);
89
91
  // Continue with structure validation only
90
92
  }
91
93
  }
92
94
  catch (error) {
93
- logger$g('Error validating model against schema:', error);
95
+ logger$h('Error validating model against schema:', error);
94
96
  // Continue with structure validation only
95
97
  }
96
98
  }
@@ -105,7 +107,7 @@ const validateModel = fromCallback(({ sendBack, input: { context }, self }) => {
105
107
  if (!hasResponded) {
106
108
  hasResponded = true;
107
109
  clearTimeout(timeoutId);
108
- logger$g('Error in validateModel:', error);
110
+ logger$h('Error in validateModel:', error);
109
111
  sendBack({
110
112
  type: 'validationError',
111
113
  errors: [{
@@ -122,7 +124,7 @@ const validateModel = fromCallback(({ sendBack, input: { context }, self }) => {
122
124
  if (!hasResponded) {
123
125
  hasResponded = true;
124
126
  clearTimeout(timeoutId);
125
- logger$g('Error in validateModel:', error);
127
+ logger$h('Error in validateModel:', error);
126
128
  sendBack({
127
129
  type: 'validationError',
128
130
  errors: [{
@@ -306,7 +308,7 @@ const modelMachine = setup({
306
308
  },
307
309
  });
308
310
 
309
- const logger$f = debug('seedSdk:model:createModelProperties');
311
+ const logger$g = debug('seedSdk:model:createModelProperties');
310
312
  /**
311
313
  * Creates and caches the ModelProperty instances for the properties passed to Model.create, from
312
314
  * the rows the model's write stored. Stops early when the model is stopped (unloaded, or evicted
@@ -319,8 +321,8 @@ async function createModelProperties(model, propertyDefinitions) {
319
321
  if (!id || !_dbId || !modelName) {
320
322
  throw new Error('Model ID, file ID, and name are required to create properties');
321
323
  }
322
- logger$f(`Creating ${Object.keys(propertyDefinitions).length} properties for model "${modelName}" (id: ${_dbId})`);
323
- const { ModelProperty } = await import('./ModelProperty-D2vJozmW.js');
324
+ logger$g(`Creating ${Object.keys(propertyDefinitions).length} properties for model "${modelName}" (id: ${_dbId})`);
325
+ const { ModelProperty } = await import('./ModelProperty-Bv7KzP97.js');
324
326
  const { and, eq } = await import('drizzle-orm');
325
327
  const db = BaseDb.getAppDb();
326
328
  for (const [propName, propData] of Object.entries(propertyDefinitions)) {
@@ -338,11 +340,11 @@ async function createModelProperties(model, propertyDefinitions) {
338
340
  : undefined;
339
341
  // Otherwise use provided ID or generate a random one
340
342
  // IDs should be generated in the import process before creating properties
341
- const _mod_1 = await import('./tabEvents-DT5ez6io.js');
342
- const _ns_1 = _mod_1.bl;
343
+ const _mod_1 = await import('./arweaveContentTags-iW_HcWJA.js');
344
+ const _ns_1 = _mod_1.ap;
343
345
  const { generateId } = _ns_1;
344
346
  const propertyFileId = propData.id || propertyRecord?.schemaFileId || generateId();
345
- logger$f(`Creating property "${propName}" with fileId "${propertyFileId}"`);
347
+ logger$g(`Creating property "${propName}" with fileId "${propertyFileId}"`);
346
348
  // Query database to get refModelId if the property has ref/refModelName
347
349
  // The property should already be in DB from addModelsToDb
348
350
  let refModelId = propData.refModelId;
@@ -352,27 +354,27 @@ async function createModelProperties(model, propertyDefinitions) {
352
354
  // First try to get refModelId from the property record in the database
353
355
  if (propertyRecord?.refModelId) {
354
356
  refModelId = propertyRecord.refModelId;
355
- logger$f(`Found refModelId ${refModelId} from database for property "${propName}"`);
357
+ logger$g(`Found refModelId ${refModelId} from database for property "${propName}"`);
356
358
  }
357
359
  else if (refModelName) {
358
360
  // Fallback: resolve the ref by name within this model's schema
359
- const _mod_2 = await import('./tabEvents-DT5ez6io.js');
360
- const _ns_2 = _mod_2.bh;
361
+ const _mod_2 = await import('./Item-BU9wIT7l.js');
362
+ const _ns_2 = _mod_2.bP;
361
363
  const { resolveModelRecord } = _ns_2;
362
364
  const refModelRecord = await resolveModelRecord(refModelName, { schemaName }, db);
363
365
  if (refModelRecord?.id) {
364
366
  refModelId = refModelRecord.id;
365
- logger$f(`Resolved refModelId ${refModelId} from model name "${refModelName}" for property "${propName}"`);
367
+ logger$g(`Resolved refModelId ${refModelId} from model name "${refModelName}" for property "${propName}"`);
366
368
  }
367
369
  }
368
370
  }
369
371
  catch (error) {
370
- logger$f(`Error fetching refModelId for property "${propName}":`, error);
372
+ logger$g(`Error fetching refModelId for property "${propName}":`, error);
371
373
  // Continue without refModelId - it will be resolved later in _initializeOriginalValues
372
374
  }
373
375
  }
374
376
  if (stopped()) {
375
- logger$f(`Model "${modelName}" was stopped while creating its properties; not creating the rest`);
377
+ logger$g(`Model "${modelName}" was stopped while creating its properties; not creating the rest`);
376
378
  return;
377
379
  }
378
380
  // Create ModelProperty instance
@@ -409,10 +411,10 @@ async function createModelProperties(model, propertyDefinitions) {
409
411
  // The model holds its properties (a child hold), released when the model is destroyed or evicted
410
412
  ModelProperty.holdForModel(property, model);
411
413
  }
412
- logger$f(`Created all properties for model "${modelName}"`);
414
+ logger$g(`Created all properties for model "${modelName}"`);
413
415
  }
414
416
 
415
- const logger$e = debug('seedSdk:db:write:saveDefinition');
417
+ const logger$f = debug('seedSdk:db:write:saveDefinition');
416
418
  // Writes that have started. Evicting an instance can't cancel its write, so code that deletes rows
417
419
  // under live instances (Schema.destroy, test cleanup) waits for these after evicting the instances.
418
420
  const inFlightWrites = new Set();
@@ -444,7 +446,7 @@ function saveDefinition(input) {
444
446
  }
445
447
  const write = async (input) => {
446
448
  if (input.type === 'model') {
447
- logger$e(`Writing model "${input.modelName}" (${input.modelFileId}) to schema ${input.schemaId}`);
449
+ logger$f(`Writing model "${input.modelName}" (${input.modelFileId}) to schema ${input.schemaId}`);
448
450
  return writeModelToDb(input.modelFileId, {
449
451
  modelName: input.modelName,
450
452
  schemaId: input.schemaId,
@@ -452,13 +454,13 @@ const write = async (input) => {
452
454
  });
453
455
  }
454
456
  // Imported here: the validation service imports the Schema barrel, which imports this module's callers
455
- const { SchemaValidationService } = await import('./SchemaValidationService-CieGrj5c.js');
457
+ const { SchemaValidationService } = await import('./SchemaValidationService-Dc-lpFQX.js');
456
458
  const data = input.data();
457
459
  const { errors } = new SchemaValidationService().validatePropertyStructure(data);
458
460
  if (errors.length > 0) {
459
461
  throw new Error(`Property "${data.name}" is invalid: ${errors.map((error) => error.message).join(', ')}`);
460
462
  }
461
- logger$e(`Writing property "${data.name}" (${input.propertyFileId})`);
463
+ logger$f(`Writing property "${data.name}" (${input.propertyFileId})`);
462
464
  await writePropertyToDb(input.propertyFileId, data);
463
465
  };
464
466
 
@@ -601,7 +603,7 @@ function setupEntityLiveQuery(instance, config) {
601
603
  }
602
604
 
603
605
  // Model and Schema are imported dynamically: Model.ts -> loadModel -> Schema.ts -> Model.ts
604
- const logger$d = debug('seedSdk:model:loadModel');
606
+ const logger$e = debug('seedSdk:model:loadModel');
605
607
  /**
606
608
  * Create ModelProperty instances for all property IDs to ensure they're cached
607
609
  * This ensures that ModelProperty.getById() in Model.properties getter will find the instances
@@ -613,10 +615,10 @@ const createPropertyInstances = async (propertyFileIds, since) => {
613
615
  return;
614
616
  }
615
617
  try {
616
- const mod = await import('./ModelProperty-D2vJozmW.js');
618
+ const mod = await import('./ModelProperty-Bv7KzP97.js');
617
619
  const ModelProperty = mod?.ModelProperty ?? mod?.default;
618
620
  if (!ModelProperty) {
619
- logger$d('createPropertyInstances: ModelProperty not available from dynamic import');
621
+ logger$e('createPropertyInstances: ModelProperty not available from dynamic import');
620
622
  return;
621
623
  }
622
624
  // Create instances for all property IDs in parallel
@@ -625,22 +627,22 @@ const createPropertyInstances = async (propertyFileIds, since) => {
625
627
  try {
626
628
  const property = await ModelProperty.createById(propertyFileId, { since });
627
629
  if (property) {
628
- logger$d(`Created/cached ModelProperty instance for propertyFileId "${propertyFileId}"`);
630
+ logger$e(`Created/cached ModelProperty instance for propertyFileId "${propertyFileId}"`);
629
631
  }
630
632
  else {
631
- logger$d(`ModelProperty.createById returned undefined for propertyFileId "${propertyFileId}" (may not exist in DB yet)`);
633
+ logger$e(`ModelProperty.createById returned undefined for propertyFileId "${propertyFileId}" (may not exist in DB yet)`);
632
634
  }
633
635
  }
634
636
  catch (error) {
635
- logger$d(`Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
637
+ logger$e(`Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
636
638
  // Don't throw - continue with other properties
637
639
  }
638
640
  });
639
641
  await Promise.all(createPromises);
640
- logger$d(`Finished creating/caching ${propertyFileIds.length} ModelProperty instances`);
642
+ logger$e(`Finished creating/caching ${propertyFileIds.length} ModelProperty instances`);
641
643
  }
642
644
  catch (error) {
643
- logger$d(`Error in createPropertyInstances: ${error}`);
645
+ logger$e(`Error in createPropertyInstances: ${error}`);
644
646
  // Don't throw - this is best-effort to pre-populate cache
645
647
  }
646
648
  };
@@ -675,7 +677,7 @@ async function loadModel(context, isStopped) {
675
677
  if (dbModels.length > 0) {
676
678
  modelRecord = dbModels[0];
677
679
  foundBySchemaFileId = true;
678
- logger$d(`Found model "${modelName}" in database by schemaFileId "${schemaFileId}"`);
680
+ logger$e(`Found model "${modelName}" in database by schemaFileId "${schemaFileId}"`);
679
681
  }
680
682
  }
681
683
  // If not found by ID, try by name
@@ -684,11 +686,11 @@ async function loadModel(context, isStopped) {
684
686
  if (!modelRecord) {
685
687
  // Model names are only unique per schema: prefer this schema's row, and otherwise only
686
688
  // consider same-name rows that no schema claims yet (never another schema's model).
687
- const _mod_3 = await import('./BaseFileManager-DLou1TZS.js');
689
+ const _mod_3 = await import('./BaseFileManager-D-pLXQAp.js');
688
690
  const _ns_3 = _mod_3.M;
689
691
  const { modelSchemas } = _ns_3;
690
- const _mod_4 = await import('./BaseFileManager-DLou1TZS.js');
691
- const _ns_4 = _mod_4.z;
692
+ const _mod_4 = await import('./BaseFileManager-D-pLXQAp.js');
693
+ const _ns_4 = _mod_4.x;
692
694
  const { schemas: schemasTable } = _ns_4;
693
695
  let dbModels = await db
694
696
  .select({
@@ -730,16 +732,16 @@ async function loadModel(context, isStopped) {
730
732
  try {
731
733
  const { Model } = await Promise.resolve().then(function () { return Model$1; });
732
734
  if (Model.peek(dbSchemaFileId)) {
733
- logger$d(`Model "${modelName}" found in database by name with schemaFileId "${dbSchemaFileId}", and a cached instance already exists. Updating current instance to use the same schemaFileId.`);
735
+ logger$e(`Model "${modelName}" found in database by name with schemaFileId "${dbSchemaFileId}", and a cached instance already exists. Updating current instance to use the same schemaFileId.`);
734
736
  // Update the current instance's schemaFileId to match the database
735
737
  // This ensures both instances point to the same cached instance
736
738
  modelRecord = foundModel;
737
739
  schemaFileId = dbSchemaFileId;
738
- logger$d(`Using existing cached instance with schemaFileId "${schemaFileId}" for model "${modelName}"`);
740
+ logger$e(`Using existing cached instance with schemaFileId "${schemaFileId}" for model "${modelName}"`);
739
741
  }
740
742
  }
741
743
  catch (error) {
742
- logger$d(`Error checking Model cache for schemaFileId "${dbSchemaFileId}": ${error}`);
744
+ logger$e(`Error checking Model cache for schemaFileId "${dbSchemaFileId}": ${error}`);
743
745
  // Fall through to normal processing
744
746
  }
745
747
  }
@@ -747,31 +749,31 @@ async function loadModel(context, isStopped) {
747
749
  // this is a new model - don't use the existing model (will be renamed if duplicate)
748
750
  // UNLESS we found a cached instance above (in which case modelRecord is already set)
749
751
  if (!modelRecord && schemaFileId && dbSchemaFileId && schemaFileId !== dbSchemaFileId) {
750
- logger$d(`Model "${modelName}" found by name but has different schemaFileId (provided: "${schemaFileId}", db: "${dbSchemaFileId}"). Creating new model (will check for duplicates).`);
752
+ logger$e(`Model "${modelName}" found by name but has different schemaFileId (provided: "${schemaFileId}", db: "${dbSchemaFileId}"). Creating new model (will check for duplicates).`);
751
753
  // Don't set modelRecord - we'll create a new model with potentially renamed name
752
754
  }
753
755
  else if (!modelRecord && schemaFileId && !dbSchemaFileId) {
754
756
  // We have a schemaFileId but the found model doesn't have one - this is a new model
755
757
  // Don't reuse the existing model, create a new one (will be renamed if duplicate)
756
- logger$d(`Model "${modelName}" found by name but found model has no schemaFileId. Creating new model with schemaFileId "${schemaFileId}" (will check for duplicates).`);
758
+ logger$e(`Model "${modelName}" found by name but found model has no schemaFileId. Creating new model with schemaFileId "${schemaFileId}" (will check for duplicates).`);
757
759
  // Don't set modelRecord - we'll create a new model
758
760
  }
759
761
  else if (!modelRecord) {
760
762
  // Only reuse existing model if:
761
763
  // 1. No schemaFileId was provided (loading existing model), OR
762
764
  // 2. schemaFileId matches (same model)
763
- logger$d(`Model "${modelName}" found by name, reusing existing model (schemaFileId: provided="${schemaFileId}", db="${dbSchemaFileId}")`);
765
+ logger$e(`Model "${modelName}" found by name, reusing existing model (schemaFileId: provided="${schemaFileId}", db="${dbSchemaFileId}")`);
764
766
  modelRecord = foundModel;
765
767
  // Only use database schemaFileId if no id was explicitly provided
766
768
  // If an id was provided, we should use it (it might be creating a new model with a specific ID)
767
769
  if (!id && dbSchemaFileId) {
768
770
  schemaFileId = dbSchemaFileId;
769
- logger$d(`Using database schemaFileId "${schemaFileId}" for model "${modelName}" (no ID was provided)`);
771
+ logger$e(`Using database schemaFileId "${schemaFileId}" for model "${modelName}" (no ID was provided)`);
770
772
  }
771
773
  else if (id) {
772
- logger$d(`Preserving provided id (schemaFileId) "${schemaFileId}" for model "${modelName}" (ignoring database schemaFileId "${dbSchemaFileId}")`);
774
+ logger$e(`Preserving provided id (schemaFileId) "${schemaFileId}" for model "${modelName}" (ignoring database schemaFileId "${dbSchemaFileId}")`);
773
775
  }
774
- logger$d(`Found model "${modelName}" in database by name, schemaFileId: "${schemaFileId}"`);
776
+ logger$e(`Found model "${modelName}" in database by name, schemaFileId: "${schemaFileId}"`);
775
777
  }
776
778
  }
777
779
  }
@@ -796,7 +798,7 @@ async function loadModel(context, isStopped) {
796
798
  // Generate schemaFileId if not set
797
799
  if (!schemaFileId) {
798
800
  schemaFileId = generateId();
799
- logger$d(`Generated id (schemaFileId) "${schemaFileId}" for model "${modelName}"`);
801
+ logger$e(`Generated id (schemaFileId) "${schemaFileId}" for model "${modelName}"`);
800
802
  }
801
803
  // Include _dbId from database record so properties can be created if needed
802
804
  // Even if we found an existing model, we may still need to create properties
@@ -820,14 +822,14 @@ async function loadModel(context, isStopped) {
820
822
  }
821
823
  }
822
824
  catch (error) {
823
- logger$d(`Error loading model from database: ${error}`);
825
+ logger$e(`Error loading model from database: ${error}`);
824
826
  // Fall through to create new model
825
827
  }
826
828
  }
827
829
  // Step 2: Fallback to Schema context (only if database doesn't have the model)
828
830
  // This handles the case where model exists in schema file but not yet in database
829
831
  try {
830
- const _mod_5 = await import('./Schema-BMXRF4j9.js');
832
+ const _mod_5 = await import('./Schema-C_4wv0RP.js');
831
833
  const _ns_5 = _mod_5.a;
832
834
  const { Schema } = _ns_5;
833
835
  // Stopped meanwhile (unloaded, or evicted with its schema): don't load the schema again
@@ -842,10 +844,10 @@ async function loadModel(context, isStopped) {
842
844
  const byName = schemaContext.models[modelName];
843
845
  const schemaModelId = byName?.id ?? null;
844
846
  if (schemaFileId === schemaModelId) {
845
- logger$d(`Found model "${modelName}" in Schema context (database fallback)`);
847
+ logger$e(`Found model "${modelName}" in Schema context (database fallback)`);
846
848
  if (!schemaFileId) {
847
849
  schemaFileId = generateId();
848
- logger$d(`Generated id (schemaFileId) "${schemaFileId}" for model "${modelName}"`);
850
+ logger$e(`Generated id (schemaFileId) "${schemaFileId}" for model "${modelName}"`);
849
851
  }
850
852
  return { id: schemaFileId };
851
853
  }
@@ -857,7 +859,7 @@ async function loadModel(context, isStopped) {
857
859
  // Schema context may not have model by name yet (e.g. still loading). If our id appears in any schema model, we're the schema model.
858
860
  for (const def of Object.values(schemaContext.models)) {
859
861
  if (def?.id === schemaFileId) {
860
- logger$d(`Found model in Schema context by id "${schemaFileId}" (database fallback)`);
862
+ logger$e(`Found model in Schema context by id "${schemaFileId}" (database fallback)`);
861
863
  return { id: schemaFileId };
862
864
  }
863
865
  }
@@ -865,7 +867,7 @@ async function loadModel(context, isStopped) {
865
867
  }
866
868
  }
867
869
  catch (error) {
868
- logger$d(`Error loading model from Schema context: ${error}`);
870
+ logger$e(`Error loading model from Schema context: ${error}`);
869
871
  // Fall through to create new model
870
872
  }
871
873
  // Step 3: Check for duplicate names in database before creating new model
@@ -885,7 +887,7 @@ async function loadModel(context, isStopped) {
885
887
  // 3. This is an internal schema (internal schemas should preserve original names from schema files), OR
886
888
  // 4. This instance was created with modelFileId from schema (_idFromSchema) - schema models keep their name
887
889
  const shouldSkipDuplicateCheck = modelRecord !== null || foundBySchemaFileId || isInternal || !!_idFromSchema;
888
- logger$d(`Duplicate check: modelRecord=${modelRecord !== null}, foundBySchemaFileId=${foundBySchemaFileId}, isInternal=${isInternal}, _idFromSchema=${!!_idFromSchema}, schemaFileId=${schemaFileId}, shouldSkip=${shouldSkipDuplicateCheck}`);
890
+ logger$e(`Duplicate check: modelRecord=${modelRecord !== null}, foundBySchemaFileId=${foundBySchemaFileId}, isInternal=${isInternal}, _idFromSchema=${!!_idFromSchema}, schemaFileId=${schemaFileId}, shouldSkip=${shouldSkipDuplicateCheck}`);
889
891
  if (!shouldSkipDuplicateCheck) {
890
892
  try {
891
893
  const lowerModelName = modelName.toLowerCase();
@@ -908,20 +910,20 @@ async function loadModel(context, isStopped) {
908
910
  }
909
911
  }
910
912
  catch (error) {
911
- logger$d(`Error checking Model cache for duplicates: ${error}`);
913
+ logger$e(`Error checking Model cache for duplicates: ${error}`);
912
914
  }
913
915
  // Also check database for models (in case they're persisted but not in Schema context)
914
916
  if (db) {
915
- const _mod_6 = await import('./BaseFileManager-DLou1TZS.js');
917
+ const _mod_6 = await import('./BaseFileManager-D-pLXQAp.js');
916
918
  const _ns_6 = _mod_6.M;
917
919
  const modelSchemaSchemaMod = _ns_6;
918
920
  const { modelSchemas } = modelSchemaSchemaMod;
919
- const _mod_7 = await import('./BaseFileManager-DLou1TZS.js');
920
- const _ns_7 = _mod_7.z;
921
+ const _mod_7 = await import('./BaseFileManager-D-pLXQAp.js');
922
+ const _ns_7 = _mod_7.x;
921
923
  const schemaSchemaMod = _ns_7;
922
924
  const { schemas: schemasTable } = schemaSchemaMod;
923
925
  // Query all models for this schema to check for duplicates (case-insensitive)
924
- logger$d(`Checking database for duplicate model names in schema "${schemaName}"`);
926
+ logger$e(`Checking database for duplicate model names in schema "${schemaName}"`);
925
927
  const allModelsForSchema = await db
926
928
  .select({
927
929
  name: models$1.name,
@@ -930,16 +932,16 @@ async function loadModel(context, isStopped) {
930
932
  .innerJoin(modelSchemas, eq(models$1.id, modelSchemas.modelId))
931
933
  .innerJoin(schemasTable, eq(modelSchemas.schemaId, schemasTable.id))
932
934
  .where(eq(schemasTable.name, schemaName));
933
- logger$d(`Found ${allModelsForSchema.length} models in database for schema "${schemaName}": ${allModelsForSchema.map((m) => m.name).join(', ')}`);
935
+ logger$e(`Found ${allModelsForSchema.length} models in database for schema "${schemaName}": ${allModelsForSchema.map((m) => m.name).join(', ')}`);
934
936
  if (allModelsForSchema.length === 0 && modelName.includes('Shared')) {
935
- logger$d(`[DEBUG] No models found in database for schema "${schemaName}" when checking for duplicate "${modelName}"`);
937
+ logger$e(`[DEBUG] No models found in database for schema "${schemaName}" when checking for duplicate "${modelName}"`);
936
938
  }
937
939
  // Check for exact match (case-insensitive) or matches with number suffix
938
940
  for (const dbModel of allModelsForSchema) {
939
941
  const lowerDbName = dbModel.name.toLowerCase();
940
942
  if (lowerDbName === lowerModelName) {
941
943
  existingNumbers.add(0); // Base name exists
942
- logger$d(`Found duplicate in database: "${dbModel.name}" matches "${modelName}"`);
944
+ logger$e(`Found duplicate in database: "${dbModel.name}" matches "${modelName}"`);
943
945
  }
944
946
  else if (lowerDbName.startsWith(lowerModelName + ' ')) {
945
947
  // Check if it's the base name followed by a space and a number
@@ -947,55 +949,55 @@ async function loadModel(context, isStopped) {
947
949
  const number = parseInt(suffix, 10);
948
950
  if (!isNaN(number) && suffix === number.toString()) {
949
951
  existingNumbers.add(number);
950
- logger$d(`Found numbered variant in database: "${dbModel.name}" (number: ${number})`);
952
+ logger$e(`Found numbered variant in database: "${dbModel.name}" (number: ${number})`);
951
953
  }
952
954
  }
953
955
  }
954
956
  }
955
957
  // If duplicates found, generate unique name
956
- logger$d(`Duplicate check results: existingNumbers=${Array.from(existingNumbers).join(', ')}, hasBaseName=${existingNumbers.has(0)}`);
958
+ logger$e(`Duplicate check results: existingNumbers=${Array.from(existingNumbers).join(', ')}, hasBaseName=${existingNumbers.has(0)}`);
957
959
  if (existingNumbers.has(0)) {
958
960
  let nextNumber = 1;
959
961
  while (existingNumbers.has(nextNumber)) {
960
962
  nextNumber++;
961
963
  }
962
964
  finalModelName = `${modelName} ${nextNumber}`;
963
- logger$d(`Found duplicate model name "${modelName}" in schema "${schemaName}", using unique name "${finalModelName}"`);
965
+ logger$e(`Found duplicate model name "${modelName}" in schema "${schemaName}", using unique name "${finalModelName}"`);
964
966
  }
965
967
  else {
966
- logger$d(`No duplicate found for model name "${modelName}" in schema "${schemaName}"`);
968
+ logger$e(`No duplicate found for model name "${modelName}" in schema "${schemaName}"`);
967
969
  }
968
970
  }
969
971
  catch (error) {
970
- logger$d(`Error checking for duplicate model names: ${error}`);
972
+ logger$e(`Error checking for duplicate model names: ${error}`);
971
973
  // Continue with original name if check fails
972
974
  }
973
975
  }
974
976
  else {
975
977
  if (foundBySchemaFileId) {
976
- logger$d(`Preserving original model name "${modelName}" for model found by schemaFileId (schemaFileId: ${schemaFileId})`);
978
+ logger$e(`Preserving original model name "${modelName}" for model found by schemaFileId (schemaFileId: ${schemaFileId})`);
977
979
  }
978
980
  else if (modelRecord) {
979
- logger$d(`Using existing model name "${modelName}" from database (modelRecord found)`);
981
+ logger$e(`Using existing model name "${modelName}" from database (modelRecord found)`);
980
982
  }
981
983
  else if (isInternal) {
982
- logger$d(`Preserving original model name "${modelName}" for internal schema "${schemaName}" (skipping duplicate check)`);
984
+ logger$e(`Preserving original model name "${modelName}" for internal schema "${schemaName}" (skipping duplicate check)`);
983
985
  }
984
986
  }
985
987
  }
986
988
  // Step 4: Create new empty model (not found in database or Schema context)
987
- logger$d(`Creating new model "${finalModelName}" in schema "${schemaName}"`);
989
+ logger$e(`Creating new model "${finalModelName}" in schema "${schemaName}"`);
988
990
  // Generate schemaFileId for new model
989
991
  if (!schemaFileId) {
990
992
  schemaFileId = generateId();
991
- logger$d(`Generated id (schemaFileId) "${schemaFileId}" for new model "${finalModelName}"`);
993
+ logger$e(`Generated id (schemaFileId) "${schemaFileId}" for new model "${finalModelName}"`);
992
994
  }
993
995
  if (finalModelName !== modelName) {
994
- logger$d(`Model name "${modelName}" is taken in schema "${schemaName}"; using "${finalModelName}"`);
996
+ logger$e(`Model name "${modelName}" is taken in schema "${schemaName}"; using "${finalModelName}"`);
995
997
  }
996
998
  // Mark schema as draft when a new model is created so saveNewVersion() can persist it
997
999
  try {
998
- const _mod_8 = await import('./Schema-BMXRF4j9.js');
1000
+ const _mod_8 = await import('./Schema-C_4wv0RP.js');
999
1001
  const _ns_8 = _mod_8.a;
1000
1002
  const { Schema } = _ns_8;
1001
1003
  if (!isStopped()) {
@@ -1004,7 +1006,7 @@ async function loadModel(context, isStopped) {
1004
1006
  }
1005
1007
  }
1006
1008
  catch (err) {
1007
- logger$d(`Failed to mark schema as draft after creating model: ${err}`);
1009
+ logger$e(`Failed to mark schema as draft after creating model: ${err}`);
1008
1010
  }
1009
1011
  return {
1010
1012
  id: schemaFileId,
@@ -1084,12 +1086,12 @@ function queueDraftSave(key, save) {
1084
1086
  return current;
1085
1087
  }
1086
1088
 
1087
- const logger$c = debug('seedSdk:model:Model');
1089
+ const logger$d = debug('seedSdk:model:Model');
1088
1090
  const saveDraftLogger = debug('seedSdk:model:saveDraftToDb');
1089
1091
  // Lazy import cache for ModelProperty to avoid circular dependency
1090
1092
  // Eagerly start loading to minimize delay on first access
1091
1093
  let ModelPropertyClass = null;
1092
- const modelPropertyImportPromise = import('./ModelProperty-D2vJozmW.js')
1094
+ const modelPropertyImportPromise = import('./ModelProperty-Bv7KzP97.js')
1093
1095
  .then(module => {
1094
1096
  ModelPropertyClass = module.ModelProperty;
1095
1097
  return ModelPropertyClass;
@@ -1240,7 +1242,7 @@ class Model {
1240
1242
  });
1241
1243
  }
1242
1244
  else {
1243
- logger$c(`Model "${context.modelName}" adopts model row ${dbId} written by its schema import`);
1245
+ logger$d(`Model "${context.modelName}" adopts model row ${dbId} written by its schema import`);
1244
1246
  }
1245
1247
  ensureRunning();
1246
1248
  service.send({ type: 'updateContext', _dbId: dbId });
@@ -1341,7 +1343,7 @@ class Model {
1341
1343
  if (cachedById) {
1342
1344
  if (holds)
1343
1345
  this.cache.holdInstance(cachedById);
1344
- logger$c(`Model.create: Found instance in ID cache for "${modelName}" (ID: ${id})`);
1346
+ logger$d(`Model.create: Found instance in ID cache for "${modelName}" (ID: ${id})`);
1345
1347
  if (!waitForReady)
1346
1348
  return cachedById;
1347
1349
  return Model.readyOrRelease(cachedById, readyTimeout, holds);
@@ -1350,7 +1352,7 @@ class Model {
1350
1352
  // a schema model (with id) keeps its name.
1351
1353
  const uniqueModelName = this.findUniqueModelName(modelName, schemaName, !!id);
1352
1354
  if (uniqueModelName !== modelName) {
1353
- logger$c(`Model.create: Found duplicate name "${modelName}" in schema "${schemaName}", using unique name "${uniqueModelName}"`);
1355
+ logger$d(`Model.create: Found duplicate name "${modelName}" in schema "${schemaName}", using unique name "${uniqueModelName}"`);
1354
1356
  }
1355
1357
  const resolvedId = id ?? generateId();
1356
1358
  // The id and the properties to create go in the initial context, so the load sees them
@@ -1360,7 +1362,7 @@ class Model {
1360
1362
  // Use the unique model name for the instance (idFromSchema: true when id was provided, so schema model is not renamed)
1361
1363
  const newInstance = new this(uniqueModelName, schemaName, resolvedId, initialContext, !!id);
1362
1364
  if (options?.properties) {
1363
- logger$c(`[Model.create] Created instance with _pendingPropertyDefinitions in initial context (${Object.keys(options.properties).length} properties) for model "${uniqueModelName}"`);
1365
+ logger$d(`[Model.create] Created instance with _pendingPropertyDefinitions in initial context (${Object.keys(options.properties).length} properties) for model "${uniqueModelName}"`);
1364
1366
  }
1365
1367
  // Wrap instance in Proxy for reactive property access. `name` is an alias for `modelName`.
1366
1368
  const sendUpdate = (target, prop, value) => {
@@ -1371,10 +1373,10 @@ class Model {
1371
1373
  const oldName = context.modelName;
1372
1374
  const newName = value;
1373
1375
  if (oldName === newName) {
1374
- logger$c(`Model name unchanged: "${oldName}"`);
1376
+ logger$d(`Model name unchanged: "${oldName}"`);
1375
1377
  return;
1376
1378
  }
1377
- logger$c(`Updating model name from "${oldName}" to "${newName}"`);
1379
+ logger$d(`Updating model name from "${oldName}" to "${newName}"`);
1378
1380
  Model.cache.setKeys(target, [context.id, modelKey(context.schemaName, newName)]);
1379
1381
  // Update Model context
1380
1382
  target._service.send({
@@ -1430,7 +1432,7 @@ class Model {
1430
1432
  this.cache.add(proxiedInstance, [resolvedId, id ? modelKey(schemaName, uniqueModelName) : undefined], {
1431
1433
  hold: holds,
1432
1434
  });
1433
- logger$c(`Model.create: Created new instance for "${uniqueModelName}" (ID: ${id})`);
1435
+ logger$d(`Model.create: Created new instance for "${uniqueModelName}" (ID: ${id})`);
1434
1436
  // Step 11: Register with schema if requested, OR trigger write if properties are provided
1435
1437
  // If properties are provided, we need to write the model to get modelId for property creation
1436
1438
  // If schema provided, trigger write process instead of registration
@@ -1455,7 +1457,7 @@ class Model {
1455
1457
  adoptExistingRow,
1456
1458
  });
1457
1459
  if (!hasProperties) {
1458
- persisted.catch((error) => logger$c(`Model "${uniqueModelName}" was not written: ${error}`));
1460
+ persisted.catch((error) => logger$d(`Model "${uniqueModelName}" was not written: ${error}`));
1459
1461
  return;
1460
1462
  }
1461
1463
  await persisted.catch((error) => {
@@ -1598,8 +1600,8 @@ class Model {
1598
1600
  if (db) {
1599
1601
  const since = evictionLog.now();
1600
1602
  try {
1601
- const _mod_9 = await import('./tabEvents-DT5ez6io.js');
1602
- const _ns_9 = _mod_9.bh;
1603
+ const _mod_9 = await import('./Item-BU9wIT7l.js');
1604
+ const _ns_9 = _mod_9.bP;
1603
1605
  const { resolveModelRecord, getModelSchemaName } = _ns_9;
1604
1606
  const resolved = scope.modelFileId
1605
1607
  ? await resolveModelRecord(undefined, { modelFileId: scope.modelFileId }, db)
@@ -1617,7 +1619,7 @@ class Model {
1617
1619
  }
1618
1620
  }
1619
1621
  catch (error) {
1620
- logger$c(`Model.resolveAsync: scoped lookup failed for "${modelName}": ${error instanceof Error ? error.message : String(error)}`);
1622
+ logger$d(`Model.resolveAsync: scoped lookup failed for "${modelName}": ${error instanceof Error ? error.message : String(error)}`);
1621
1623
  }
1622
1624
  }
1623
1625
  }
@@ -1645,8 +1647,8 @@ class Model {
1645
1647
  }
1646
1648
  const since = options?.since ?? evictionLog.now();
1647
1649
  try {
1648
- const _mod_10 = await import('./tabEvents-DT5ez6io.js');
1649
- const _ns_10 = _mod_10.bh;
1650
+ const _mod_10 = await import('./Item-BU9wIT7l.js');
1651
+ const _ns_10 = _mod_10.bP;
1650
1652
  const { resolveModelRecord, getModelSchemaName } = _ns_10;
1651
1653
  const resolved = await resolveModelRecord(modelName, schemaName ? { schemaName, strict: true } : {}, db);
1652
1654
  if (!resolved?.schemaFileId) {
@@ -1669,7 +1671,7 @@ class Model {
1669
1671
  catch (error) {
1670
1672
  if (error instanceof AmbiguousModelError)
1671
1673
  throw error;
1672
- logger$c(`Model.getByNameAsync: Error looking up model by name "${modelName}": ${error instanceof Error ? error.message : String(error)}`);
1674
+ logger$d(`Model.getByNameAsync: Error looking up model by name "${modelName}": ${error instanceof Error ? error.message : String(error)}`);
1673
1675
  return undefined;
1674
1676
  }
1675
1677
  }
@@ -1687,13 +1689,13 @@ class Model {
1687
1689
  // Step 1: Check cache first (fast path)
1688
1690
  const cached = this.getById(modelFileId, options);
1689
1691
  if (cached) {
1690
- logger$c(`Model.createById: Found instance in cache for ID "${modelFileId}"`);
1692
+ logger$d(`Model.createById: Found instance in cache for ID "${modelFileId}"`);
1691
1693
  return cached;
1692
1694
  }
1693
1695
  // Step 2: Query database to get modelName and schemaName
1694
1696
  const db = BaseDb.getAppDb();
1695
1697
  if (!db) {
1696
- logger$c(`Model.createById: Database not available for ID "${modelFileId}"`);
1698
+ logger$d(`Model.createById: Database not available for ID "${modelFileId}"`);
1697
1699
  return undefined;
1698
1700
  }
1699
1701
  const since = options?.since ?? evictionLog.now();
@@ -1711,30 +1713,30 @@ class Model {
1711
1713
  .where(eq(models$1.schemaFileId, modelFileId))
1712
1714
  .limit(1);
1713
1715
  if (modelRecords.length === 0) {
1714
- logger$c(`Model.createById: Model not found in database for ID "${modelFileId}"`);
1716
+ logger$d(`Model.createById: Model not found in database for ID "${modelFileId}"`);
1715
1717
  return undefined;
1716
1718
  }
1717
1719
  const { modelName, schemaName } = modelRecords[0];
1718
1720
  if (!modelName || !schemaName) {
1719
- logger$c(`Model.createById: Missing modelName or schemaName for ID "${modelFileId}"`);
1721
+ logger$d(`Model.createById: Missing modelName or schemaName for ID "${modelFileId}"`);
1720
1722
  return undefined;
1721
1723
  }
1722
1724
  // Its schema was evicted while we read the row (e.g. a Schema live query in flight during
1723
1725
  // Schema.destroy or test cleanup): don't bring the model back.
1724
1726
  // or the model was destroyed (destroy() leaves the cache before it deletes the rows)
1725
1727
  if (evictionLog.evictedSince([`schema:${schemaName}`, `model:${modelFileId}`], since)) {
1726
- logger$c(`Model.createById: "${modelFileId}" or its schema "${schemaName}" was evicted during the lookup`);
1728
+ logger$d(`Model.createById: "${modelFileId}" or its schema "${schemaName}" was evicted during the lookup`);
1727
1729
  return undefined;
1728
1730
  }
1729
1731
  // Step 3: Create model using existing create method (which will handle caching)
1730
- logger$c(`Model.createById: Creating model "${modelName}" in schema "${schemaName}" from ID "${modelFileId}"`);
1732
+ logger$d(`Model.createById: Creating model "${modelName}" in schema "${schemaName}" from ID "${modelFileId}"`);
1731
1733
  return this.create(modelName, schemaName, {
1732
1734
  id: modelFileId, // id is now the schemaFileId (string)
1733
1735
  hold: options?.hold,
1734
1736
  });
1735
1737
  }
1736
1738
  catch (error) {
1737
- logger$c(`Model.createById: Error looking up model by ID "${modelFileId}": ${error instanceof Error ? error.message : String(error)}`);
1739
+ logger$d(`Model.createById: Error looking up model by ID "${modelFileId}": ${error instanceof Error ? error.message : String(error)}`);
1738
1740
  return undefined;
1739
1741
  }
1740
1742
  }
@@ -1776,7 +1778,7 @@ class Model {
1776
1778
  }
1777
1779
  const db = BaseDb.getAppDb();
1778
1780
  if (!db) {
1779
- logger$c(`Model.createBySchemaId: Database not available for schema "${schemaIdentifier}"`);
1781
+ logger$d(`Model.createBySchemaId: Database not available for schema "${schemaIdentifier}"`);
1780
1782
  return [];
1781
1783
  }
1782
1784
  const since = evictionLog.now();
@@ -1793,7 +1795,7 @@ class Model {
1793
1795
  .innerJoin(models$1, eq(modelSchemas.modelId, models$1.id))
1794
1796
  .where(or(eq(schemas.schemaFileId, schemaIdentifier), eq(schemas.name, schemaIdentifier)));
1795
1797
  if (modelRecords.length === 0) {
1796
- logger$c(`Model.createBySchemaId: No models found in database for schema "${schemaIdentifier}"`);
1798
+ logger$d(`Model.createBySchemaId: No models found in database for schema "${schemaIdentifier}"`);
1797
1799
  return [];
1798
1800
  }
1799
1801
  // Create Model instances for each model (create() handles caching)
@@ -1821,11 +1823,11 @@ class Model {
1821
1823
  modelInstances.push(model);
1822
1824
  }
1823
1825
  }
1824
- logger$c(`Model.createBySchemaId: Found ${modelInstances.length} models for schema "${schemaIdentifier}"`);
1826
+ logger$d(`Model.createBySchemaId: Found ${modelInstances.length} models for schema "${schemaIdentifier}"`);
1825
1827
  return modelInstances;
1826
1828
  }
1827
1829
  catch (error) {
1828
- logger$c(`Model.createBySchemaId: Error looking up models for schema "${schemaIdentifier}": ${error instanceof Error ? error.message : String(error)}`);
1830
+ logger$d(`Model.createBySchemaId: Error looking up models for schema "${schemaIdentifier}": ${error instanceof Error ? error.message : String(error)}`);
1829
1831
  return [];
1830
1832
  }
1831
1833
  }
@@ -1874,7 +1876,7 @@ class Model {
1874
1876
  // Leave out models that errored or didn't get ready in time. Don't re-read their state afterwards:
1875
1877
  // a ready model can already be validating again, and it still belongs in the result.
1876
1878
  const ready = await Promise.all(instances.map((m) => waitForEntityReady(m, { timeout: readyTimeout }).then(() => true, (error) => {
1877
- logger$c(`Model.all: leaving out a model that isn't ready: ${error instanceof Error ? error.message : String(error)}`);
1879
+ logger$d(`Model.all: leaving out a model that isn't ready: ${error instanceof Error ? error.message : String(error)}`);
1878
1880
  return false;
1879
1881
  })));
1880
1882
  return held(instances.filter((_, i) => ready[i]));
@@ -1929,7 +1931,7 @@ class Model {
1929
1931
  return { hasConflict: false };
1930
1932
  }
1931
1933
  catch (error) {
1932
- logger$c(`Error checking for conflicts: ${error instanceof Error ? error.message : String(error)}`);
1934
+ logger$d(`Error checking for conflicts: ${error instanceof Error ? error.message : String(error)}`);
1933
1935
  return { hasConflict: false }; // On error, assume no conflict to allow save
1934
1936
  }
1935
1937
  }
@@ -2093,7 +2095,7 @@ class Model {
2093
2095
  */
2094
2096
  async reload() {
2095
2097
  this._assertLoaded('reload');
2096
- logger$c(`Reloading model ${this.modelName} from database`);
2098
+ logger$d(`Reloading model ${this.modelName} from database`);
2097
2099
  await runEntityLoad(this, () => Model.load(this));
2098
2100
  if (this.status === 'error')
2099
2101
  throw new Error('Failed to reload model from database');
@@ -2146,9 +2148,9 @@ class Model {
2146
2148
  .limit(1);
2147
2149
  if (modelRecord) {
2148
2150
  const modelId = modelRecord.id;
2149
- const { detachModelRows } = await import('./detachRowsBeforeDelete-BZr_0iZU.js');
2150
- const _mod_11 = await import('./tabEvents-DT5ez6io.js');
2151
- const _ns_11 = _mod_11.bj;
2151
+ const { detachModelRows } = await import('./detachRowsBeforeDelete-Cie55SoG.js');
2152
+ const _mod_11 = await import('./Item-BU9wIT7l.js');
2153
+ const _ns_11 = _mod_11.bR;
2152
2154
  const { inDefinitionsTransaction } = _ns_11;
2153
2155
  // The model's rows go together or not at all
2154
2156
  await inDefinitionsTransaction(async (tx) => {
@@ -2164,7 +2166,7 @@ class Model {
2164
2166
  getModelProperty()?.evictForModel({ modelName, schemaName, modelId: dbId });
2165
2167
  if (schemaName && modelName) {
2166
2168
  // Imported here, not at module load: Model and Schema import each other.
2167
- const _mod_12 = await import('./Schema-BMXRF4j9.js');
2169
+ const _mod_12 = await import('./Schema-C_4wv0RP.js');
2168
2170
  const _ns_12 = _mod_12.a;
2169
2171
  const { Schema } = _ns_12;
2170
2172
  const schema = await Schema.find({ schemaName, waitForReady: false });
@@ -2203,8 +2205,8 @@ class Model {
2203
2205
  return undefined;
2204
2206
  }
2205
2207
  // Model names are only unique per schema: find this model's row by its id (schemaFileId).
2206
- const _mod_13 = await import('./tabEvents-DT5ez6io.js');
2207
- const _ns_13 = _mod_13.bh;
2208
+ const _mod_13 = await import('./Item-BU9wIT7l.js');
2209
+ const _ns_13 = _mod_13.bP;
2208
2210
  const { resolveModelRecord } = _ns_13;
2209
2211
  const modelRecord = await resolveModelRecord(modelName, { modelFileId: context.id, schemaName: context.schemaName }, db);
2210
2212
  if (!modelRecord?.id) {
@@ -2226,7 +2228,7 @@ class Model {
2226
2228
  });
2227
2229
  }
2228
2230
  catch (error) {
2229
- logger$c(`[Model._setupLiveQuerySubscription] Error finding model: ${error}`);
2231
+ logger$d(`[Model._setupLiveQuerySubscription] Error finding model: ${error}`);
2230
2232
  return undefined;
2231
2233
  }
2232
2234
  }
@@ -2253,7 +2255,7 @@ class Model {
2253
2255
  await modelPropertyImportPromise;
2254
2256
  const ModelProperty = getModelProperty();
2255
2257
  if (!ModelProperty) {
2256
- logger$c('[Model._setupLiveQuerySubscription] ModelProperty not yet loaded');
2258
+ logger$d('[Model._setupLiveQuerySubscription] ModelProperty not yet loaded');
2257
2259
  return;
2258
2260
  }
2259
2261
  const createPromises = ids.map(async (propertyFileId) => {
@@ -2261,7 +2263,7 @@ class Model {
2261
2263
  await ModelProperty.createById(propertyFileId);
2262
2264
  }
2263
2265
  catch (error) {
2264
- logger$c(`[Model._setupLiveQuerySubscription] Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
2266
+ logger$d(`[Model._setupLiveQuerySubscription] Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
2265
2267
  }
2266
2268
  });
2267
2269
  await Promise.all(createPromises);
@@ -2314,28 +2316,28 @@ class Model {
2314
2316
  await modelPropertyImportPromise;
2315
2317
  const ModelProperty = getModelProperty();
2316
2318
  if (!ModelProperty) {
2317
- logger$c('[Model._refreshPropertiesFromDb] ModelProperty not yet loaded');
2319
+ logger$d('[Model._refreshPropertiesFromDb] ModelProperty not yet loaded');
2318
2320
  }
2319
2321
  else {
2320
2322
  const createPromises = propertyIds.map(async (propertyFileId) => {
2321
2323
  try {
2322
2324
  const property = await ModelProperty.createById(propertyFileId);
2323
2325
  if (property) {
2324
- logger$c(`[Model._refreshPropertiesFromDb] Created/cached ModelProperty instance for propertyFileId "${propertyFileId}"`);
2326
+ logger$d(`[Model._refreshPropertiesFromDb] Created/cached ModelProperty instance for propertyFileId "${propertyFileId}"`);
2325
2327
  }
2326
2328
  else {
2327
- logger$c(`[Model._refreshPropertiesFromDb] ModelProperty.createById returned undefined for propertyFileId "${propertyFileId}"`);
2329
+ logger$d(`[Model._refreshPropertiesFromDb] ModelProperty.createById returned undefined for propertyFileId "${propertyFileId}"`);
2328
2330
  }
2329
2331
  }
2330
2332
  catch (error) {
2331
- logger$c(`[Model._refreshPropertiesFromDb] Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
2333
+ logger$d(`[Model._refreshPropertiesFromDb] Error creating ModelProperty instance for propertyFileId "${propertyFileId}": ${error}`);
2332
2334
  }
2333
2335
  });
2334
2336
  await Promise.all(createPromises);
2335
2337
  }
2336
2338
  }
2337
2339
  catch (error) {
2338
- logger$c(`[Model._refreshPropertiesFromDb] Error importing ModelProperty or creating instances: ${error}`);
2340
+ logger$d(`[Model._refreshPropertiesFromDb] Error importing ModelProperty or creating instances: ${error}`);
2339
2341
  }
2340
2342
  }
2341
2343
  // Update context with refreshed property IDs AFTER creating instances (the awaits above can
@@ -2349,7 +2351,7 @@ class Model {
2349
2351
  });
2350
2352
  }
2351
2353
  catch (error) {
2352
- logger$c(`[Model._refreshPropertiesFromDb] Error refreshing properties: ${error}`);
2354
+ logger$d(`[Model._refreshPropertiesFromDb] Error refreshing properties: ${error}`);
2353
2355
  }
2354
2356
  }
2355
2357
  /**
@@ -2382,7 +2384,6 @@ var Model$1 = /*#__PURE__*/Object.freeze({
2382
2384
  });
2383
2385
 
2384
2386
  // Export the Model class
2385
- const TModelValues = Type.Record(Type.String(), Type.Any());
2386
2387
  const TModelSchema = Type.Record(Type.String(), TProperty);
2387
2388
 
2388
2389
  let writeTail = Promise.resolve();
@@ -2585,7 +2586,7 @@ function toRunSyncArg(options) {
2585
2586
  return undefined;
2586
2587
  }
2587
2588
  const runEasSync = fromPromise(async ({ input, }) => {
2588
- const { runSyncFromEas } = await import('./syncDbWithEas-FuyqoEKk.js');
2589
+ const { runSyncFromEas } = await import('./syncDbWithEas-V_9gIyHp.js');
2589
2590
  // Sync checks for rows and then inserts the rest (no unique index on uids), so syncs in
2590
2591
  // different tabs take turns. A sync can run for minutes, so wait without a timeout.
2591
2592
  await withDbTabLock('eas-sync', async () => {
@@ -2901,14 +2902,14 @@ function applySeedConfigDefaults(config) {
2901
2902
  return { ...config, filesDir, endpoints: next };
2902
2903
  }
2903
2904
 
2904
- const logger$b = debug('seedSdk:ClientManager:initialize');
2905
+ const logger$c = debug('seedSdk:ClientManager:initialize');
2905
2906
  /**
2906
2907
  * The first init step: configures the platform services, EAS reads and the Arweave gateway from
2907
2908
  * the init options, starts tab coordination and the item event handlers, and returns the context
2908
2909
  * the following steps read.
2909
2910
  */
2910
2911
  async function platformClassesInit({ event }) {
2911
- logger$b('initialize from ClientManager');
2912
+ logger$c('initialize from ClientManager');
2912
2913
  if (!event || !('options' in event)) {
2913
2914
  throw new Error('Initialize event must include options');
2914
2915
  }
@@ -2918,7 +2919,7 @@ async function platformClassesInit({ event }) {
2918
2919
  if (!config) {
2919
2920
  throw new Error('client.init requires a config');
2920
2921
  }
2921
- const { configurePlatform } = await import('./configurePlatform-WkmvqeTO.js');
2922
+ const { configurePlatform } = await import('./configurePlatform-BH1YOZpg.js');
2922
2923
  if (isBrowser()) {
2923
2924
  const { createPlatformServices } = await import('./platform.browser.js');
2924
2925
  configurePlatform(createPlatformServices());
@@ -2994,17 +2995,12 @@ async function platformClassesInit({ event }) {
2994
2995
  };
2995
2996
  }
2996
2997
 
2997
- const logger$a = debug('seedSdk:client:writeToDb');
2998
+ const logger$b = debug('seedSdk:client:writeToDb');
2998
2999
  const saveAppState = fromCallback(({ sendBack, input: { key, value } }) => {
2999
3000
  const _saveAppState = async () => {
3000
3001
  try {
3001
3002
  const appDb = BaseDb.getAppDb();
3002
3003
  if (!appDb) {
3003
- // In test environments, continue anyway
3004
- if (isTestOrSeedDev()) {
3005
- logger$a('[client/actors] [saveAppState] App DB not found, but continuing in test environment');
3006
- return;
3007
- }
3008
3004
  throw new Error('App DB not found');
3009
3005
  }
3010
3006
  const result = await appDb.insert(appState)
@@ -3018,15 +3014,10 @@ const saveAppState = fromCallback(({ sendBack, input: { key, value } }) => {
3018
3014
  value: JSON.stringify(value),
3019
3015
  },
3020
3016
  });
3021
- logger$a('result', result);
3017
+ logger$b('result', result);
3022
3018
  }
3023
3019
  catch (error) {
3024
- logger$a('[client/actors] [saveAppState] Error saving app state:', error);
3025
- // In test environments, continue anyway
3026
- if (isTestOrSeedDev()) {
3027
- logger$a('[client/actors] [saveAppState] Continuing despite error in test environment');
3028
- return;
3029
- }
3020
+ logger$b('[client/actors] [saveAppState] Error saving app state:', error);
3030
3021
  throw error;
3031
3022
  }
3032
3023
  };
@@ -3039,24 +3030,13 @@ const saveAppState = fromCallback(({ sendBack, input: { key, value } }) => {
3039
3030
  });
3040
3031
  })
3041
3032
  .catch((error) => {
3042
- logger$a('[client/actors] [saveAppState] Error in promise chain:', error);
3043
- // In test environments, still send success to allow state machine to progress
3044
- if (isTestOrSeedDev()) {
3045
- logger$a('[client/actors] [saveAppState] Sending success despite error in test environment');
3046
- sendBack({
3047
- type: 'saveAppStateSuccess',
3048
- key,
3049
- value,
3050
- });
3051
- }
3052
- else {
3053
- sendBack({
3054
- type: 'saveAppStateError',
3055
- error: error instanceof Error ? error : new Error(String(error)),
3056
- key,
3057
- value,
3058
- });
3059
- }
3033
+ logger$b('[client/actors] [saveAppState] Error in promise chain:', error);
3034
+ sendBack({
3035
+ type: 'saveAppStateError',
3036
+ error: error instanceof Error ? error : new Error(String(error)),
3037
+ key,
3038
+ value,
3039
+ });
3060
3040
  });
3061
3041
  });
3062
3042
 
@@ -3068,7 +3048,7 @@ async function fileSystemInit({ filesDir }) {
3068
3048
  await BaseFileManager.initializeFileSystem(filesDir);
3069
3049
  }
3070
3050
 
3071
- const logger$9 = debug('seedSdk:client:actors:dbInit');
3051
+ const logger$a = debug('seedSdk:client:actors:dbInit');
3072
3052
  /** Init step: opens and migrates the app database (BaseDb.prepareDb). */
3073
3053
  async function dbInit({ filesDir, dbConfig }) {
3074
3054
  if (!filesDir) {
@@ -3078,7 +3058,7 @@ async function dbInit({ filesDir, dbConfig }) {
3078
3058
  if (!BaseDb.getAppDb()) {
3079
3059
  throw new Error('Database not available after preparation');
3080
3060
  }
3081
- logger$9('Database prepared and ready');
3061
+ logger$a('Database prepared and ready');
3082
3062
  }
3083
3063
 
3084
3064
  /**
@@ -3136,7 +3116,7 @@ const localModelSchemaLookups = async (modelNames) => {
3136
3116
  return names.filter((name) => name !== 'Seed').map((name) => ({ name, level: levels.model(name) }));
3137
3117
  };
3138
3118
 
3139
- const logger$8 = debug('seedSdk:client:actors:addModelsToDb');
3119
+ const logger$9 = debug('seedSdk:client:actors:addModelsToDb');
3140
3120
  /**
3141
3121
  * Returns every `models` row named in `modelNames`, inserting a null-schemaFileId stub for each name
3142
3122
  * with no row yet (the schema import adopts stubs via findOrCreateModelRow). Check-then-insert:
@@ -3185,7 +3165,7 @@ async function addModelsToDb(context) {
3185
3165
  for (const modelName of modelNames) {
3186
3166
  const foundModel = rowFor(modelName);
3187
3167
  if (!foundModel) {
3188
- logger$8('[client/actors] [addModelsToDb] Warning: Could not find or create model:', modelName);
3168
+ logger$9('[client/actors] [addModelsToDb] Warning: Could not find or create model:', modelName);
3189
3169
  hasModelsInDb = false;
3190
3170
  continue;
3191
3171
  }
@@ -3193,7 +3173,7 @@ async function addModelsToDb(context) {
3193
3173
  }
3194
3174
  // If we have no models to process, still send success (empty models is valid)
3195
3175
  if (modelNamesWithRows.size === 0) {
3196
- logger$8('[client/actors] [addModelsToDb] No models to process, but continuing');
3176
+ logger$9('[client/actors] [addModelsToDb] No models to process, but continuing');
3197
3177
  return;
3198
3178
  }
3199
3179
  // Fetch schemas from EAS in background - do not block init. EAS can be slow (10s+);
@@ -3205,7 +3185,7 @@ async function addModelsToDb(context) {
3205
3185
  timeout = setTimeout(() => reject(new Error('EAS query timeout after 10 seconds')), 10000);
3206
3186
  });
3207
3187
  // The lookup and the model_uids rule sync uses too (each name at its nameMatch level).
3208
- const { getModelSchemasFromEas } = await import('./eas-FZPPqxqe.js');
3188
+ const { getModelSchemasFromEas } = await import('./eas-MXEdKKVc.js');
3209
3189
  const lookups = await localModelSchemaLookups([...modelNamesWithRows]);
3210
3190
  // Cleared, so a finished lookup doesn't keep a Node process alive for 10 s.
3211
3191
  const schemas = await Promise.race([getModelSchemasFromEas(lookups), timeoutPromise]).finally(() => clearTimeout(timeout));
@@ -3217,16 +3197,11 @@ async function addModelsToDb(context) {
3217
3197
  requestEasSyncFromModelsInit();
3218
3198
  }
3219
3199
  else {
3220
- logger$8('[client/actors] [addModelsToDb] No schemas found from EAS, but continuing');
3200
+ logger$9('[client/actors] [addModelsToDb] No schemas found from EAS, but continuing');
3221
3201
  }
3222
3202
  }
3223
3203
  catch (error) {
3224
- if (isTestOrSeedDev()) {
3225
- logger$8('[client/actors] [addModelsToDb] Warning: Could not fetch schemas from EAS (background):', error.message);
3226
- }
3227
- else {
3228
- logger$8('[client/actors] [addModelsToDb] Error fetching schemas (background):', error.message);
3229
- }
3204
+ logger$9('[client/actors] [addModelsToDb] Error fetching schemas (background):', error.message);
3230
3205
  // Probably offline: the startup sync waits for this lookup, so try again.
3231
3206
  modelUidsRetry.schedule();
3232
3207
  }
@@ -3240,18 +3215,16 @@ async function addModelsToDb(context) {
3240
3215
  await _addModelsToDb();
3241
3216
  // Model.findByModelType reads the nameMatch levels synchronously: have them before init is ready.
3242
3217
  await loadNameMatchLevels().catch((error) => {
3243
- logger$8('[client/actors] [addModelsToDb] Could not load nameMatch levels:', error);
3218
+ logger$9('[client/actors] [addModelsToDb] Could not load nameMatch levels:', error);
3244
3219
  });
3245
3220
  }
3246
3221
  catch (error) {
3247
- logger$8('[client/actors] [addModelsToDb] Error:', error);
3248
- // In test environments, initialization completes anyway
3249
- if (!isTestOrSeedDev())
3250
- throw error;
3222
+ logger$9('[client/actors] [addModelsToDb] Error:', error);
3223
+ throw error;
3251
3224
  }
3252
3225
  }
3253
3226
 
3254
- const logger$7 = debug('seedSdk:helpers:localDbChain');
3227
+ const logger$8 = debug('seedSdk:helpers:localDbChain');
3255
3228
  /** appState key recording which EAS chain the local database holds attestations from. */
3256
3229
  const EAS_CHAIN_ID_APP_STATE_KEY = 'easChainId';
3257
3230
  /**
@@ -3321,7 +3294,7 @@ async function loadLocalDbChain() {
3321
3294
  const localChainId = await resolveLocalDbChainId(appDb);
3322
3295
  if (localChainId !== undefined) {
3323
3296
  configureEasReadChain('localDb', { chainId: localChainId });
3324
- logger$7('local DB holds chain %d', localChainId);
3297
+ logger$8('local DB holds chain %d', localChainId);
3325
3298
  return;
3326
3299
  }
3327
3300
  if (isEasReadChainConfigured()) {
@@ -3370,7 +3343,7 @@ function resetAddressesPersistedAnnouncement() {
3370
3343
  lastAnnounced = undefined;
3371
3344
  }
3372
3345
 
3373
- const logger$6 = debug('seedSdk:client:actors:saveConfig');
3346
+ const logger$7 = debug('seedSdk:client:actors:saveConfig');
3374
3347
  let warnedKeptStoredAddresses = false;
3375
3348
  /**
3376
3349
  * Saves the addresses passed to init.
@@ -3415,22 +3388,17 @@ async function persistInitAddresses(appDb, { addresses, addressesProvided, owned
3415
3388
  * checks the database's chain against the configured one.
3416
3389
  */
3417
3390
  async function saveConfig(context) {
3418
- logger$6('saveConfig starting');
3391
+ logger$7('saveConfig starting');
3419
3392
  const { endpoints, addresses, addressesProvided, ownedAddresses, watchedAddresses, arweaveDomain, uploadApiBaseUrl, gatewayTransport, gatewayHyperKey, gatewaySidecarHost, gatewaySidecarPort, gatewayProxyBaseUrl, } = context;
3420
3393
  // Validate endpoints - required for proper initialization
3421
3394
  if (!endpoints || !endpoints.filePaths || !endpoints.files) {
3422
- logger$6('[internal/actors] [saveConfig] Invalid endpoints:', { endpoints });
3395
+ logger$7('[internal/actors] [saveConfig] Invalid endpoints:', { endpoints });
3423
3396
  throw new Error('saveConfig called with invalid endpoints: endpoints must include both filePaths and files');
3424
3397
  }
3425
3398
  const _saveConfig = async () => {
3426
3399
  try {
3427
3400
  const appDb = BaseDb.getAppDb();
3428
3401
  if (!appDb) {
3429
- // In test environments, continue anyway
3430
- if (isTestOrSeedDev()) {
3431
- logger$6('[internal/actors] [saveConfig] App DB not found, but continuing in test environment');
3432
- return;
3433
- }
3434
3402
  throw new Error('App DB not found');
3435
3403
  }
3436
3404
  const endpointsValueString = JSON.stringify(endpoints);
@@ -3483,21 +3451,16 @@ async function saveConfig(context) {
3483
3451
  await loadLocalDbChain();
3484
3452
  }
3485
3453
  catch (error) {
3486
- logger$6('[internal/actors] [saveConfig] Error saving config:', error);
3487
- // In test environments, continue anyway
3488
- if (isTestOrSeedDev()) {
3489
- logger$6('[internal/actors] [saveConfig] Continuing despite error in test environment');
3490
- return;
3491
- }
3454
+ logger$7('[internal/actors] [saveConfig] Error saving config:', error);
3492
3455
  throw error;
3493
3456
  }
3494
3457
  };
3495
3458
  // Init writes run one tab at a time (docs/MULTI_TAB.md).
3496
3459
  await withSeedDbLock('init', context.filesDir, _saveConfig);
3497
- logger$6('[internal/actors] [saveConfig] saveConfig success');
3460
+ logger$7('[internal/actors] [saveConfig] saveConfig success');
3498
3461
  }
3499
3462
 
3500
- const logger$5 = debug('seedSdk:helpers:schema');
3463
+ const logger$6 = debug('seedSdk:helpers:schema');
3501
3464
  /** Filesystem-safe schema name: other characters become `_`, runs of `_` collapse, ends trimmed. */
3502
3465
  const sanitizeSchemaName = (name) => name
3503
3466
  .replace(/[^a-zA-Z0-9\s_-]/g, '_')
@@ -3551,7 +3514,7 @@ async function readSchemaFile(filePath) {
3551
3514
  return schema;
3552
3515
  }
3553
3516
  catch (error) {
3554
- logger$5(`Could not read schema file ${filePath}:`, error);
3517
+ logger$6(`Could not read schema file ${filePath}:`, error);
3555
3518
  return undefined;
3556
3519
  }
3557
3520
  }
@@ -3565,7 +3528,7 @@ async function listCompleteSchemaFiles() {
3565
3528
  const workingDir = BaseFileManager.getWorkingDir();
3566
3529
  try {
3567
3530
  if (!(await BaseFileManager.pathExists(workingDir))) {
3568
- logger$5(`Working directory does not exist yet: ${workingDir}, returning empty schema list`);
3531
+ logger$6(`Working directory does not exist yet: ${workingDir}, returning empty schema list`);
3569
3532
  return [];
3570
3533
  }
3571
3534
  const files = await fs.promises.readdir(workingDir);
@@ -3636,7 +3599,7 @@ async function loadAllSchemasFromDb() {
3636
3599
  });
3637
3600
  }
3638
3601
  catch (error) {
3639
- logger$5(`Error resolving schema row ${row.name}:`, error);
3602
+ logger$6(`Error resolving schema row ${row.name}:`, error);
3640
3603
  }
3641
3604
  }
3642
3605
  return result;
@@ -3668,7 +3631,7 @@ var schema = /*#__PURE__*/Object.freeze({
3668
3631
  readSchemaFile: readSchemaFile
3669
3632
  });
3670
3633
 
3671
- function toBytes(data) {
3634
+ function toBytes$1(data) {
3672
3635
  if (typeof data === 'string')
3673
3636
  return utf8ToBytes(data);
3674
3637
  if (data instanceof Uint8Array)
@@ -3676,7 +3639,7 @@ function toBytes(data) {
3676
3639
  return new Uint8Array(data);
3677
3640
  }
3678
3641
  const getContentHash = async (data) => {
3679
- return bytesToHex(sha3_256(toBytes(data)));
3642
+ return bytesToHex(sha3_256(toBytes$1(data)));
3680
3643
  };
3681
3644
  /**
3682
3645
  * Generate a deterministic ID from a seed string.
@@ -3700,7 +3663,7 @@ const getDeterministicId = (seed) => {
3700
3663
  * Schema identity is the file id. Missing ids are assigned once, at first apply, and written into
3701
3664
  * the stored file.
3702
3665
  */
3703
- const logger$4 = debug('seedSdk:helpers:schemaApply');
3666
+ const logger$5 = debug('seedSdk:helpers:schemaApply');
3704
3667
  const SCHEMA_FILE_FORMAT = 'https://seedprotocol.org/schemas/data-model/v1';
3705
3668
  /**
3706
3669
  * A complete schema file from any accepted shape: models and properties as objects, `version`
@@ -3920,8 +3883,8 @@ async function applySchemaFile(source, options = {}) {
3920
3883
  if (!schemaRecord) {
3921
3884
  warnOnEasNameCollisions(file);
3922
3885
  warnOnInvalidNameMatch(file);
3923
- const _mod_14 = await import('./tabEvents-DT5ez6io.js');
3924
- const _ns_14 = _mod_14.bk;
3886
+ const _mod_14 = await import('./Item-BU9wIT7l.js');
3887
+ const _ns_14 = _mod_14.bS;
3925
3888
  const { addModelsToDb, broadcastSchemasInvalidate } = _ns_14;
3926
3889
  const modelFileIds = new Map(Object.entries(file.models).map(([name, model]) => [name, model.id]));
3927
3890
  const propertyDefinitions = schemaFilePropertyDefinitions(file);
@@ -3959,10 +3922,10 @@ async function applySchemaFile(source, options = {}) {
3959
3922
  broadcastSchemasInvalidate();
3960
3923
  // A cached instance is otherwise never re-read from its row (createById returns it as is)
3961
3924
  await refreshCachedProperties(updatedPropertyFileIds);
3962
- logger$4(`Applied schema ${file.metadata.name} v${file.version} (${file.id})`);
3925
+ logger$5(`Applied schema ${file.metadata.name} v${file.version} (${file.id})`);
3963
3926
  }
3964
3927
  else {
3965
- logger$4(`Schema ${file.metadata.name} v${file.version} (${file.id}) already applied`);
3928
+ logger$5(`Schema ${file.metadata.name} v${file.version} (${file.id}) already applied`);
3966
3929
  }
3967
3930
  const models = await createModelsForSchemaFile(file);
3968
3931
  // Callers use the models next (an item create reads their properties): wait until each has them.
@@ -3993,7 +3956,7 @@ async function storeSchemaFile(file, onChangedContent) {
3993
3956
  }
3994
3957
  if (onChangedContent === 'bump') {
3995
3958
  file.version = Math.max(...versions.map((info) => info.version)) + 1;
3996
- logger$4(`Schema ${file.metadata.name} content changed, storing it as v${file.version}`);
3959
+ logger$5(`Schema ${file.metadata.name} content changed, storing it as v${file.version}`);
3997
3960
  return writeSchemaFile(file);
3998
3961
  }
3999
3962
  console.warn(`[seed] schema ${file.metadata.name} v${file.version}: content changed but id ` +
@@ -4015,7 +3978,7 @@ async function unlinkModelsFromSchema(db, schemaId, modelNames) {
4015
3978
  if (links.length === 0)
4016
3979
  return;
4017
3980
  await db.delete(modelSchemas).where(inArray(modelSchemas.id, links.map((link) => link.id)));
4018
- logger$4(`Unlinked models dropped from the schema (schema id ${schemaId}): ${modelNames.join(', ')}`);
3981
+ logger$5(`Unlinked models dropped from the schema (schema id ${schemaId}): ${modelNames.join(', ')}`);
4019
3982
  }
4020
3983
  /**
4021
3984
  * A schema row's definition. A draft row's is its `schema_data`; a published row's is its
@@ -4034,7 +3997,7 @@ async function resolveSchemaRow(row, db = BaseDb.getAppDb()) {
4034
3997
  return { ...data, models: normalizeSchemaModels(data.models) };
4035
3998
  }
4036
3999
  catch (error) {
4037
- logger$4(`Could not parse schema_data of ${row.name}:`, error);
4000
+ logger$5(`Could not parse schema_data of ${row.name}:`, error);
4038
4001
  return undefined;
4039
4002
  }
4040
4003
  };
@@ -4072,8 +4035,8 @@ async function resolveSchemaRow(row, db = BaseDb.getAppDb()) {
4072
4035
  metadata: { ...schema.metadata, name: row.name },
4073
4036
  };
4074
4037
  if (row.id && db) {
4075
- const _mod_15 = await import('./tabEvents-DT5ez6io.js');
4076
- const _ns_15 = _mod_15.bk;
4038
+ const _mod_15 = await import('./Item-BU9wIT7l.js');
4039
+ const _ns_15 = _mod_15.bS;
4077
4040
  const { loadModelsFromDbForSchema, mergeDbModelsIntoSchemaModels } = _ns_15;
4078
4041
  const dbModels = await loadModelsFromDbForSchema(row.id, db);
4079
4042
  if (Object.keys(dbModels).length > 0) {
@@ -4140,7 +4103,7 @@ async function writeNextSchemaVersion(schemaNameOrId, patch, change) {
4140
4103
  ],
4141
4104
  };
4142
4105
  const filePath = await writeSchemaFile(schemaFile);
4143
- logger$4(`Wrote schema ${schemaFile.metadata.name} v${version} at ${filePath}`);
4106
+ logger$5(`Wrote schema ${schemaFile.metadata.name} v${version} at ${filePath}`);
4144
4107
  if (change.apply !== false) {
4145
4108
  await applySchemaFile(schemaFile, { store: false, modelRenames: change.modelRenames });
4146
4109
  }
@@ -4443,7 +4406,7 @@ var SEEDPROTOCOL_Seed_Protocol_v1 = /*#__PURE__*/Object.freeze({
4443
4406
  version: version
4444
4407
  });
4445
4408
 
4446
- const logger$3 = debug('seedSdk:client:actors:processSchemaFiles');
4409
+ const logger$4 = debug('seedSdk:client:actors:processSchemaFiles');
4447
4410
  let processSchemaFilesChain = Promise.resolve();
4448
4411
  function resolveSchemaFilePath(schemaFile) {
4449
4412
  const path = BaseFileManager.getPathModule();
@@ -4475,7 +4438,7 @@ async function processSchemaFilesOnce(context) {
4475
4438
  appliedNames.add(applied.schemaFile.metadata.name);
4476
4439
  }
4477
4440
  catch (error) {
4478
- logger$3('Error loading internal seed-protocol schema:', error);
4441
+ logger$4('Error loading internal seed-protocol schema:', error);
4479
4442
  }
4480
4443
  // The app's schema (canonical) takes precedence over schemaFile
4481
4444
  if (context.schema) {
@@ -4514,14 +4477,14 @@ async function processSchemaFilesOnce(context) {
4514
4477
  loaded = await loadAllSchemasFromDb();
4515
4478
  }
4516
4479
  catch (error) {
4517
- logger$3('Error loading schemas from database (continuing anyway):', error);
4480
+ logger$4('Error loading schemas from database (continuing anyway):', error);
4518
4481
  }
4519
4482
  for (const { schema } of loaded) {
4520
4483
  try {
4521
4484
  Object.assign(allModels, await createModelsForSchemaFile(schema));
4522
4485
  }
4523
4486
  catch (error) {
4524
- logger$3(`Error creating models for schema ${schema.metadata?.name}:`, error);
4487
+ logger$4(`Error creating models for schema ${schema.metadata?.name}:`, error);
4525
4488
  }
4526
4489
  }
4527
4490
  return allModels;
@@ -4532,7 +4495,7 @@ async function processSchemaFilesOnce(context) {
4532
4495
  * (docs/MULTI_TAB.md), and so do overlapping inits in this tab.
4533
4496
  */
4534
4497
  function processSchemaFiles(context) {
4535
- logger$3('processSchemaFiles started');
4498
+ logger$4('processSchemaFiles started');
4536
4499
  const run = processSchemaFilesChain.then(() => withSeedDbLock('init', context.filesDir, () => processSchemaFilesOnce(context)));
4537
4500
  // The chain only orders runs; a failed run must not fail the next init too.
4538
4501
  processSchemaFilesChain = run.then(() => { }, () => { });
@@ -4552,7 +4515,7 @@ async function applyPendingSchemaFiles(appliedNames) {
4552
4515
  files = await listLatestSchemaFiles();
4553
4516
  }
4554
4517
  catch (error) {
4555
- logger$3('Error listing schema files (continuing anyway):', error);
4518
+ logger$4('Error listing schema files (continuing anyway):', error);
4556
4519
  return;
4557
4520
  }
4558
4521
  const rows = await db
@@ -4571,12 +4534,12 @@ async function applyPendingSchemaFiles(appliedNames) {
4571
4534
  await applySchemaFile({ ...file, id: file.id ?? info.schemaFileId }, { store: false });
4572
4535
  }
4573
4536
  catch (error) {
4574
- logger$3(`Error applying schema file ${info.filePath}:`, error);
4537
+ logger$4(`Error applying schema file ${info.filePath}:`, error);
4575
4538
  }
4576
4539
  }
4577
4540
  }
4578
4541
 
4579
- const logger$2 = debug('seedSdk:db:canonicalizeModelTypes');
4542
+ const logger$3 = debug('seedSdk:db:canonicalizeModelTypes');
4580
4543
  /**
4581
4544
  * Model types in the local DB (`seeds.type`, `versions.seed_type`, `metadata.model_type`) are the
4582
4545
  * EAS name of the model (ADR 0008): `html_page` for HTMLPage. Rows written before that hold other
@@ -4614,7 +4577,7 @@ async function canonicalizeModelTypes() {
4614
4577
  await appDb.update(seeds).set({ type: next }).where(eq(seeds.type, type));
4615
4578
  await appDb.update(versions).set({ seedType: next }).where(eq(versions.seedType, type));
4616
4579
  await appDb.update(metadata$1).set({ modelType: next }).where(eq(metadata$1.modelType, type));
4617
- logger$2(`model type ${type} → ${next}`);
4580
+ logger$3(`model type ${type} → ${next}`);
4618
4581
  rewritten++;
4619
4582
  }
4620
4583
  return rewritten;
@@ -4627,7 +4590,7 @@ const runInBackground = () => {
4627
4590
  .then(() => undefined)
4628
4591
  .catch((error) => {
4629
4592
  // Never fails init: readers still match old types by name, only exact type filters miss them.
4630
- logger$2('canonicalizing model types failed', error);
4593
+ logger$3('canonicalizing model types failed', error);
4631
4594
  })
4632
4595
  .finally(() => {
4633
4596
  running = undefined;
@@ -4679,10 +4642,26 @@ const getVersionData = async ({ localId, seedLocalId, uid, }, db = BaseDb.getApp
4679
4642
  return queryRows[0];
4680
4643
  };
4681
4644
 
4682
- const logger$1 = debug('seedSdk:write:updateItemPropertyValue');
4645
+ /**
4646
+ * The publisher a seed's version and property rows take: the seed's own (its `publisher`, else the
4647
+ * attester of its stored attestation). Undefined for a draft that belongs to no wallet yet; those
4648
+ * rows are then claimed together with their seed (claimUnpublishedDrafts).
4649
+ */
4650
+ async function getSeedPublisher(seedLocalId,
4651
+ /** The database or transaction to read; the app database by default. */
4652
+ db = BaseDb.getAppDb()) {
4653
+ if (!seedLocalId)
4654
+ return undefined;
4655
+ const row = await findSeedRow({ localId: seedLocalId }, db);
4656
+ if (!row)
4657
+ return undefined;
4658
+ return normalizePublisher(resolvePublisherFromSeedRow(row));
4659
+ }
4660
+
4661
+ const logger$2 = debug('seedSdk:write:updateItemPropertyValue');
4683
4662
  const updateItemPropertyValue = async ({ localId: localIdParam, propertyName, newValue, seedUid, seedLocalId, modelName, refSeedType, refResolvedValue, refResolvedDisplayValue, versionLocalId, versionUid, schemaUid, localStorageDir, dataType, refValueType, retract, }, options = {}) => {
4684
4663
  if (!localIdParam && !seedLocalId) {
4685
- logger$1(`[db/write] [updateItemPropertyValue] no propertyLocalId or seedLocalId for property: ${propertyName}`);
4664
+ logger$2(`[db/write] [updateItemPropertyValue] no propertyLocalId or seedLocalId for property: ${propertyName}`);
4686
4665
  return;
4687
4666
  }
4688
4667
  // Every write below binds parameters: store the value as given (no SQL escaping).
@@ -4739,7 +4718,9 @@ const updateItemPropertyValue = async ({ localId: localIdParam, propertyName, ne
4739
4718
  rows = newestRows.slice(0, 1);
4740
4719
  }
4741
4720
  if (rows && rows.length > 0) {
4742
- const { localId, uid, propertyId: propertyIdFromDb, propertyName: propertyNameFromDb, propertyValue: propertyValueFromDb, modelType, seedLocalId: seedLocalIdFromDb, versionLocalId, schemaUid, easDataType, refSeedType: refSeedTypeFromDb, refResolvedValue: refResolvedValueFromDb, retract: retractFromDb, } = rows[0];
4721
+ const { localId, uid, propertyId: propertyIdFromDb, propertyName: propertyNameFromDb, propertyValue: propertyValueFromDb, modelType, seedLocalId: seedLocalIdFromDb, versionLocalId, schemaUid: schemaUidFromDb, easDataType, refSeedType: refSeedTypeFromDb, refResolvedValue: refResolvedValueFromDb, retract: retractFromDb, } = rows[0];
4722
+ // The resolved schema uid fills in a row stored before its schema was known.
4723
+ const rowSchemaUid = schemaUidFromDb || schemaUid;
4743
4724
  // Stored model types are the model's EAS name (ADR 0008): `blog_post` for BlogPost.
4744
4725
  const modelTypeForName = modelName ? easName(modelName) : undefined;
4745
4726
  if (propertyValueFromDb === newValue &&
@@ -4748,8 +4729,8 @@ const updateItemPropertyValue = async ({ localId: localIdParam, propertyName, ne
4748
4729
  (refSeedTypeFromDb ?? null) === (refSeedType ?? null) &&
4749
4730
  (refResolvedValueFromDb ?? null) === (refResolvedValue ?? null) &&
4750
4731
  !!retractFromDb === !!retractValue) {
4751
- logger$1(`[db/write] [updateItemPropertyValue] value is the same as most recent record for property: ${propertyNameFromDb}`);
4752
- return localId == null ? undefined : { localId, uid: uid ?? null, schemaUid: schemaUid ?? '' };
4732
+ logger$2(`[db/write] [updateItemPropertyValue] value is the same as most recent record for property: ${propertyNameFromDb}`);
4733
+ return localId == null ? undefined : { localId, uid: uid ?? null, schemaUid: schemaUidFromDb ?? '' };
4753
4734
  }
4754
4735
  // This means we already have a local-only record so we should just update that one
4755
4736
  if (!uid) {
@@ -4774,11 +4755,14 @@ const updateItemPropertyValue = async ({ localId: localIdParam, propertyName, ne
4774
4755
  if (propertyIdFromDb == null && property.propertyId != null) {
4775
4756
  updatePayload.propertyId = property.propertyId;
4776
4757
  }
4758
+ if (!schemaUidFromDb && schemaUid) {
4759
+ updatePayload.schemaUid = schemaUid;
4760
+ }
4777
4761
  await appDb
4778
4762
  .update(metadata$1)
4779
4763
  .set(updatePayload)
4780
4764
  .where(eq(metadata$1.localId, localId));
4781
- return { localId, uid: null, schemaUid: schemaUid ?? '' };
4765
+ return { localId, uid: null, schemaUid: rowSchemaUid ?? '' };
4782
4766
  }
4783
4767
  const seedDataFromDb = seedLocalId ? await findSeedRow({ localId: seedLocalId }, appDb) : null;
4784
4768
  const versionDataFromDb = versionLocalId ? await getVersionData({ localId: versionLocalId }, appDb) : null;
@@ -4798,7 +4782,7 @@ const updateItemPropertyValue = async ({ localId: localIdParam, propertyName, ne
4798
4782
  seedLocalId: seedLocalIdFromDb,
4799
4783
  versionLocalId,
4800
4784
  versionUid: versionDataFromDb?.uid || null,
4801
- schemaUid,
4785
+ schemaUid: rowSchemaUid,
4802
4786
  easDataType: easDataType || null,
4803
4787
  refSeedType: refSeedType || null,
4804
4788
  refResolvedValue: refResolvedValue || null,
@@ -4809,7 +4793,7 @@ const updateItemPropertyValue = async ({ localId: localIdParam, propertyName, ne
4809
4793
  return {
4810
4794
  localId: newLocalId,
4811
4795
  uid: null,
4812
- schemaUid: schemaUid ?? '',
4796
+ schemaUid: rowSchemaUid ?? '',
4813
4797
  };
4814
4798
  }
4815
4799
  // Here there are no records for this property on this seed so we should create one
@@ -4868,6 +4852,116 @@ const createVersion = async ({ seedLocalId, seedUid, seedType, uid, }, options =
4868
4852
  return newVersionLocalId;
4869
4853
  };
4870
4854
 
4855
+ /** Error thrown when metadata validation fails (e.g. enum violation). */
4856
+ class MetadataValidationError extends Error {
4857
+ constructor(message, validationErrors) {
4858
+ super(message);
4859
+ this.validationErrors = validationErrors;
4860
+ this.name = 'MetadataValidationError';
4861
+ }
4862
+ }
4863
+ const createMetadata = async (metadataValues, propertyRecordSchema, options) => {
4864
+ const appDb = options?.db ?? BaseDb.getAppDb();
4865
+ metadataValues.localId = generateId();
4866
+ // A property row belongs to its seed's wallet (none yet for a draft made with no wallet connected).
4867
+ const publisher = await getSeedPublisher(metadataValues.seedLocalId, appDb);
4868
+ if (publisher) {
4869
+ metadataValues.publisher = publisher;
4870
+ }
4871
+ if (!metadataValues.modelType && metadataValues.modelName) {
4872
+ metadataValues.modelType = easName(metadataValues.modelName);
4873
+ }
4874
+ // property_id is the match key (decision D4); the row is written under the property's storage name.
4875
+ const property = await resolveMetadataProperty({
4876
+ propertyId: metadataValues.propertyId,
4877
+ definitionId: propertyRecordSchema?.id,
4878
+ propertyName: metadataValues.propertyName,
4879
+ modelName: metadataValues.modelName ?? metadataValues.modelType,
4880
+ modelFileId: options?.modelFileId,
4881
+ seedLocalId: metadataValues.seedLocalId,
4882
+ seedUid: metadataValues.seedUid,
4883
+ }, appDb, { onAmbiguousModel: 'none' });
4884
+ if (property) {
4885
+ metadataValues.propertyId = property.id;
4886
+ metadataValues.propertyName = property.storageName;
4887
+ }
4888
+ else if (metadataValues.propertyName && propertyRecordSchema) {
4889
+ metadataValues.propertyName = metadataStorageName(metadataValues.propertyName, propertyRecordSchema);
4890
+ }
4891
+ const isItemStorage = propertyRecordSchema && propertyRecordSchema.storageType === 'ItemStorage';
4892
+ // if (
4893
+ // propertyRecordSchema &&
4894
+ // propertyRecordSchema.localStorageDir &&
4895
+ // isItemStorage
4896
+ // ) {
4897
+ // const filename = `${metadataValues.seedUid || metadataValues.seedLocalId}${propertyRecordSchema.filenameSuffix}`
4898
+ // const filePath = path.join(propertyRecordSchema.localStorageDir, filename)
4899
+ // await fs.promises.writeFile(filePath, metadataValues.propertyValue)
4900
+ // metadataValues.propertyValue = filename
4901
+ // metadataValues.refValueType = 'file'
4902
+ // }
4903
+ // Convert propertyValue to string if it's not already (metadata table expects text)
4904
+ if (metadataValues.propertyValue !== undefined && metadataValues.propertyValue !== null) {
4905
+ if (Array.isArray(metadataValues.propertyValue)) {
4906
+ // List values: JSON array is the storage format (String() would give legacy comma-separated ids)
4907
+ metadataValues.propertyValue = JSON.stringify(metadataValues.propertyValue);
4908
+ }
4909
+ else if (typeof metadataValues.propertyValue !== 'string') {
4910
+ metadataValues.propertyValue = String(metadataValues.propertyValue);
4911
+ }
4912
+ }
4913
+ // Validate against property validation rules (enum, pattern, minLength, maxLength) unless skipped
4914
+ if (!options?.skipValidation &&
4915
+ propertyRecordSchema?.validation &&
4916
+ metadataValues.propertyValue != null &&
4917
+ metadataValues.propertyValue !== '') {
4918
+ const { SchemaValidationService } = await import('./SchemaValidationService-Dc-lpFQX.js');
4919
+ const validationService = new SchemaValidationService();
4920
+ const validationResult = validationService.validatePropertyValue(metadataValues.propertyValue, normalizeDataType(propertyRecordSchema.dataType), propertyRecordSchema.validation, propertyRecordSchema.refValueType
4921
+ ? normalizeDataType(String(propertyRecordSchema.refValueType))
4922
+ : undefined);
4923
+ if (!validationResult.isValid && validationResult.errors.length > 0) {
4924
+ const validationErrors = validationResult.errors.map((e) => ({
4925
+ field: e.field,
4926
+ message: e.message,
4927
+ code: e.code,
4928
+ }));
4929
+ const message = validationErrors.map((e) => e.message).join('; ');
4930
+ throw new MetadataValidationError(message, validationErrors);
4931
+ }
4932
+ }
4933
+ // With a given db (a transaction), the caller resolves schemaUid first: the lookup may call EAS.
4934
+ // Without one it isn't required for metadata insertion, so a failed lookup leaves it unset.
4935
+ if (!options?.db && !isItemStorage && !metadataValues.schemaUid && metadataValues.propertyName) {
4936
+ const schemaUid = await lookupPropertySchemaUid(metadataValues.propertyName, propertyRecordSchema);
4937
+ if (schemaUid)
4938
+ metadataValues.schemaUid = schemaUid;
4939
+ }
4940
+ let inserted;
4941
+ try {
4942
+ inserted = await appDb
4943
+ .insert(metadata$1)
4944
+ .values({
4945
+ ...metadataValues,
4946
+ // Stamped here: a value carried in metadataValues would otherwise win over the column default.
4947
+ createdAt: Date.now(),
4948
+ updatedAt: Date.now(),
4949
+ })
4950
+ .returning();
4951
+ }
4952
+ catch (error) {
4953
+ // Drizzle's message is the full SQL; the driver error that explains it is only on `cause`.
4954
+ const cause = error?.cause;
4955
+ const reason = cause instanceof Error ? cause.message : cause != null ? String(cause) : undefined;
4956
+ throw new Error(`Failed to insert metadata for property ${metadataValues.propertyName}` +
4957
+ (reason ? `: ${reason}` : `: ${error instanceof Error ? error.message : String(error)}`), { cause: error });
4958
+ }
4959
+ if (!inserted || inserted.length === 0) {
4960
+ throw new Error(`Failed to insert metadata record for property ${metadataValues.propertyName}`);
4961
+ }
4962
+ return inserted[0];
4963
+ };
4964
+
4871
4965
  /** Absolute path of the file {@link writeItemStorageFile} writes for `target`. */
4872
4966
  const getItemStorageFilePath = (target) => BaseFileManager.getFilesPath(getItemStorageSubdir(target.localStorageDir), target.fileName || getItemStorageLocalFileName(target.seedId, target.filenameSuffix));
4873
4967
  /**
@@ -5008,8 +5102,8 @@ async function applyStorageValue(seedLocalId, propertyName, value, db, prepared)
5008
5102
  const row = prepared
5009
5103
  ? { ...stored, refResolvedValue: prepared.fileName, schemaUid: prepared.schemaUid, refModelUid: prepared.refModelUid }
5010
5104
  : stored;
5011
- const _mod_16 = await import('./Item-oPnmIehk.js');
5012
- const _ns_16 = _mod_16.b7;
5105
+ const _mod_16 = await import('./Item-BU9wIT7l.js');
5106
+ const _ns_16 = _mod_16.bT;
5013
5107
  const { currentPropertyRow } = _ns_16;
5014
5108
  const existing = await currentPropertyRow({ seedLocalId }, {
5015
5109
  propertyName,
@@ -5322,8 +5416,8 @@ function startItemOutbox() {
5322
5416
  return {};
5323
5417
  // Cached, shared with item creation, so a save doesn't wait on the EAS indexer each time.
5324
5418
  // Not required for the row; publish resolves the schema again.
5325
- const _mod_17 = await import('./Item-oPnmIehk.js');
5326
- const _ns_17 = _mod_17.ba;
5419
+ const _mod_17 = await import('./Item-BU9wIT7l.js');
5420
+ const _ns_17 = _mod_17.bU;
5327
5421
  const { lookupPropertySchemaUid } = _ns_17;
5328
5422
  return {
5329
5423
  schemaUid: await lookupPropertySchemaUid(op.propertyName, value.propertyRecordSchema),
@@ -6258,7 +6352,7 @@ async function claimPendingCreates(tx, publisher) {
6258
6352
  return claimed;
6259
6353
  }
6260
6354
 
6261
- const logger = debug('seedSdk:client');
6355
+ const logger$1 = debug('seedSdk:client');
6262
6356
  const createClientManager = () => {
6263
6357
  const actor = createActor(clientManagerMachine, {
6264
6358
  input: {
@@ -6269,8 +6363,8 @@ const createClientManager = () => {
6269
6363
  }
6270
6364
  });
6271
6365
  const actorSubscription = actor.subscribe((snapshot) => {
6272
- logger('ClientManager snapshot.value:', snapshot.value);
6273
- logger('ClientManager snapshot.context.isInitialized:', snapshot.context.isInitialized);
6366
+ logger$1('ClientManager snapshot.value:', snapshot.value);
6367
+ logger$1('ClientManager snapshot.context.isInitialized:', snapshot.context.isInitialized);
6274
6368
  });
6275
6369
  actor.start();
6276
6370
  return { actor, actorSubscription };
@@ -6286,7 +6380,7 @@ const notifyServiceListeners = () => {
6286
6380
  listener(clientManager);
6287
6381
  }
6288
6382
  catch (error) {
6289
- logger('subscribeService listener threw:', error);
6383
+ logger$1('subscribeService listener threw:', error);
6290
6384
  }
6291
6385
  }
6292
6386
  };
@@ -6356,14 +6450,14 @@ const coalescedInit = createCoalescedAsync(async (options) => {
6356
6450
  const isStopped = snapshot.status === 'stopped';
6357
6451
  if (isStopped) {
6358
6452
  // stop() (or a failed test) stopped it: start over with a new actor and a clean context.
6359
- logger('Client manager was stopped, creating a new one');
6453
+ logger$1('Client manager was stopped, creating a new one');
6360
6454
  ({ actor: clientManager, actorSubscription: subscription } = createClientManager());
6361
6455
  notifyServiceListeners();
6362
6456
  }
6363
6457
  // Clear any stale context from previous failed initializations
6364
6458
  // This ensures we start with a clean slate before the new init updates it
6365
6459
  if (!isStopped && currentState !== 'uninitialized') {
6366
- logger(`Client manager is in state ${currentState}, clearing stale context before re-init...`);
6460
+ logger$1(`Client manager is in state ${currentState}, clearing stale context before re-init...`);
6367
6461
  clientManager.send({
6368
6462
  type: 'updateContext',
6369
6463
  context: {
@@ -6464,14 +6558,14 @@ const clientInstance = {
6464
6558
  },
6465
6559
  setAddresses: async (addresses) => {
6466
6560
  ensureInitialized();
6467
- logger('setAddresses', addresses);
6561
+ logger$1('setAddresses', addresses);
6468
6562
  clientManager.send({ type: 'setAddresses', addresses });
6469
6563
  await waitFor(clientManager, (snapshot) => !snapshot.context.isSaving, { timeout: 10000 });
6470
6564
  const saveError = clientManager.getSnapshot().context.saveError;
6471
6565
  if (saveError) {
6472
6566
  throw saveError instanceof Error ? saveError : new Error(String(saveError));
6473
6567
  }
6474
- logger('setAddresses success', addresses);
6568
+ logger$1('setAddresses success', addresses);
6475
6569
  },
6476
6570
  getAddresses: async () => {
6477
6571
  ensureInitialized();
@@ -6585,6 +6679,1457 @@ const getClient = () => {
6585
6679
  return clientInstance;
6586
6680
  };
6587
6681
 
6682
+ const getMetadata = async (props) => {
6683
+ const appDb = BaseDb.getAppDb();
6684
+ let storageTransactionId;
6685
+ if (props) {
6686
+ storageTransactionId = props.storageTransactionId;
6687
+ }
6688
+ const whereClauses = [];
6689
+ if (storageTransactionId) {
6690
+ whereClauses.push(eq(metadata$1.propertyName, 'storageTransactionId'));
6691
+ whereClauses.push(eq(metadata$1.propertyValue, storageTransactionId));
6692
+ }
6693
+ const metadataRows = await appDb
6694
+ .select()
6695
+ .from(metadata$1)
6696
+ .where(and(...whereClauses));
6697
+ if (!metadataRows || metadataRows.length === 0) {
6698
+ return;
6699
+ }
6700
+ return metadataRows[0];
6701
+ };
6702
+
6703
+ /**
6704
+ * The local seed row a relation/list/image ref points at, by local id or uid. A uid resolves as
6705
+ * {@link findSeedRow} resolves it (the order EAS sync matches seeds in): the old uid of a seed
6706
+ * that was unpublished and published again resolves to that seed, through its old version rows.
6707
+ * Null when nothing local matches (e.g. a published uid with no local copy).
6708
+ */
6709
+ const findRelatedSeedRow = async ({ seedLocalId, seedUid, }) => {
6710
+ const row = await findSeedRow({ localId: seedLocalId, uid: seedUid });
6711
+ return row ? { seedLocalId: row.localId, seedUid: row.uid, revokedAt: row.revokedAt, type: row.type } : null;
6712
+ };
6713
+ /** The seed is attested and its attestation is live. */
6714
+ const isLivePublishedSeed = (row) => !!row?.seedUid && row.seedUid !== ZERO_BYTES32 && !isVersionRevoked(row.revokedAt);
6715
+ /** The seed was attested and that attestation was revoked (unpublished). */
6716
+ const isUnpublishedSeed = (row) => !!row?.seedUid && row.seedUid !== ZERO_BYTES32 && isVersionRevoked(row.revokedAt);
6717
+ /** Model a relation/list/image property points at, from its definition. */
6718
+ function relatedModelNameFromDef(propertyDef) {
6719
+ const def = propertyDef;
6720
+ if (!def)
6721
+ return undefined;
6722
+ const dataType = normalizeDataType(def.dataType);
6723
+ if (dataType === ModelPropertyDataTypes.Image)
6724
+ return 'Image';
6725
+ if (dataType === ModelPropertyDataTypes.File)
6726
+ return 'File';
6727
+ if (dataType === ModelPropertyDataTypes.Html)
6728
+ return 'Html';
6729
+ return def.ref ?? def.refModelName;
6730
+ }
6731
+
6732
+ const STORAGE_SEED_TYPES = new Set([
6733
+ ModelPropertyDataTypes.Image,
6734
+ ModelPropertyDataTypes.File,
6735
+ ModelPropertyDataTypes.Html,
6736
+ ModelPropertyDataTypes.Json,
6737
+ ]);
6738
+ const isStorageTransactionPropertyName = (name) => name === 'storageTransactionId' || name === 'storage_transaction_id';
6739
+ function publishPropertyKind(key, def) {
6740
+ const dataType = normalizeDataType(def.dataType);
6741
+ const refValueType = normalizeDataType(def.refValueType);
6742
+ if (STORAGE_SEED_TYPES.has(dataType))
6743
+ return 'storageSeed';
6744
+ if (dataType === ModelPropertyDataTypes.Relation) {
6745
+ return STORAGE_SEED_TYPES.has(refValueType) ? 'storageSeed' : 'relation';
6746
+ }
6747
+ if (dataType === ModelPropertyDataTypes.List)
6748
+ return def.ref || def.refModelName ? 'list' : 'basic';
6749
+ if (def.storageType === 'ItemStorage')
6750
+ return 'itemStorage';
6751
+ if (isStorageTransactionPropertyName(key))
6752
+ return 'storageTransaction';
6753
+ return 'basic';
6754
+ }
6755
+ const isEmptyValue = (value) => value == null || value.trim() === '';
6756
+ /** The seed refs a property's stored value names: one (Relation, storage seed) or each list member. */
6757
+ function refsOf(value, kind) {
6758
+ if (isEmptyValue(value))
6759
+ return [];
6760
+ if (kind === 'list') {
6761
+ return parseListPropertyValueFromStorage(value)
6762
+ .map((member) => normalizeRelationPropertyValue(member))
6763
+ .filter((ref) => !!ref);
6764
+ }
6765
+ const ref = normalizeRelationPropertyValue(value);
6766
+ return ref ? [ref] : [];
6767
+ }
6768
+ async function modelOfSeed(seed, modelNameHint) {
6769
+ if (seed.modelFileId) {
6770
+ const model = await resolveModelRecord(undefined, { modelFileId: seed.modelFileId });
6771
+ if (model)
6772
+ return model;
6773
+ }
6774
+ return (await resolveModelRecordByNameOrType(seed.type ?? modelNameHint, {})) ?? undefined;
6775
+ }
6776
+ /** One seed with its model's properties and their current rows. Undefined when the seed or its model isn't local. */
6777
+ async function readNode(seedLocalId, kind, modelNameHint) {
6778
+ const db = BaseDb.getAppDb();
6779
+ if (!db)
6780
+ return undefined;
6781
+ const seed = await findSeedRow({ localId: seedLocalId });
6782
+ if (!seed)
6783
+ return undefined;
6784
+ const model = await modelOfSeed(seed, modelNameHint);
6785
+ if (!model)
6786
+ return undefined;
6787
+ const definitions = await getPropertyDefinitions(model.id);
6788
+ const metadataProperties = await readModelMetadataProperties(db, model.id);
6789
+ const seedUid = isValidEasAttestationUid(seed.uid) ? seed.uid : undefined;
6790
+ const seedRef = { seedLocalId: seed.localId, seedUid };
6791
+ const properties = [];
6792
+ for (const [key, definition] of definitions) {
6793
+ const property = metadataProperties.find((p) => p.id === definition._dbId);
6794
+ const match = property
6795
+ ? { property, propertyId: property.id, names: metadataPropertyNameForms(property) }
6796
+ : { property: undefined, propertyId: null, names: [key] };
6797
+ const row = await currentPropertyRow(seedRef, { match, propertyName: key }, { overlay: false });
6798
+ const propertyKind = publishPropertyKind(key, definition);
6799
+ properties.push({
6800
+ key,
6801
+ definition,
6802
+ kind: propertyKind,
6803
+ row,
6804
+ uid: isValidEasAttestationUid(row?.uid) ? row.uid : undefined,
6805
+ walked: false,
6806
+ deferred: false,
6807
+ refs: [],
6808
+ });
6809
+ }
6810
+ const head = await getLatestVersionRow(seed.localId);
6811
+ const published = seedUid ? await getLatestPublishedVersionRow(seed.localId) : null;
6812
+ const isFullNode = kind === 'root' || kind === 'draft';
6813
+ const embeddedImages = isFullNode
6814
+ ? await db.select().from(htmlEmbeddedImageCoPublish).where(eq(htmlEmbeddedImageCoPublish.parentSeedLocalId, seed.localId))
6815
+ : [];
6816
+ return {
6817
+ seedLocalId: seed.localId,
6818
+ kind,
6819
+ seedUid,
6820
+ newSeed: !seedUid || (kind === 'root' && isVersionRevoked(seed.revokedAt)),
6821
+ modelName: model.name,
6822
+ seedSchemaUid: isValidEasAttestationUid(seed.schemaUid) ? seed.schemaUid : undefined,
6823
+ version: {
6824
+ localId: head?.localId ?? undefined,
6825
+ uid: isValidEasAttestationUid(head?.uid) ? head.uid : undefined,
6826
+ publishedUid: published?.uid,
6827
+ },
6828
+ properties,
6829
+ links: [],
6830
+ embeddedImages,
6831
+ };
6832
+ }
6833
+ /** The Html property of `node` whose value is the Html storage seed `htmlSeedLocalId`. */
6834
+ function htmlPropertyForHtmlSeed(node, htmlSeedLocalId) {
6835
+ const want = htmlSeedLocalId.trim();
6836
+ return node.properties.find((p) => normalizeDataType(p.definition.dataType) === ModelPropertyDataTypes.Html && p.row?.propertyValue?.trim() === want);
6837
+ }
6838
+ /**
6839
+ * The publish graph of the seed `root` (an Item, or any `{ seedLocalId }`). Waits for pending
6840
+ * outbox ops first, so every edit made before publishing is in its row (ADR 0019), then reads rows
6841
+ * only.
6842
+ */
6843
+ async function getPublishGraph(root, options = {}) {
6844
+ const { waitForPendingOutboxOps } = await import('./index-BXTyvnKb.js');
6845
+ await waitForPendingOutboxOps();
6846
+ const rootNode = await readNode(root.seedLocalId, 'root', root.modelName);
6847
+ if (!rootNode)
6848
+ throw new Error(`Cannot publish ${root.seedLocalId}: no local seed with a known model`);
6849
+ const republishRevokedSeed = !!rootNode.seedUid && rootNode.newSeed;
6850
+ const forceFullSnapshot = options.publishMode === 'new_version' || republishRevokedSeed;
6851
+ const graph = {
6852
+ root: rootNode,
6853
+ nodes: [],
6854
+ forceFullSnapshot,
6855
+ republishRevokedSeed,
6856
+ unpublishedRelatedItems: [],
6857
+ relatedRefUids: new Map(),
6858
+ brokenRefs: [],
6859
+ missingEmbeddedImages: [],
6860
+ };
6861
+ const nodes = new Map([[rootNode.seedLocalId, rootNode]]);
6862
+ // Items getting a new seed in this publish whose walk hasn't finished: a ref to one is a back edge
6863
+ const inProgress = new Set();
6864
+ const noteUnpublished = (propertyKey, modelName, seed) => {
6865
+ if (graph.unpublishedRelatedItems.some((r) => r.propertyName === propertyKey && r.seedLocalId === seed.seedLocalId))
6866
+ return;
6867
+ graph.unpublishedRelatedItems.push({
6868
+ propertyName: propertyKey,
6869
+ modelName: modelName ?? seed.type ?? 'unknown',
6870
+ seedLocalId: seed.seedLocalId,
6871
+ seedUid: seed.seedUid,
6872
+ });
6873
+ };
6874
+ const link = (target, owner, propertyKey) => {
6875
+ if (!target.links.some((l) => l.seedLocalId === owner.seedLocalId && l.propertyKey === propertyKey)) {
6876
+ target.links.push({ seedLocalId: owner.seedLocalId, propertyKey });
6877
+ }
6878
+ };
6879
+ /**
6880
+ * Follows one ref of `property`: a draft target becomes a node (walked now, or already in the
6881
+ * graph). Returns the ref with its node, or undefined for a back edge.
6882
+ */
6883
+ const follow = async (owner, property, ref) => {
6884
+ const list = property.kind === 'list';
6885
+ const { seedLocalId, seedUid } = resolveSeedIdsFromRefString(ref);
6886
+ if (!seedLocalId && !seedUid) {
6887
+ graph.brokenRefs.push({ seedLocalId: owner.seedLocalId, propertyKey: property.key, ref, reason: 'invalid', list });
6888
+ return { ref };
6889
+ }
6890
+ const row = await findRelatedSeedRow({ seedLocalId, seedUid });
6891
+ const targetLocalId = row?.seedLocalId ?? seedLocalId;
6892
+ if (property.kind !== 'storageSeed' && targetLocalId && inProgress.has(targetLocalId))
6893
+ return 'backEdge';
6894
+ if (isUnpublishedSeed(row)) {
6895
+ const targetNode = nodes.get(row.seedLocalId);
6896
+ noteUnpublished(property.key, targetNode?.modelName ?? relatedModelNameFromDef(property.definition), row);
6897
+ return { ref, seedLocalId: row.seedLocalId };
6898
+ }
6899
+ if (isLivePublishedSeed(row)) {
6900
+ if (ref !== row.seedUid)
6901
+ graph.relatedRefUids.set(ref, row.seedUid);
6902
+ return { ref, seedLocalId: row.seedLocalId };
6903
+ }
6904
+ if (!row) {
6905
+ // An attested uid with no local copy is already published: attested as-is
6906
+ if (!isPublishedSeedRef(ref)) {
6907
+ graph.brokenRefs.push({ seedLocalId: owner.seedLocalId, propertyKey: property.key, ref, reason: 'missing', list });
6908
+ }
6909
+ return { ref, seedLocalId };
6910
+ }
6911
+ let node = nodes.get(row.seedLocalId);
6912
+ if (!node) {
6913
+ const kind = property.kind === 'storageSeed' ? 'storageSeed' : 'draft';
6914
+ node = await readNode(row.seedLocalId, kind, relatedModelNameFromDef(property.definition));
6915
+ if (!node) {
6916
+ graph.brokenRefs.push({ seedLocalId: owner.seedLocalId, propertyKey: property.key, ref, reason: 'missing', list });
6917
+ return { ref, seedLocalId: row.seedLocalId };
6918
+ }
6919
+ nodes.set(node.seedLocalId, node);
6920
+ await visit(node);
6921
+ }
6922
+ return { ref, seedLocalId: node.seedLocalId, node };
6923
+ };
6924
+ const walkProperty = async (owner, property) => {
6925
+ const refs = refsOf(property.row?.propertyValue, property.kind);
6926
+ property.walked = refs.length > 0 && (!property.uid || forceFullSnapshot);
6927
+ if (!property.walked)
6928
+ return;
6929
+ // A list member that is a back edge defers the whole list; its other drafts are still published
6930
+ const followed = [];
6931
+ for (const ref of refs) {
6932
+ const result = await follow(owner, property, ref);
6933
+ if (result === 'backEdge') {
6934
+ property.deferred = true;
6935
+ if (property.kind !== 'list')
6936
+ return;
6937
+ continue;
6938
+ }
6939
+ followed.push(result);
6940
+ }
6941
+ property.refs = followed;
6942
+ if (property.deferred)
6943
+ return;
6944
+ for (const { node } of followed)
6945
+ if (node)
6946
+ link(node, owner, property.key);
6947
+ };
6948
+ const walkEmbeddedImages = async (owner) => {
6949
+ const done = new Set();
6950
+ for (const row of owner.embeddedImages) {
6951
+ if (done.has(row.imageSeedLocalId))
6952
+ continue;
6953
+ done.add(row.imageSeedLocalId);
6954
+ const seed = await findRelatedSeedRow({ seedLocalId: row.imageSeedLocalId });
6955
+ if (isUnpublishedSeed(seed)) {
6956
+ noteUnpublished(htmlPropertyForHtmlSeed(owner, row.htmlSeedLocalId)?.key ?? 'html', 'Image', seed);
6957
+ continue;
6958
+ }
6959
+ if (isLivePublishedSeed(seed))
6960
+ continue;
6961
+ if (!seed) {
6962
+ graph.missingEmbeddedImages.push({ seedLocalId: owner.seedLocalId, imageSeedLocalId: row.imageSeedLocalId });
6963
+ continue;
6964
+ }
6965
+ if (nodes.has(seed.seedLocalId))
6966
+ continue;
6967
+ const node = await readNode(seed.seedLocalId, 'embeddedImage', 'Image');
6968
+ if (!node) {
6969
+ graph.missingEmbeddedImages.push({ seedLocalId: owner.seedLocalId, imageSeedLocalId: row.imageSeedLocalId });
6970
+ continue;
6971
+ }
6972
+ nodes.set(node.seedLocalId, node);
6973
+ graph.nodes.push(node);
6974
+ }
6975
+ };
6976
+ async function visit(node) {
6977
+ const full = node.kind === 'root' || node.kind === 'draft';
6978
+ if (node.newSeed)
6979
+ inProgress.add(node.seedLocalId);
6980
+ try {
6981
+ if (full) {
6982
+ for (const property of node.properties) {
6983
+ if (property.kind === 'relation' || property.kind === 'storageSeed')
6984
+ await walkProperty(node, property);
6985
+ }
6986
+ await walkEmbeddedImages(node);
6987
+ for (const property of node.properties) {
6988
+ if (property.kind === 'list')
6989
+ await walkProperty(node, property);
6990
+ }
6991
+ }
6992
+ }
6993
+ finally {
6994
+ inProgress.delete(node.seedLocalId);
6995
+ }
6996
+ graph.nodes.push(node);
6997
+ }
6998
+ await visit(rootNode);
6999
+ return graph;
7000
+ }
7001
+ /** Nodes of `graph` that are full items (the root and model drafts): the owners of Html properties and embedded images. */
7002
+ const publishGraphItemNodes = (graph) => graph.nodes.filter((n) => n.kind === 'root' || n.kind === 'draft');
7003
+
7004
+ const HTML_EMBEDDED_MAX_IMAGES_PER_DOC = 50;
7005
+ const HTML_EMBEDDED_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
7006
+ const HTML_EMBEDDED_MAX_TOTAL_BYTES = 25 * 1024 * 1024;
7007
+ const DATA_URI_IMAGE = /^data:(image\/[a-zA-Z0-9.+-]+);base64,([\s\S]*)$/i;
7008
+ const MIME_NORMALIZE = {
7009
+ 'image/jpg': 'image/jpeg',
7010
+ };
7011
+ function normalizeMime(m) {
7012
+ const lower = m.toLowerCase().trim();
7013
+ return MIME_NORMALIZE[lower] ?? lower;
7014
+ }
7015
+ const ALLOWED_MIMES = new Set([
7016
+ 'image/png',
7017
+ 'image/jpeg',
7018
+ 'image/webp',
7019
+ 'image/gif',
7020
+ 'image/svg+xml',
7021
+ ]);
7022
+ class HtmlEmbeddedDataUriLimitError extends Error {
7023
+ constructor(message) {
7024
+ super(message);
7025
+ this.name = 'HtmlEmbeddedDataUriLimitError';
7026
+ }
7027
+ }
7028
+ function resolveEffectiveHtmlEmbeddedDataUriPolicy(propertyDef, publishLevel) {
7029
+ const fromProp = propertyDef?.htmlEmbeddedDataUriPolicy;
7030
+ if (fromProp === 'materialize' || fromProp === 'preserve')
7031
+ return fromProp;
7032
+ if (publishLevel === 'materialize' || publishLevel === 'preserve')
7033
+ return publishLevel;
7034
+ return 'materialize';
7035
+ }
7036
+ function getAttr(node, name) {
7037
+ const a = node.attrs?.find((x) => x.name === name);
7038
+ return a?.value;
7039
+ }
7040
+ /**
7041
+ * Walk fragment tree; collect unique data:image base64 URIs with limits.
7042
+ */
7043
+ async function extractDataUriImagesFromHtml(html) {
7044
+ const fragment = parseFragment(html);
7045
+ const found = [];
7046
+ const seenUris = new Set();
7047
+ let totalBytes = 0;
7048
+ const walk = async (node) => {
7049
+ if (node.nodeName === '#text' || node.nodeName === '#comment')
7050
+ return;
7051
+ const el = node;
7052
+ if (el.tagName === 'img') {
7053
+ const src = getAttr(el, 'src')?.trim();
7054
+ if (src && src.startsWith('data:image/')) {
7055
+ const m = DATA_URI_IMAGE.exec(src);
7056
+ if (m) {
7057
+ const mimeRaw = normalizeMime(m[1]);
7058
+ if (!ALLOWED_MIMES.has(mimeRaw)) {
7059
+ throw new HtmlEmbeddedDataUriLimitError(`Unsupported embedded image type: ${mimeRaw}. Allowed: ${[...ALLOWED_MIMES].join(', ')}`);
7060
+ }
7061
+ let binary;
7062
+ try {
7063
+ const b64 = m[2].replace(/\s/g, '');
7064
+ binary = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
7065
+ }
7066
+ catch {
7067
+ throw new HtmlEmbeddedDataUriLimitError('Invalid base64 in embedded image data URI');
7068
+ }
7069
+ const byteLength = binary.byteLength;
7070
+ if (byteLength > HTML_EMBEDDED_MAX_IMAGE_BYTES) {
7071
+ throw new HtmlEmbeddedDataUriLimitError(`Embedded image exceeds max size (${HTML_EMBEDDED_MAX_IMAGE_BYTES} bytes)`);
7072
+ }
7073
+ totalBytes += byteLength;
7074
+ if (totalBytes > HTML_EMBEDDED_MAX_TOTAL_BYTES) {
7075
+ throw new HtmlEmbeddedDataUriLimitError(`Total embedded image size exceeds max (${HTML_EMBEDDED_MAX_TOTAL_BYTES} bytes)`);
7076
+ }
7077
+ if (!seenUris.has(src)) {
7078
+ if (seenUris.size >= HTML_EMBEDDED_MAX_IMAGES_PER_DOC) {
7079
+ throw new HtmlEmbeddedDataUriLimitError(`Too many distinct embedded images (max ${HTML_EMBEDDED_MAX_IMAGES_PER_DOC})`);
7080
+ }
7081
+ seenUris.add(src);
7082
+ const stableKey = await getContentHash(binary);
7083
+ found.push({ dataUri: src, stableKey, byteLength, mimeType: mimeRaw, bytes: binary });
7084
+ }
7085
+ }
7086
+ }
7087
+ }
7088
+ const parent = el;
7089
+ if (parent.childNodes) {
7090
+ for (const c of parent.childNodes)
7091
+ await walk(c);
7092
+ }
7093
+ };
7094
+ for (const c of fragment.childNodes)
7095
+ await walk(c);
7096
+ return found;
7097
+ }
7098
+ function escapeAttributeValue(value) {
7099
+ return value.replace(/&/g, '&').replace(/"/g, '"');
7100
+ }
7101
+ /**
7102
+ * Rewrite attribute values of `html` by splicing each new value into the original string: every
7103
+ * byte other than the rewritten attributes stays as written. Re-serializing the parsed tree would
7104
+ * not round-trip (parse5 drops the newline the parser eats after `<pre>`/`<textarea>`/`<listing>`,
7105
+ * a fragment parse drops doctype/html/head/body, and quoting, case and line endings are normalized),
7106
+ * and sealed Html must not change with the serializer.
7107
+ *
7108
+ * `rewrite` gets each attribute's (entity-decoded) value and returns its new value, or undefined to
7109
+ * leave it. A rewritten attribute is written as `name="value"`.
7110
+ */
7111
+ function rewriteHtmlAttributes(html, rewrite) {
7112
+ const fragment = parseFragment(html, { sourceCodeLocationInfo: true });
7113
+ const edits = [];
7114
+ const walk = (node) => {
7115
+ if (node.nodeName === '#text' || node.nodeName === '#comment')
7116
+ return;
7117
+ const el = node;
7118
+ if (el.attrs?.length) {
7119
+ for (const attr of el.attrs) {
7120
+ if (attr.prefix)
7121
+ continue;
7122
+ const next = rewrite(el, attr.name, attr.value);
7123
+ const location = el.sourceCodeLocation?.attrs?.[attr.name];
7124
+ if (next === undefined || !location)
7125
+ continue;
7126
+ edits.push({
7127
+ start: location.startOffset,
7128
+ end: location.endOffset,
7129
+ text: `${attr.name}="${escapeAttributeValue(next)}"`,
7130
+ });
7131
+ }
7132
+ }
7133
+ const parent = el;
7134
+ if (parent.childNodes) {
7135
+ for (const c of parent.childNodes)
7136
+ walk(c);
7137
+ }
7138
+ };
7139
+ for (const c of fragment.childNodes)
7140
+ walk(c);
7141
+ let out = html;
7142
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
7143
+ out = out.slice(0, edit.start) + edit.text + out.slice(edit.end);
7144
+ }
7145
+ return out;
7146
+ }
7147
+ /**
7148
+ * Replace `data:image/...` `<img src>` values with Arweave URLs (keyed by data URI). Only those
7149
+ * `src` attributes change; the rest of the Html is kept byte for byte.
7150
+ */
7151
+ function replaceDataUrisInParsedHtml(html, replacements) {
7152
+ if (replacements.size === 0)
7153
+ return html;
7154
+ return rewriteHtmlAttributes(html, (el, attrName, value) => {
7155
+ if (el.tagName !== 'img' || attrName !== 'src')
7156
+ return undefined;
7157
+ return replacements.get(value.trim());
7158
+ });
7159
+ }
7160
+ /**
7161
+ * `seed:property/<propertyName>` in Html stands for the Arweave URL of the same item's
7162
+ * `<propertyName>` storage property (Image, File or Json). Publish uploads that property first,
7163
+ * then rewrites the placeholder to its gateway URL before the Html is uploaded, so Html can show a
7164
+ * file published alongside it without embedding it (e.g. `<img class="u-featured"
7165
+ * src="seed:property/featureImage">`).
7166
+ */
7167
+ const SEED_PROPERTY_REF_PREFIX = 'seed:property/';
7168
+ const SEED_PROPERTY_REF = /^seed:property\/([A-Za-z_$][A-Za-z0-9_$]*)$/;
7169
+ /** Attributes whose whole (trimmed) value may be a `seed:property/` placeholder. */
7170
+ const SEED_PROPERTY_REF_ATTRIBUTES = new Set(['src', 'href', 'poster']);
7171
+ const seedPropertyRefName = (attrName, value) => SEED_PROPERTY_REF_ATTRIBUTES.has(attrName) ? SEED_PROPERTY_REF.exec(value.trim())?.[1] : undefined;
7172
+ /** A `seed:property/` placeholder that names no usable property, or whose upload is unknown. */
7173
+ class HtmlSeedPropertyRefError extends Error {
7174
+ constructor(message) {
7175
+ super(message);
7176
+ this.name = 'HtmlSeedPropertyRefError';
7177
+ }
7178
+ }
7179
+ /** Property names referenced by `seed:property/<name>` placeholders in `html`, each once. */
7180
+ function findSeedPropertyRefsInHtml(html) {
7181
+ if (!html.includes(SEED_PROPERTY_REF_PREFIX))
7182
+ return [];
7183
+ const names = new Set();
7184
+ rewriteHtmlAttributes(html, (_el, attrName, value) => {
7185
+ const name = seedPropertyRefName(attrName, value);
7186
+ if (name)
7187
+ names.add(name);
7188
+ return undefined;
7189
+ });
7190
+ return [...names];
7191
+ }
7192
+ /** Replace `seed:property/<name>` placeholders with `urlByPropertyName.get(name)`; other bytes are kept. */
7193
+ function replaceSeedPropertyRefsInHtml(html, urlByPropertyName) {
7194
+ if (urlByPropertyName.size === 0)
7195
+ return html;
7196
+ return rewriteHtmlAttributes(html, (_el, attrName, value) => {
7197
+ const name = seedPropertyRefName(attrName, value);
7198
+ return name ? urlByPropertyName.get(name) : undefined;
7199
+ });
7200
+ }
7201
+ const SEED_PROPERTY_REF_DATA_TYPES = [
7202
+ ModelPropertyDataTypes.Image,
7203
+ ModelPropertyDataTypes.File,
7204
+ ModelPropertyDataTypes.Json,
7205
+ ];
7206
+ /**
7207
+ * The storage property of `node` a `seed:property/<name>` placeholder names, and the seed it holds.
7208
+ * Html properties are refused: an Html seed may itself be deferred to after the rewrite.
7209
+ */
7210
+ function resolveSeedPropertyRefTarget(node, propertyName) {
7211
+ const property = node.properties.find((p) => p.key === propertyName);
7212
+ if (!property) {
7213
+ throw new HtmlSeedPropertyRefError(`Html references ${SEED_PROPERTY_REF_PREFIX}${propertyName}, but ${node.modelName} has no property ${propertyName}`);
7214
+ }
7215
+ const def = property.definition;
7216
+ const dataType = normalizeDataType(def.dataType);
7217
+ const storageType = dataType === ModelPropertyDataTypes.Relation ? normalizeDataType(def.refValueType) : dataType;
7218
+ if (!SEED_PROPERTY_REF_DATA_TYPES.includes(storageType)) {
7219
+ throw new HtmlSeedPropertyRefError(`Html references ${SEED_PROPERTY_REF_PREFIX}${propertyName}, but ${propertyName} is ${def.dataType}; ` +
7220
+ `only Image, File and Json properties can be referenced`);
7221
+ }
7222
+ const ids = resolveSeedIdsFromRefString(normalizeRelationPropertyValue(property.row?.propertyValue) ?? '');
7223
+ if (!ids.seedLocalId && !ids.seedUid) {
7224
+ throw new HtmlSeedPropertyRefError(`Html references ${SEED_PROPERTY_REF_PREFIX}${propertyName}, but ${propertyName} has no value`);
7225
+ }
7226
+ return ids;
7227
+ }
7228
+ const ARWEAVE_TX_ID$1 = /^[a-z0-9_-]{43}$/i;
7229
+ /**
7230
+ * The Arweave tx of the storage seed a placeholder names: uploaded in this publish's phase 1, or,
7231
+ * when not uploaded now, the transaction it was published with (its storageTransactionId row).
7232
+ */
7233
+ async function resolveSeedPropertyRefTxId(node, propertyName, txBySeedLocalId) {
7234
+ const target = resolveSeedPropertyRefTarget(node, propertyName);
7235
+ const uploaded = target.seedLocalId ? txBySeedLocalId.get(target.seedLocalId) : undefined;
7236
+ if (uploaded)
7237
+ return uploaded;
7238
+ const recorded = await storageSeedTransactionId(target);
7239
+ if (recorded && ARWEAVE_TX_ID$1.test(recorded))
7240
+ return recorded;
7241
+ throw new HtmlSeedPropertyRefError(`Html references ${SEED_PROPERTY_REF_PREFIX}${propertyName}, but ${propertyName} was not uploaded ` +
7242
+ `in this publish and has no published transaction`);
7243
+ }
7244
+ /**
7245
+ * Name of the file holding an embedded image's bytes in the images folder: `<imageSeedLocalId>.<ext>`,
7246
+ * the extension following the image's MIME type (one of {@link ALLOWED_MIMES}).
7247
+ */
7248
+ function embeddedImageFileName(imageSeedLocalId, mimeType) {
7249
+ return `${imageSeedLocalId}.${getExtensionForMimeType(mimeType) ?? 'bin'}`;
7250
+ }
7251
+ /** Path of the file {@link createImageItemFromBytes} wrote for an Image seed, or undefined if there is none. */
7252
+ async function findEmbeddedImageFilePath(imageSeedLocalId) {
7253
+ for (const mimeType of ALLOWED_MIMES) {
7254
+ const filePath = BaseFileManager.getFilesPath('images', embeddedImageFileName(imageSeedLocalId, mimeType));
7255
+ if (await BaseFileManager.pathExists(filePath))
7256
+ return filePath;
7257
+ }
7258
+ return undefined;
7259
+ }
7260
+ /**
7261
+ * Create an Image item for an embedded image and write its bytes to the images folder, where publish
7262
+ * reads them for upload ({@link findEmbeddedImageFilePath}). Its storageTransactionId stays empty
7263
+ * until the upload writes the transaction id, as for any other storage seed.
7264
+ */
7265
+ async function createImageItemFromBytes(bytes, mimeType) {
7266
+ const imageItem = await Item.create({
7267
+ modelName: 'Image',
7268
+ modelFileId: INTERNAL_STORAGE_MODEL_FILE_IDS.image,
7269
+ });
7270
+ await waitForEntityReady(imageItem, { timeout: 60000 });
7271
+ const filePath = BaseFileManager.getFilesPath('images', embeddedImageFileName(imageItem.seedLocalId, mimeType));
7272
+ await BaseFileManager.createDirIfNotExists(BaseFileManager.getFilesPath('images'));
7273
+ const data = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
7274
+ try {
7275
+ await BaseFileManager.saveFile(filePath, data);
7276
+ }
7277
+ catch {
7278
+ const fs = await BaseFileManager.getFs();
7279
+ fs.writeFileSync(filePath, bytes);
7280
+ }
7281
+ const { seedLocalId } = imageItem;
7282
+ // Publish reads the image from its rows when it uploads and attests it
7283
+ imageItem.unload();
7284
+ return { seedLocalId };
7285
+ }
7286
+ /** The Html properties of a node with a stored Html seed and file name. */
7287
+ const htmlValuesOf = (node) => node.properties.flatMap((property) => {
7288
+ if (normalizeDataType(property.definition.dataType) !== ModelPropertyDataTypes.Html)
7289
+ return [];
7290
+ const htmlSeedLocalId = property.row?.propertyValue?.trim();
7291
+ const fileName = property.row?.refResolvedValue;
7292
+ return htmlSeedLocalId && fileName ? [{ property, htmlSeedLocalId, filePath: `${BaseFileManager.getFilesPath('html')}/${fileName}` }] : [];
7293
+ });
7294
+ /**
7295
+ * Scan Html storage properties, materialize embedded images as Image items, insert co-publish rows.
7296
+ * Call before phase-1 Arweave tx build. Does not rewrite Html files yet.
7297
+ */
7298
+ async function prepareHtmlEmbeddedImagesForPublish(item, publishPolicy) {
7299
+ const appDb = BaseDb.getAppDb();
7300
+ if (!appDb) {
7301
+ return { deferredHtmlSeedLocalIds: [] };
7302
+ }
7303
+ const deferred = new Set();
7304
+ // The item's own Html, and the Html of the draft items published with it: each gets its embedded
7305
+ // images materialized and co-published (rows keyed by the item owning the Html property).
7306
+ const graph = await getPublishGraph(item);
7307
+ for (const node of publishGraphItemNodes(graph)) {
7308
+ await prepareHtmlEmbeddedImagesOfNode(appDb, node, publishPolicy, deferred);
7309
+ }
7310
+ return { deferredHtmlSeedLocalIds: [...deferred] };
7311
+ }
7312
+ /**
7313
+ * An embedded image an earlier attempt materialized for this data URI that can be used again: its
7314
+ * Image seed is still a local draft and its file is there.
7315
+ */
7316
+ async function reusableImage(row) {
7317
+ if (!row)
7318
+ return undefined;
7319
+ const seed = await findSeedRow({ localId: row.imageSeedLocalId });
7320
+ if (!seed || isValidEasAttestationUid(seed.uid))
7321
+ return undefined;
7322
+ return (await findEmbeddedImageFilePath(row.imageSeedLocalId)) ? row.imageSeedLocalId : undefined;
7323
+ }
7324
+ async function prepareHtmlEmbeddedImagesOfNode(appDb, node, publishPolicy, deferred) {
7325
+ // An earlier attempt's rows: their images are reused for the same Html and data URI
7326
+ const earlier = node.embeddedImages;
7327
+ const kept = [];
7328
+ for (const { property, htmlSeedLocalId, filePath } of htmlValuesOf(node)) {
7329
+ if (!(await BaseFileManager.pathExists(filePath)))
7330
+ continue;
7331
+ const html = await BaseFileManager.readFileAsString(filePath);
7332
+ // seed:property/ placeholders are filled in after phase 1 whatever the data URI policy. A
7333
+ // placeholder naming no usable property fails here, before anything is uploaded.
7334
+ const refs = findSeedPropertyRefsInHtml(html);
7335
+ for (const name of refs)
7336
+ resolveSeedPropertyRefTarget(node, name);
7337
+ if (refs.length > 0)
7338
+ deferred.add(htmlSeedLocalId);
7339
+ // The policy is the row's definition (a schema file's htmlEmbeddedDataUriPolicy or a code-defined one)
7340
+ const policy = resolveEffectiveHtmlEmbeddedDataUriPolicy(property.definition, publishPolicy);
7341
+ if (policy !== 'materialize')
7342
+ continue;
7343
+ const extracted = await extractDataUriImagesFromHtml(html);
7344
+ if (extracted.length === 0)
7345
+ continue;
7346
+ deferred.add(htmlSeedLocalId);
7347
+ for (const ent of extracted) {
7348
+ const previous = earlier.find((r) => r.htmlSeedLocalId.trim() === htmlSeedLocalId && r.stableKey === ent.stableKey);
7349
+ const reused = await reusableImage(previous);
7350
+ if (reused) {
7351
+ kept.push(previous);
7352
+ continue;
7353
+ }
7354
+ const { seedLocalId: imageSeedLocalId } = await createImageItemFromBytes(ent.bytes, ent.mimeType);
7355
+ if (previous)
7356
+ await appDb.delete(htmlEmbeddedImageCoPublish).where(eq(htmlEmbeddedImageCoPublish.id, previous.id));
7357
+ await appDb.insert(htmlEmbeddedImageCoPublish).values({
7358
+ parentSeedLocalId: node.seedLocalId,
7359
+ htmlSeedLocalId,
7360
+ imageSeedLocalId,
7361
+ stableKey: ent.stableKey,
7362
+ createdAt: Date.now(),
7363
+ }).onConflictDoNothing();
7364
+ }
7365
+ }
7366
+ // Rows for Html or data URIs no longer there
7367
+ for (const row of earlier) {
7368
+ if (kept.includes(row))
7369
+ continue;
7370
+ await appDb.delete(htmlEmbeddedImageCoPublish).where(eq(htmlEmbeddedImageCoPublish.id, row.id));
7371
+ }
7372
+ }
7373
+ /**
7374
+ * After phase-1 uploads, rewrite Html files on disk: data URI → Arweave gateway URL, and
7375
+ * `seed:property/<name>` → the gateway URL of that property's upload.
7376
+ *
7377
+ * `deferredHtmlSeedLocalIds` (from {@link prepareHtmlEmbeddedImagesForPublish}) limits the rewrite to
7378
+ * the Html held back from phase 1; without it, every Html of the item and its co-published drafts is
7379
+ * considered.
7380
+ */
7381
+ async function rewriteHtmlEmbeddedImagesOnDisk(parentSeedLocalId, uploadedTransactions, deferredHtmlSeedLocalIds) {
7382
+ const appDb = BaseDb.getAppDb();
7383
+ if (!appDb)
7384
+ return;
7385
+ const txBySeedLocalId = new Map();
7386
+ for (const u of uploadedTransactions) {
7387
+ if (u.seedLocalId && u.txId) {
7388
+ txBySeedLocalId.set(u.seedLocalId.trim(), u.txId.trim());
7389
+ }
7390
+ }
7391
+ const deferred = deferredHtmlSeedLocalIds
7392
+ ? new Set(deferredHtmlSeedLocalIds.map((id) => id.trim()))
7393
+ : undefined;
7394
+ // The parent's Html and that of the draft items published with it (see prepareHtmlEmbeddedImagesForPublish).
7395
+ const graph = await getPublishGraph({ seedLocalId: parentSeedLocalId });
7396
+ for (const node of publishGraphItemNodes(graph)) {
7397
+ await rewriteHtmlEmbeddedImagesOfNode(node, txBySeedLocalId, deferred);
7398
+ }
7399
+ }
7400
+ async function rewriteHtmlEmbeddedImagesOfNode(node, txBySeedLocalId, deferred) {
7401
+ for (const { htmlSeedLocalId, filePath } of htmlValuesOf(node)) {
7402
+ if (deferred && !deferred.has(htmlSeedLocalId))
7403
+ continue;
7404
+ if (!(await BaseFileManager.pathExists(filePath)))
7405
+ continue;
7406
+ const html = await BaseFileManager.readFileAsString(filePath);
7407
+ const replacements = new Map();
7408
+ const entries = await extractDataUriImagesFromHtml(html);
7409
+ for (const r of node.embeddedImages) {
7410
+ if (r.htmlSeedLocalId.trim() !== htmlSeedLocalId)
7411
+ continue;
7412
+ const txId = txBySeedLocalId.get(r.imageSeedLocalId.trim());
7413
+ const dataUri = entries.find((e) => e.stableKey === r.stableKey)?.dataUri;
7414
+ if (txId && dataUri)
7415
+ replacements.set(dataUri, getPublicArweaveRawUrl(txId));
7416
+ }
7417
+ const urlByPropertyName = new Map();
7418
+ for (const name of findSeedPropertyRefsInHtml(html)) {
7419
+ const txId = await resolveSeedPropertyRefTxId(node, name, txBySeedLocalId);
7420
+ urlByPropertyName.set(name, getPublicArweaveRawUrl(txId));
7421
+ }
7422
+ const newHtml = replaceSeedPropertyRefsInHtml(replaceDataUrisInParsedHtml(html, replacements), urlByPropertyName);
7423
+ if (newHtml === html)
7424
+ continue;
7425
+ try {
7426
+ await BaseFileManager.saveFile(filePath, newHtml);
7427
+ }
7428
+ catch {
7429
+ const fs = await BaseFileManager.getFs();
7430
+ fs.writeFileSync(filePath, newHtml);
7431
+ }
7432
+ }
7433
+ }
7434
+ /**
7435
+ * Remove co-publish rows after a successful publish for this parent seed.
7436
+ */
7437
+ async function clearHtmlEmbeddedImageCoPublishRows(parentSeedLocalId) {
7438
+ const appDb = BaseDb.getAppDb();
7439
+ if (!appDb || !parentSeedLocalId)
7440
+ return;
7441
+ await appDb
7442
+ .delete(htmlEmbeddedImageCoPublish)
7443
+ .where(eq(htmlEmbeddedImageCoPublish.parentSeedLocalId, parentSeedLocalId.trim()));
7444
+ }
7445
+
7446
+ /**
7447
+ * `Promise.all(items.map(fn))` with at most `limit` calls in flight. Results keep the input order.
7448
+ * Rejects with the first error (by time); after that no new calls start, though calls already in
7449
+ * flight run to completion.
7450
+ */
7451
+ async function mapWithConcurrency(items, limit, fn) {
7452
+ const results = new Array(items.length);
7453
+ const workerCount = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
7454
+ let next = 0;
7455
+ let failed = false;
7456
+ const worker = async () => {
7457
+ while (!failed && next < items.length) {
7458
+ const index = next++;
7459
+ try {
7460
+ results[index] = await fn(items[index], index);
7461
+ }
7462
+ catch (error) {
7463
+ failed = true;
7464
+ throw error;
7465
+ }
7466
+ }
7467
+ };
7468
+ await Promise.all(Array.from({ length: workerCount }, worker));
7469
+ return results;
7470
+ }
7471
+
7472
+ /**
7473
+ * Composite uploads: several ItemStorage files of one item in a single Arweave transaction.
7474
+ *
7475
+ * Producer: `getPublishUploads.ts` (`processUploadPropertyData`), for an item with a
7476
+ * `storageTransactionId` property and ItemStorage properties (e.g. a Post's `html` and `json`).
7477
+ * Consumer: the download worker (`browser/workers/filesDownload.ts`), which keeps its own copy of
7478
+ * {@link decodeCompositeUpload} because it runs from a string; a test keeps the two in step.
7479
+ *
7480
+ * Format (unchanged for text children, so composites already on Arweave still read):
7481
+ *
7482
+ * ===FILE_SEPARATOR===<name>===FILE_SEPARATOR===<utf-8 text> (repeated per child)
7483
+ *
7484
+ * A child that is not valid UTF-8 text (or whose text contains the separator) is written as
7485
+ * base64 under the name `<name>;base64`. Composites written before this suffix existed converted
7486
+ * every child to text, which corrupted binary children; those bytes cannot be recovered.
7487
+ */
7488
+ const COMPOSITE_SEPARATOR = '===FILE_SEPARATOR===';
7489
+ const COMPOSITE_BASE64_SUFFIX = ';base64';
7490
+ const bytesToBase64 = (bytes) => {
7491
+ let binary = '';
7492
+ const chunk = 0x8000;
7493
+ for (let i = 0; i < bytes.length; i += chunk) {
7494
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
7495
+ }
7496
+ return btoa(binary);
7497
+ };
7498
+ /** The child's text when it can travel as text: valid UTF-8, no NUL bytes, no separator. */
7499
+ const childAsText = (bytes) => {
7500
+ if (bytes.includes(0))
7501
+ return undefined;
7502
+ let text;
7503
+ try {
7504
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
7505
+ }
7506
+ catch {
7507
+ return undefined;
7508
+ }
7509
+ return text.includes(COMPOSITE_SEPARATOR) ? undefined : text;
7510
+ };
7511
+ const encodeCompositeUpload = (children) => {
7512
+ let composite = '';
7513
+ for (const { name, bytes } of children) {
7514
+ const text = childAsText(bytes);
7515
+ composite +=
7516
+ text !== undefined
7517
+ ? `${COMPOSITE_SEPARATOR}${name}${COMPOSITE_SEPARATOR}${text}`
7518
+ : `${COMPOSITE_SEPARATOR}${name}${COMPOSITE_BASE64_SUFFIX}${COMPOSITE_SEPARATOR}${bytesToBase64(bytes)}`;
7519
+ }
7520
+ return new TextEncoder().encode(composite);
7521
+ };
7522
+ new TextEncoder().encode(COMPOSITE_SEPARATOR);
7523
+
7524
+ const logger = debug('seedSdk:item:getPublishUploads');
7525
+ /** L1 transactions {@link getPublishUploads} creates at once (each asks the gateway for price and anchor). */
7526
+ const CREATE_TRANSACTION_CONCURRENCY = 4;
7527
+ const toUint8Array = async (data) => {
7528
+ if (typeof Blob !== 'undefined' && data instanceof Blob) {
7529
+ return new Uint8Array(await data.arrayBuffer());
7530
+ }
7531
+ return new Uint8Array(data);
7532
+ };
7533
+ /** A local file's bytes plus its MIME type: the Blob's own type, else the name's extension, else a sniff of the bytes. */
7534
+ const readUploadFile = async (filePath, fileName = filePath) => {
7535
+ const data = await BaseFileManager.readFileAsBuffer(filePath);
7536
+ const bytes = await toUint8Array(data);
7537
+ const sourceType = typeof Blob !== 'undefined' && data instanceof Blob ? data.type : undefined;
7538
+ return { bytes, contentType: resolveUploadMimeType({ sourceType, fileName, bytes }) };
7539
+ };
7540
+ /**
7541
+ * The one place an upload's bytes become a {@link PublishUploadData}: everything after this
7542
+ * (Content-SHA-256, tags, the L1 transaction or the bundler DataItem, signing) works from what it
7543
+ * returns. A future private-publish option that encrypts content belongs here, before the hash and
7544
+ * tags are computed, so both publish paths pick it up.
7545
+ */
7546
+ const buildPublishUploadData = (data, contentType, target, options) => {
7547
+ const contentHash = getContentSha256(data);
7548
+ return {
7549
+ data,
7550
+ contentHash,
7551
+ contentType,
7552
+ tags: buildArweaveContentTags(contentHash, contentType, options?.arweaveUploadTags),
7553
+ ...target,
7554
+ };
7555
+ };
7556
+ const isUsablePath = (filePath) => !!filePath && !filePath.endsWith('undefined') && !filePath.endsWith('null');
7557
+ /**
7558
+ * Local file of one ItemStorage property of a node. Tries, in order: its current row's
7559
+ * refResolvedValue + localStorageDir, then (for a local draft, or no row) the name saveItemStorage
7560
+ * gives a new file (`<seedUid or seedLocalId><extension>` in the model's storage directory). A
7561
+ * published or derived row is never swapped for a local file of another name: that file would be
7562
+ * older content.
7563
+ *
7564
+ * `missingLocalEdit` lists the paths tried when nothing exists although the row is a saved local
7565
+ * edit (no attestation uid, not derived from one, refResolvedValue set by saveItemStorage): that
7566
+ * edit's file should be there. A published or derived row without a file (e.g. content not
7567
+ * downloaded, or a property the composite never had) is skipped.
7568
+ */
7569
+ const resolveItemStorageChildFile = async (node, property) => {
7570
+ const tried = [];
7571
+ const tryPath = async (filePath) => {
7572
+ if (!isUsablePath(filePath) || tried.includes(filePath))
7573
+ return undefined;
7574
+ tried.push(filePath);
7575
+ return (await BaseFileManager.pathExists(filePath)) ? filePath : undefined;
7576
+ };
7577
+ const { row, definition } = property;
7578
+ const dir = getItemStorageSubdir(definition.localStorageDir || row?.localStorageDir);
7579
+ if (row?.refResolvedValue) {
7580
+ const fromRow = await tryPath(BaseFileManager.getFilesPath(dir, row.refResolvedValue));
7581
+ if (fromRow)
7582
+ return { filePath: fromRow };
7583
+ }
7584
+ const isLocalDraft = !!row && !row.uid && row.derivedFromUid == null;
7585
+ const seedIds = !row || isLocalDraft ? [row?.seedUid, node.seedUid, row?.seedLocalId, node.seedLocalId] : [];
7586
+ for (const seedId of seedIds) {
7587
+ if (!seedId)
7588
+ continue;
7589
+ const derived = await tryPath(BaseFileManager.getFilesPath(dir, getItemStorageLocalFileName(seedId, definition.filenameSuffix)));
7590
+ if (derived)
7591
+ return { filePath: derived };
7592
+ }
7593
+ // saveItemStorage sets refResolvedValue on the draft row once the file is written. A draft row
7594
+ // without one is the empty row createNewItem makes for every model property: nothing to upload.
7595
+ return isLocalDraft && !!row?.refResolvedValue ? { missingLocalEdit: tried } : {};
7596
+ };
7597
+ const slotOf = (node) => node.properties.find((p) => p.kind === 'storageTransaction');
7598
+ /** Where an upload attesting through `node`'s storageTransactionId is recorded. */
7599
+ const slotTarget = (node) => {
7600
+ const slot = slotOf(node);
7601
+ return {
7602
+ seedLocalId: node.seedLocalId,
7603
+ itemPropertyName: slot?.key ?? 'storageTransactionId',
7604
+ itemPropertyLocalId: slot?.row?.localId ?? '',
7605
+ versionLocalId: slot?.row?.versionLocalId ?? node.version.localId ?? '',
7606
+ };
7607
+ };
7608
+ /** A node's own files: the ItemStorage properties beside its storageTransactionId, as one composite. */
7609
+ async function compositeSource(node) {
7610
+ if (!slotOf(node))
7611
+ return undefined;
7612
+ const children = [];
7613
+ const missingLocalEdits = [];
7614
+ for (const property of node.properties) {
7615
+ if (property.kind !== 'itemStorage')
7616
+ continue;
7617
+ const { filePath, missingLocalEdit } = await resolveItemStorageChildFile(node, property);
7618
+ if (filePath)
7619
+ children.push({ name: property.key, filePath });
7620
+ else if (missingLocalEdit)
7621
+ missingLocalEdits.push({ name: property.key, tried: missingLocalEdit });
7622
+ }
7623
+ if (children.length === 0 && missingLocalEdits.length === 0)
7624
+ return undefined;
7625
+ return { ...slotTarget(node), children, ...(missingLocalEdits.length ? { missingLocalEdits } : {}) };
7626
+ }
7627
+ /** The file of a storage seed this publish attests, named by the referring property's row. */
7628
+ async function storageSeedSource(owner, property) {
7629
+ const target = property.refs[0]?.node;
7630
+ const refResolvedValue = property.row?.refResolvedValue;
7631
+ if (!target || !refResolvedValue)
7632
+ return undefined;
7633
+ const filePath = storageUploadPath({
7634
+ dataType: property.definition.refValueType ?? property.definition.dataType,
7635
+ refResolvedValue,
7636
+ localStorageDir: property.row?.localStorageDir,
7637
+ });
7638
+ if (!(await BaseFileManager.pathExists(filePath)))
7639
+ return undefined;
7640
+ return {
7641
+ seedLocalId: target.seedLocalId,
7642
+ itemPropertyName: property.key,
7643
+ itemPropertyLocalId: property.row?.localId ?? '',
7644
+ versionLocalId: property.row?.versionLocalId ?? owner.version.localId ?? '',
7645
+ filePath,
7646
+ };
7647
+ }
7648
+ const isHtmlStorageProperty = (property) => isDataType(property.definition.refValueType ?? property.definition.dataType, ModelPropertyDataTypes.Html);
7649
+ /**
7650
+ * Every upload a publish makes, in graph order (the root's first), one per attesting seed. The
7651
+ * options select the phases of a publish with Html-embedded images: phase 1 defers the listed Html
7652
+ * storage seeds, phase 2 uploads only those.
7653
+ */
7654
+ async function getPublishUploadSources(graph, options) {
7655
+ const onlyHtml = options?.onlyHtmlStorageSeedLocalIds?.length ? new Set(options.onlyHtmlStorageSeedLocalIds) : undefined;
7656
+ const deferHtml = new Set(options?.deferHtmlStorageSeedLocalIds ?? []);
7657
+ const sources = [];
7658
+ const add = (source) => {
7659
+ if (source && !sources.some((s) => s.seedLocalId === source.seedLocalId))
7660
+ sources.push(source);
7661
+ };
7662
+ for (const node of [graph.root, ...graph.nodes.filter((n) => n !== graph.root)]) {
7663
+ if (!onlyHtml)
7664
+ add(await compositeSource(node));
7665
+ if (node.kind !== 'root' && node.kind !== 'draft')
7666
+ continue;
7667
+ for (const property of node.properties) {
7668
+ if (property.kind !== 'storageSeed' || property.deferred)
7669
+ continue;
7670
+ const seedLocalId = property.refs[0]?.node?.seedLocalId;
7671
+ if (!seedLocalId)
7672
+ continue;
7673
+ if (isHtmlStorageProperty(property) ? deferHtml.has(seedLocalId) || (onlyHtml && !onlyHtml.has(seedLocalId)) : onlyHtml)
7674
+ continue;
7675
+ add(await storageSeedSource(node, property));
7676
+ }
7677
+ if (onlyHtml)
7678
+ continue;
7679
+ for (const row of node.embeddedImages) {
7680
+ const image = graph.nodes.find((n) => n.seedLocalId === row.imageSeedLocalId && n.kind === 'embeddedImage');
7681
+ const filePath = image ? await findEmbeddedImageFilePath(image.seedLocalId) : undefined;
7682
+ if (image && filePath)
7683
+ add({ ...slotTarget(image), filePath });
7684
+ }
7685
+ }
7686
+ return sources;
7687
+ }
7688
+ /** The bytes of one source; a missing local ItemStorage edit fails the publish. */
7689
+ async function readSource(source, options) {
7690
+ const { filePath, children, missingLocalEdits, ...target } = source;
7691
+ const missing = missingLocalEdits?.[0];
7692
+ if (missing) {
7693
+ // Uploading without it would publish a composite that silently lacks this edit.
7694
+ throw new Error(`Cannot publish ${source.seedLocalId}: the local file for ${missing.name} ` +
7695
+ `was not found (looked in ${missing.tried.join(', ')})`);
7696
+ }
7697
+ if (children) {
7698
+ // Composite of the node's ItemStorage files; see helpers/compositeUpload.ts for the format.
7699
+ // Children are read as bytes: a binary child is base64-framed instead of decoded as text.
7700
+ const parts = [];
7701
+ for (const child of children)
7702
+ parts.push({ name: child.name, bytes: (await readUploadFile(child.filePath)).bytes });
7703
+ return buildPublishUploadData(encodeCompositeUpload(parts), 'application/octet-stream', target, options);
7704
+ }
7705
+ const { bytes, contentType } = await readUploadFile(filePath);
7706
+ return buildPublishUploadData(bytes, contentType, target, options);
7707
+ }
7708
+ /** Fails like a publish would when an item's relation names a seed that isn't there. */
7709
+ function assertRefsResolve(graph) {
7710
+ for (const broken of graph.brokenRefs) {
7711
+ if (broken.list)
7712
+ continue;
7713
+ throw new Error(broken.reason === 'invalid'
7714
+ ? `Invalid relation value for ${broken.propertyKey}: expected local seed id or 0x uid`
7715
+ : `No relatedItem found for ${broken.propertyKey}`);
7716
+ }
7717
+ }
7718
+ /**
7719
+ * True if publish would include at least one Arweave upload (local file / storage seed present,
7720
+ * or a saved local edit whose file is missing, which the upload then reports). Reads rows and
7721
+ * file metadata only, for routing (e.g. skip the Arweave steps).
7722
+ */
7723
+ async function itemHasPublishUploadCandidates(item) {
7724
+ const graph = await getPublishGraph(item);
7725
+ assertRefsResolve(graph);
7726
+ return (await getPublishUploadSources(graph)).length > 0;
7727
+ }
7728
+ /**
7729
+ * Upload data for a publish of `item`: its own, and that of every seed the publish attests with it
7730
+ * (see publishGraph.ts). Raw bytes plus Content-SHA-256 / Content-Type / configured tags; no
7731
+ * Arweave transaction is created, so this does not touch the network. The bundler path in
7732
+ * `@seedprotocol/publish` signs these as DataItems; {@link getPublishUploads} turns them into L1
7733
+ * transactions.
7734
+ */
7735
+ const getPublishUploadData = async (item, options) => {
7736
+ const graph = await getPublishGraph(item);
7737
+ assertRefsResolve(graph);
7738
+ const sources = await getPublishUploadSources(graph, options);
7739
+ logger('%d uploads for %s', sources.length, item.seedLocalId);
7740
+ const uploads = [];
7741
+ for (const source of sources)
7742
+ uploads.push(await readSource(source, options));
7743
+ return uploads;
7744
+ };
7745
+ /**
7746
+ * Arweave L1 transactions for a publish of `item`: {@link getPublishUploadData}, each made into an
7747
+ * unsigned transaction carrying the same tags.
7748
+ */
7749
+ const getPublishUploads = async (item, options) => {
7750
+ const uploadData = await getPublishUploadData(item, options);
7751
+ // Each createTransaction fetches the price / anchor from the gateway: run a few at once.
7752
+ return mapWithConcurrency(uploadData, CREATE_TRANSACTION_CONCURRENCY, async (upload) => ({
7753
+ itemPropertyName: upload.itemPropertyName,
7754
+ itemPropertyLocalId: upload.itemPropertyLocalId,
7755
+ seedLocalId: upload.seedLocalId,
7756
+ versionLocalId: upload.versionLocalId,
7757
+ transactionToSign: await BaseArweaveClient.createTransaction(upload.data, {
7758
+ tags: upload.tags.length ? upload.tags : undefined,
7759
+ }),
7760
+ }));
7761
+ };
7762
+
7763
+ var getPublishUploads$1 = /*#__PURE__*/Object.freeze({
7764
+ __proto__: null,
7765
+ CREATE_TRANSACTION_CONCURRENCY: CREATE_TRANSACTION_CONCURRENCY,
7766
+ getPublishUploadData: getPublishUploadData,
7767
+ getPublishUploadSources: getPublishUploadSources,
7768
+ getPublishUploads: getPublishUploads,
7769
+ itemHasPublishUploadCandidates: itemHasPublishUploadCandidates,
7770
+ resolveItemStorageChildFile: resolveItemStorageChildFile
7771
+ });
7772
+
7773
+ /** The item can't be exported as it is. `code` says why. */
7774
+ class ArchiveExportError extends Error {
7775
+ constructor(code, message) {
7776
+ super(message);
7777
+ this.name = 'ArchiveExportError';
7778
+ this.code = code;
7779
+ }
7780
+ }
7781
+ const ARWEAVE_TX_ID = /^[a-z0-9_-]{43}$/i;
7782
+ const SOFTWARE_AGENT = '@seedprotocol/sdk exportArchive (https://github.com/JournoLabs/seed-protocol-sdk)';
7783
+ const isoFromMs = (ms) => typeof ms === 'number' && Number.isFinite(ms) && ms > 0 ? new Date(ms).toISOString() : undefined;
7784
+ const toBytes = async (data) => typeof Blob !== 'undefined' && data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : new Uint8Array(data);
7785
+ const readBytes = async (filePath) => toBytes(await BaseFileManager.readFileAsBuffer(filePath));
7786
+ /** The Arweave transaction id a gateway URL serves (`https://<host>/<tx>` or `/raw/<tx>`), if it is one. */
7787
+ function arweaveTxIdOfUrl(url) {
7788
+ try {
7789
+ const parsed = new URL(url);
7790
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
7791
+ return undefined;
7792
+ const segments = parsed.pathname.split('/').filter(Boolean);
7793
+ const last = segments[segments.length - 1];
7794
+ const isTxPath = segments.length === 1 || (segments.length === 2 && segments[0] === 'raw');
7795
+ return isTxPath && last && ARWEAVE_TX_ID.test(last) ? last : undefined;
7796
+ }
7797
+ catch {
7798
+ return undefined;
7799
+ }
7800
+ }
7801
+ const hasValue = (value) => value != null && value !== '';
7802
+ /** A saved local value that no attestation backs: the bag would mix it with the published record. */
7803
+ const isUnpublishedEdit = (property) => {
7804
+ const row = property.row;
7805
+ return !!row && !property.deferred && !row.uid && row.derivedFromUid == null && hasValue(row.propertyValue);
7806
+ };
7807
+ /**
7808
+ * Builds the bag for `item`'s current published state:
7809
+ *
7810
+ * - `data/index.html` (or `data/<property>.html` when the model has several Html properties): the
7811
+ * Html with references to files in the bag rewritten to relative paths.
7812
+ * - `data/source/<property>.html`: each Html exactly as published.
7813
+ * - `data/assets/<sha256>.<ext>`: Images, Files and images the Html references.
7814
+ * - `data/item.json`: every property's value, attestation uid and Arweave transaction.
7815
+ * - `metadata/dc.xml` and `metadata/premis.xml`.
7816
+ *
7817
+ * Throws {@link ArchiveExportError} `not_published` for an item without a live published version,
7818
+ * and `unpublished_changes` when a property holds a saved value that isn't published yet.
7819
+ * A file that can't be read or downloaded is left out and reported in `omitted`, not thrown.
7820
+ */
7821
+ async function exportArchive(item, options = {}) {
7822
+ const fetch = options.fetch ?? true;
7823
+ const now = options.baggingDate ?? new Date();
7824
+ const graph = await getPublishGraph(item);
7825
+ const root = graph.root;
7826
+ const seedRow = await findSeedRow({ localId: root.seedLocalId });
7827
+ const seedUid = root.seedUid;
7828
+ const version = await getLatestPublishedVersionRow(root.seedLocalId);
7829
+ if (!seedUid || !isValidEasAttestationUid(seedUid) || seedRow?.revokedAt || !version) {
7830
+ throw new ArchiveExportError('not_published', `${root.modelName} ${root.seedLocalId} has no live published version`);
7831
+ }
7832
+ const unpublished = root.properties.filter(isUnpublishedEdit).map((p) => p.key);
7833
+ if (unpublished.length > 0) {
7834
+ throw new ArchiveExportError('unpublished_changes', `${root.modelName} ${root.seedLocalId} has unpublished changes to ${unpublished.join(', ')}; publish them first`);
7835
+ }
7836
+ const chainId = getEasReadChainId();
7837
+ const chain = getEasChainDeployment(chainId);
7838
+ const explorerUrl = (uid) => (chain ? getEasAttestationExplorerUrl(chain, uid) : undefined);
7839
+ const schemaName = item.getService?.().getSnapshot().context.schemaName;
7840
+ const attester = seedRow?.publisher ?? undefined;
7841
+ const omitted = [];
7842
+ const payload = [];
7843
+ const fileInfo = new Map();
7844
+ const assetPathByTx = new Map();
7845
+ const assetPathByProperty = new Map();
7846
+ const htmlSources = [];
7847
+ const properties = {};
7848
+ const addAsset = (bytes, mimeType, arweaveTransactionId) => {
7849
+ const ext = getExtensionForMimeType(mimeType);
7850
+ const path = `assets/${digestHex('sha256', bytes)}${ext ? `.${ext}` : ''}`;
7851
+ if (!payload.some((f) => f.path === path)) {
7852
+ payload.push({ path, bytes });
7853
+ fileInfo.set(path, { mimeType, arweaveTransactionId });
7854
+ }
7855
+ if (arweaveTransactionId)
7856
+ assetPathByTx.set(arweaveTransactionId, path);
7857
+ return path;
7858
+ };
7859
+ for (const property of root.properties) {
7860
+ const { key, definition: def, kind, row } = property;
7861
+ const dataType = normalizeDataType(def.dataType) ?? String(def.dataType);
7862
+ const entry = {
7863
+ dataType,
7864
+ attestationUid: row?.uid ?? row?.derivedFromUid ?? undefined,
7865
+ attestedAt: isoFromMs(row?.attestationCreatedAt),
7866
+ };
7867
+ properties[key] = entry;
7868
+ if (kind === 'basic' || kind === 'storageTransaction') {
7869
+ entry.value = row?.propertyValue ?? null;
7870
+ continue;
7871
+ }
7872
+ if (kind === 'relation' || kind === 'list') {
7873
+ entry.value = kind === 'list' ? property.refs.map((r) => r.ref) : (property.refs[0]?.ref ?? null);
7874
+ continue;
7875
+ }
7876
+ if (kind === 'itemStorage') {
7877
+ const { filePath } = await resolveItemStorageChildFile(root, property);
7878
+ if (!filePath) {
7879
+ if (row && hasValue(row.propertyValue))
7880
+ omitted.push({ property: key, reason: 'its file is not on this device' });
7881
+ continue;
7882
+ }
7883
+ const bytes = await readBytes(filePath);
7884
+ if (dataType === 'Html') {
7885
+ htmlSources.push({ key, text: new TextDecoder().decode(bytes) });
7886
+ }
7887
+ else {
7888
+ const mimeType = resolveUploadMimeType({ fileName: filePath, bytes }) ?? 'application/octet-stream';
7889
+ entry.file = addAsset(bytes, mimeType);
7890
+ assetPathByProperty.set(key, entry.file);
7891
+ }
7892
+ continue;
7893
+ }
7894
+ // storageSeed: the value is a storage seed whose own row records the Arweave transaction.
7895
+ if (!row || !hasValue(row.propertyValue))
7896
+ continue;
7897
+ const storageKind = storedValueKind(def, row);
7898
+ if (!storageKind || storageKind === 'itemStorage') {
7899
+ omitted.push({ property: key, reason: `${dataType} storage seeds are not exported yet` });
7900
+ continue;
7901
+ }
7902
+ const arweaveTransactionId = await storageSeedTransactionId(resolveSeedIdsFromRefString(normalizeRelationPropertyValue(row.propertyValue) ?? ''));
7903
+ if (arweaveTransactionId)
7904
+ entry.arweaveTransactionId = arweaveTransactionId;
7905
+ const display = await resolveStorageDisplay(storageKind, row, { fetch });
7906
+ if (storageKind === 'html') {
7907
+ const text = display.filePath ? await BaseFileManager.readFileAsString(display.filePath) : display.renderValue;
7908
+ if (text === undefined) {
7909
+ omitted.push({ property: key, reason: 'its Html could not be read or downloaded' });
7910
+ continue;
7911
+ }
7912
+ htmlSources.push({ key, text, arweaveTransactionId });
7913
+ continue;
7914
+ }
7915
+ if (!display.filePath) {
7916
+ omitted.push({ property: key, reason: 'its file could not be read or downloaded' });
7917
+ continue;
7918
+ }
7919
+ const bytes = await readBytes(display.filePath);
7920
+ const mimeType = resolveUploadMimeType({ fileName: display.filePath, bytes }) ?? 'application/octet-stream';
7921
+ entry.file = addAsset(bytes, mimeType, arweaveTransactionId);
7922
+ assetPathByProperty.set(key, entry.file);
7923
+ }
7924
+ /** The bag path for an image the Html names by transaction id but no property holds (an embedded image). */
7925
+ const embeddedAssetPath = async (txId) => {
7926
+ const known = assetPathByTx.get(txId);
7927
+ if (known)
7928
+ return known;
7929
+ const imageRow = await getMetadata({ storageTransactionId: txId });
7930
+ let filePath = imageRow?.seedLocalId ? await findEmbeddedImageFilePath(imageRow.seedLocalId) : undefined;
7931
+ if (!filePath) {
7932
+ const display = await resolveStorageDisplay('image', { refResolvedValue: txId, localStorageDir: '/images' }, { fetch });
7933
+ filePath = display.filePath;
7934
+ }
7935
+ if (!filePath)
7936
+ return undefined;
7937
+ const bytes = await readBytes(filePath);
7938
+ const mimeType = resolveUploadMimeType({ fileName: filePath, bytes }) ?? 'application/octet-stream';
7939
+ return addAsset(bytes, mimeType, txId);
7940
+ };
7941
+ const encoder = new TextEncoder();
7942
+ for (const html of htmlSources) {
7943
+ const targets = new Map();
7944
+ // Collect first: rewriteHtmlUrls takes a synchronous mapping.
7945
+ rewriteHtmlUrls(html.text, (url) => {
7946
+ targets.set(url, url);
7947
+ return undefined;
7948
+ });
7949
+ for (const url of targets.keys()) {
7950
+ if (url.startsWith(SEED_PROPERTY_REF_PREFIX)) {
7951
+ const path = assetPathByProperty.get(url.slice(SEED_PROPERTY_REF_PREFIX.length));
7952
+ if (path)
7953
+ targets.set(url, path);
7954
+ else
7955
+ targets.delete(url);
7956
+ continue;
7957
+ }
7958
+ const txId = arweaveTxIdOfUrl(url);
7959
+ const path = txId ? await embeddedAssetPath(txId) : undefined;
7960
+ if (path)
7961
+ targets.set(url, path);
7962
+ else {
7963
+ targets.delete(url);
7964
+ if (txId)
7965
+ omitted.push({ property: html.key, reason: `references ${url}, which is not in the bag` });
7966
+ }
7967
+ }
7968
+ const rewritten = rewriteHtmlUrls(html.text, (url) => targets.get(url));
7969
+ const viewPath = htmlSources.length === 1 ? 'index.html' : `${html.key}.html`;
7970
+ payload.push({ path: viewPath, bytes: encoder.encode(rewritten) });
7971
+ fileInfo.set(viewPath, { mimeType: 'text/html' });
7972
+ const sourcePath = `source/${html.key}.html`;
7973
+ payload.push({ path: sourcePath, bytes: encoder.encode(html.text) });
7974
+ fileInfo.set(sourcePath, { mimeType: 'text/html', arweaveTransactionId: html.arweaveTransactionId });
7975
+ properties[html.key].file = sourcePath;
7976
+ }
7977
+ const itemJson = {
7978
+ seedUid,
7979
+ versionUid: version.uid,
7980
+ schemaName,
7981
+ modelName: root.modelName,
7982
+ chain: { id: chainId, name: chain?.name },
7983
+ attester,
7984
+ properties,
7985
+ omitted,
7986
+ };
7987
+ payload.push({ path: 'item.json', bytes: encoder.encode(`${JSON.stringify(itemJson, null, 2)}\n`) });
7988
+ fileInfo.set('item.json', { mimeType: 'application/json' });
7989
+ // Descriptive metadata comes from the archive document, so nothing is guessed from property names.
7990
+ const primary = htmlSources[0];
7991
+ const doc = primary ? parseArchiveDocument(primary.text) : undefined;
7992
+ const versionDate = isoFromMs(version.attestationCreatedAt);
7993
+ const dc = dublinCoreXml({
7994
+ title: doc?.name,
7995
+ creator: doc?.authors,
7996
+ description: doc?.summary,
7997
+ date: doc?.published ?? versionDate?.slice(0, 10),
7998
+ type: 'Text',
7999
+ format: primary ? 'text/html' : undefined,
8000
+ identifier: [seedUid, explorerUrl(seedUid)].filter((v) => !!v),
8001
+ source: doc?.url,
8002
+ });
8003
+ const premis = premisXml({
8004
+ objects: [
8005
+ {
8006
+ kind: 'intellectualEntity',
8007
+ identifiers: [
8008
+ { type: 'Seed Protocol seed UID', value: seedUid },
8009
+ { type: 'Seed Protocol version UID', value: version.uid },
8010
+ ],
8011
+ },
8012
+ ...payload.map((file) => {
8013
+ const info = fileInfo.get(file.path);
8014
+ return {
8015
+ kind: 'file',
8016
+ identifiers: [{ type: 'bag path', value: `data/${file.path}` }],
8017
+ fixity: { 'SHA-256': digestHex('sha256', file.bytes), 'SHA-512': digestHex('sha512', file.bytes) },
8018
+ size: file.bytes.byteLength,
8019
+ formatName: info?.mimeType ?? 'application/octet-stream',
8020
+ originalName: info?.arweaveTransactionId ? getPublicArweaveRawUrl(info.arweaveTransactionId) : undefined,
8021
+ };
8022
+ }),
8023
+ ],
8024
+ events: [...(await historyEvents(root.seedLocalId, seedUid, version, attester)), ...bagEvents(now, seedUid)],
8025
+ agents: [
8026
+ ...(attester
8027
+ ? [{ identifiers: [{ type: 'EVM address', value: attester }], note: `EAS attester on chain ${chainId}` }]
8028
+ : []),
8029
+ { identifiers: [{ type: 'software', value: '@seedprotocol/sdk' }], name: '@seedprotocol/sdk', type: 'software' },
8030
+ ],
8031
+ });
8032
+ const info = [];
8033
+ if (options.sourceOrganization)
8034
+ info.push(['Source-Organization', options.sourceOrganization]);
8035
+ info.push(['External-Identifier', seedUid]);
8036
+ if (doc?.name)
8037
+ info.push(['External-Description', doc.name]);
8038
+ info.push(['Seed-Uid', seedUid], ['Seed-Version-Uid', version.uid], ['Seed-Model', root.modelName]);
8039
+ if (schemaName)
8040
+ info.push(['Seed-Schema', schemaName]);
8041
+ info.push(['EAS-Chain-Id', String(chainId)]);
8042
+ if (chain?.name)
8043
+ info.push(['EAS-Chain-Name', chain.name]);
8044
+ if (attester)
8045
+ info.push(['EAS-Attester', attester]);
8046
+ const bag = createBag({
8047
+ payload,
8048
+ tagFiles: [
8049
+ { path: 'metadata/dc.xml', bytes: encoder.encode(dc) },
8050
+ { path: 'metadata/premis.xml', bytes: encoder.encode(premis) },
8051
+ ],
8052
+ info,
8053
+ baggingDate: now,
8054
+ softwareAgent: SOFTWARE_AGENT,
8055
+ });
8056
+ return { bag, omitted };
8057
+ }
8058
+ /**
8059
+ * The seed's attested history: its first live Version as creation, later Versions and property
8060
+ * attestations made after the current Version (patches) as modifications. Attestations without
8061
+ * a recorded time are left out, since PREMIS events need one.
8062
+ */
8063
+ async function historyEvents(seedLocalId, seedUid, current, attester) {
8064
+ const db = BaseDb.getAppDb();
8065
+ const agents = attester ? [{ type: 'EVM address', value: attester, role: 'attester' }] : [];
8066
+ const objects = [{ type: 'Seed Protocol seed UID', value: seedUid }];
8067
+ const versionRows = (await db
8068
+ .select({ uid: versions.uid, attestationCreatedAt: versions.attestationCreatedAt, revokedAt: versions.revokedAt })
8069
+ .from(versions)
8070
+ .where(eq(versions.seedLocalId, seedLocalId)))
8071
+ .filter((v) => isValidEasAttestationUid(v.uid) && v.attestationCreatedAt)
8072
+ .sort((a, b) => a.attestationCreatedAt - b.attestationCreatedAt);
8073
+ const events = versionRows.map((v, index) => ({
8074
+ identifier: { type: 'EAS attestation UID', value: v.uid },
8075
+ type: index === 0 ? 'creation' : 'modification',
8076
+ dateTime: isoFromMs(v.attestationCreatedAt),
8077
+ detail: index === 0 ? 'First Version attested' : 'New Version attested',
8078
+ outcome: 'success',
8079
+ outcomeNote: v.revokedAt ? `Version revoked at ${new Date(v.revokedAt * 1000).toISOString()}` : undefined,
8080
+ agents,
8081
+ objects,
8082
+ }));
8083
+ if (current.attestationCreatedAt) {
8084
+ const patches = (await db
8085
+ .select({ uid: metadata$1.uid, propertyName: metadata$1.propertyName, attestationCreatedAt: metadata$1.attestationCreatedAt })
8086
+ .from(metadata$1)
8087
+ .where(and(eq(metadata$1.seedLocalId, seedLocalId), eq(metadata$1.versionUid, current.uid))));
8088
+ for (const row of patches) {
8089
+ if (!isValidEasAttestationUid(row.uid) || !row.attestationCreatedAt)
8090
+ continue;
8091
+ if (row.attestationCreatedAt <= current.attestationCreatedAt)
8092
+ continue;
8093
+ events.push({
8094
+ identifier: { type: 'EAS attestation UID', value: row.uid },
8095
+ type: 'modification',
8096
+ dateTime: isoFromMs(row.attestationCreatedAt),
8097
+ detail: `Property ${row.propertyName} attested again on Version ${current.uid}`,
8098
+ outcome: 'success',
8099
+ agents,
8100
+ objects,
8101
+ });
8102
+ }
8103
+ }
8104
+ return events.sort((a, b) => a.dateTime.localeCompare(b.dateTime));
8105
+ }
8106
+ /** The bag's own events: its checksums and the package itself. */
8107
+ const bagEvents = (now, seedUid) => {
8108
+ const agents = [{ type: 'software', value: '@seedprotocol/sdk', role: 'executing program' }];
8109
+ const objects = [{ type: 'Seed Protocol seed UID', value: seedUid }];
8110
+ const dateTime = now.toISOString();
8111
+ return [
8112
+ {
8113
+ identifier: { type: 'bag event', value: `message-digest-${dateTime}` },
8114
+ type: 'message digest calculation',
8115
+ dateTime,
8116
+ detail: 'SHA-256 and SHA-512 of every payload and tag file',
8117
+ outcome: 'success',
8118
+ agents,
8119
+ objects,
8120
+ },
8121
+ {
8122
+ identifier: { type: 'bag event', value: `package-${dateTime}` },
8123
+ type: 'information package creation',
8124
+ dateTime,
8125
+ detail: 'BagIt 1.0 bag written by exportArchive',
8126
+ outcome: 'success',
8127
+ agents,
8128
+ objects,
8129
+ },
8130
+ ];
8131
+ };
8132
+
6588
8133
  /**
6589
8134
  * Thrown by Item.create when values don't fit the model: unknown keys, wrong types, or values
6590
8135
  * that break the property's validation rules. Nothing is left written: most values are checked
@@ -6634,5 +8179,45 @@ var errors = /*#__PURE__*/Object.freeze({
6634
8179
  UnknownModelError: UnknownModelError
6635
8180
  });
6636
8181
 
6637
- export { sendEasSyncClientRequest as $, waitForEasReadChain as A, assertLocalDbChain as B, ConflictError as C, writeModelUids as D, getContentHash as E, saveDefinition as F, EAS_CHAIN_ID_APP_STATE_KEY as G, INIT_CONFIG_CONFLICT_MESSAGE as H, ItemCreateValidationError as I, ItemCreateError as J, claimUnpublishedDrafts as K, createLocalQueryDataSource as L, Model as M, easSyncActor as N, getClient as O, getPublishedSeedRecord as P, getSchemaNameFromId as Q, RelationTargetError as R, SCHEMA_FILE_FORMAT as S, TModelSchema as T, UnknownModelError as U, isSeedQueryLocalSourceRegistered as V, listCompleteSchemaFiles as W, mergeEasSyncRequestIntents as X, registerSeedQueryLocalSource as Y, requestEasSyncFromAddressChange as Z, requestEasSyncFromModelsInit as _, applyCreateItem as a, setRetriesWhenOnlineEnabled as a0, startEasSyncActor as a1, unregisterSeedQueryLocalSource as a2, saveDefinition$1 as a3, Model$1 as a4, schema as a5, SEEDPROTOCOL_Seed_Protocol_v1 as a6, itemAppliers as a7, errors as a8, applyDelete as b, applyItemStorageValue as c, deleteFiles as d, applyStorageValue as e, TModelValues as f, ClientManager as g, syncSchemaFromSource as h, importJsonSchema as i, transformImportToSchemaFile as j, localModelSchemaLookups as k, loadSchemaFromFile as l, listLatestSchemaFiles as m, readSchemaFile as n, applySchemaFile as o, resolveSchemaRow as p, loadAllSchemasFromDb as q, readJsonImportFile as r, startItemOutbox as s, throwItemWriteError as t, getDraftSaveError as u, queueDraftSave as v, writeNextSchemaVersion as w, setupEntityLiveQuery as x, mergeSchemaFiles as y, trackDraftSave as z };
6638
- //# sourceMappingURL=errors-CnT0fSMK.js.map
8182
+ /** Error thrown when publish validation fails. Includes all validation errors for user to fix. */
8183
+ class PublishValidationFailedError extends Error {
8184
+ constructor(message, validationErrors) {
8185
+ super(message);
8186
+ this.validationErrors = validationErrors;
8187
+ this.name = 'PublishValidationFailedError';
8188
+ }
8189
+ }
8190
+ /** Validation error code for a relation, list member or image/file ref to an unpublished seed. */
8191
+ const RELATED_ITEM_UNPUBLISHED_CODE = 'related_item_unpublished';
8192
+ const describeUnpublishedRelatedItem = (r) => `${r.propertyName} -> ${r.modelName} ${r.seedLocalId} (seed ${r.seedUid})`;
8193
+ /**
8194
+ * Thrown before anything is uploaded or attested when the item references items whose seed was
8195
+ * unpublished: the parent would attest a revoked seed uid. Republish those items (which gives
8196
+ * them a new seed uid) or remove the references, then publish again.
8197
+ * See docs/ATTESTATION_REVOCATION.md ("Republishing").
8198
+ *
8199
+ * A PublishValidationFailedError, so validateItemForPublish and the publish package's checking
8200
+ * step report it as validation errors (code `related_item_unpublished`, one per reference).
8201
+ */
8202
+ class RelatedItemUnpublishedError extends PublishValidationFailedError {
8203
+ constructor(unpublishedRelatedItems, otherValidationErrors = []) {
8204
+ const lines = unpublishedRelatedItems.map(describeUnpublishedRelatedItem);
8205
+ const message = `Cannot publish: it references ${lines.length} unpublished item${lines.length === 1 ? '' : 's'} ` +
8206
+ `(their seed attestations were revoked). Republish them or remove the references, then publish again:\n` +
8207
+ lines.join('\n');
8208
+ super(message, [
8209
+ ...unpublishedRelatedItems.map((r) => ({
8210
+ field: r.propertyName,
8211
+ message: `${r.propertyName} references unpublished ${r.modelName} ${r.seedLocalId} (seed ${r.seedUid}). ` +
8212
+ `Republish it or remove the reference.`,
8213
+ code: RELATED_ITEM_UNPUBLISHED_CODE,
8214
+ })),
8215
+ ...otherValidationErrors,
8216
+ ]);
8217
+ this.name = 'RelatedItemUnpublishedError';
8218
+ this.unpublishedRelatedItems = unpublishedRelatedItems;
8219
+ }
8220
+ }
8221
+
8222
+ export { extractDataUriImagesFromHtml as $, ArchiveExportError as A, waitForEasReadChain as B, ConflictError as C, assertLocalDbChain as D, writeModelUids as E, getPublishGraph as F, htmlPropertyForHtmlSeed as G, HtmlEmbeddedDataUriLimitError as H, ItemCreateError as I, saveDefinition as J, getPublishUploadSources as K, EAS_CHAIN_ID_APP_STATE_KEY as L, Model as M, HTML_EMBEDDED_MAX_IMAGES_PER_DOC as N, HTML_EMBEDDED_MAX_IMAGE_BYTES as O, PublishValidationFailedError as P, HTML_EMBEDDED_MAX_TOTAL_BYTES as Q, RELATED_ITEM_UNPUBLISHED_CODE as R, SCHEMA_FILE_FORMAT as S, TModelSchema as T, UnknownModelError as U, INIT_CONFIG_CONFLICT_MESSAGE as V, SEED_PROPERTY_REF_PREFIX as W, claimUnpublishedDrafts as X, clearHtmlEmbeddedImageCoPublishRows as Y, createLocalQueryDataSource as Z, easSyncActor as _, HtmlSeedPropertyRefError as a, findSeedPropertyRefsInHtml as a0, getClient as a1, getPublishUploadData as a2, getPublishedSeedRecord as a3, getSchemaNameFromId as a4, isSeedQueryLocalSourceRegistered as a5, itemHasPublishUploadCandidates as a6, listCompleteSchemaFiles as a7, mergeEasSyncRequestIntents as a8, prepareHtmlEmbeddedImagesForPublish as a9, registerSeedQueryLocalSource as aa, replaceDataUrisInParsedHtml as ab, replaceSeedPropertyRefsInHtml as ac, requestEasSyncFromAddressChange as ad, requestEasSyncFromModelsInit as ae, resolveEffectiveHtmlEmbeddedDataUriPolicy as af, rewriteHtmlEmbeddedImagesOnDisk as ag, sendEasSyncClientRequest as ah, setRetriesWhenOnlineEnabled as ai, startEasSyncActor as aj, unregisterSeedQueryLocalSource as ak, saveDefinition$1 as al, Model$1 as am, schema as an, SEEDPROTOCOL_Seed_Protocol_v1 as ao, itemAppliers as ap, getPublishUploads$1 as aq, errors as ar, ItemCreateValidationError as b, RelatedItemUnpublishedError as c, deleteFiles as d, RelationTargetError as e, ClientManager as f, exportArchive as g, syncSchemaFromSource as h, importJsonSchema as i, transformImportToSchemaFile as j, localModelSchemaLookups as k, loadSchemaFromFile as l, listLatestSchemaFiles as m, readSchemaFile as n, applySchemaFile as o, resolveSchemaRow as p, loadAllSchemasFromDb as q, readJsonImportFile as r, startItemOutbox as s, throwItemWriteError as t, getDraftSaveError as u, queueDraftSave as v, writeNextSchemaVersion as w, setupEntityLiveQuery as x, mergeSchemaFiles as y, trackDraftSave as z };
8223
+ //# sourceMappingURL=publishErrors-DZXDX3B6.js.map